| 30 |
ashish |
1 |
%-----------------------------------------------------------------------------
|
|
|
2 |
%
|
|
|
3 |
% Thrift whitepaper
|
|
|
4 |
%
|
|
|
5 |
% Name: thrift.tex
|
|
|
6 |
%
|
|
|
7 |
% Authors: Mark Slee (mcslee@facebook.com)
|
|
|
8 |
%
|
|
|
9 |
% Created: 05 March 2007
|
|
|
10 |
%
|
|
|
11 |
% You will need a copy of sigplanconf.cls to format this document.
|
|
|
12 |
% It is available at <http://www.sigplan.org/authorInformation.htm>.
|
|
|
13 |
%
|
|
|
14 |
%-----------------------------------------------------------------------------
|
|
|
15 |
|
|
|
16 |
|
|
|
17 |
\documentclass[nocopyrightspace,blockstyle]{sigplanconf}
|
|
|
18 |
|
|
|
19 |
\usepackage{amssymb}
|
|
|
20 |
\usepackage{amsfonts}
|
|
|
21 |
\usepackage{amsmath}
|
|
|
22 |
\usepackage{url}
|
|
|
23 |
|
|
|
24 |
\begin{document}
|
|
|
25 |
|
|
|
26 |
% \conferenceinfo{WXYZ '05}{date, City.}
|
|
|
27 |
% \copyrightyear{2007}
|
|
|
28 |
% \copyrightdata{[to be supplied]}
|
|
|
29 |
|
|
|
30 |
% \titlebanner{banner above paper title} % These are ignored unless
|
|
|
31 |
% \preprintfooter{short description of paper} % 'preprint' option specified.
|
|
|
32 |
|
|
|
33 |
\title{Thrift: Scalable Cross-Language Services Implementation}
|
|
|
34 |
\subtitle{}
|
|
|
35 |
|
|
|
36 |
\authorinfo{Mark Slee, Aditya Agarwal and Marc Kwiatkowski}
|
|
|
37 |
{Facebook, 156 University Ave, Palo Alto, CA}
|
|
|
38 |
{\{mcslee,aditya,marc\}@facebook.com}
|
|
|
39 |
|
|
|
40 |
\maketitle
|
|
|
41 |
|
|
|
42 |
\begin{abstract}
|
|
|
43 |
Thrift is a software library and set of code-generation tools developed at
|
|
|
44 |
Facebook to expedite development and implementation of efficient and scalable
|
|
|
45 |
backend services. Its primary goal is to enable efficient and reliable
|
|
|
46 |
communication across programming languages by abstracting the portions of each
|
|
|
47 |
language that tend to require the most customization into a common library
|
|
|
48 |
that is implemented in each language. Specifically, Thrift allows developers to
|
|
|
49 |
define datatypes and service interfaces in a single language-neutral file
|
|
|
50 |
and generate all the necessary code to build RPC clients and servers.
|
|
|
51 |
|
|
|
52 |
This paper details the motivations and design choices we made in Thrift, as
|
|
|
53 |
well as some of the more interesting implementation details. It is not
|
|
|
54 |
intended to be taken as research, but rather it is an exposition on what we did
|
|
|
55 |
and why.
|
|
|
56 |
\end{abstract}
|
|
|
57 |
|
|
|
58 |
% \category{D.3.3}{Programming Languages}{Language constructs and features}
|
|
|
59 |
|
|
|
60 |
%\terms
|
|
|
61 |
%Languages, serialization, remote procedure call
|
|
|
62 |
|
|
|
63 |
%\keywords
|
|
|
64 |
%Data description language, interface definition language, remote procedure call
|
|
|
65 |
|
|
|
66 |
\section{Introduction}
|
|
|
67 |
As Facebook's traffic and network structure have scaled, the resource
|
|
|
68 |
demands of many operations on the site (i.e. search,
|
|
|
69 |
ad selection and delivery, event logging) have presented technical requirements
|
|
|
70 |
drastically outside the scope of the LAMP framework. In our implementation of
|
|
|
71 |
these services, various programming languages have been selected to
|
|
|
72 |
optimize for the right combination of performance, ease and speed of
|
|
|
73 |
development, availability of existing libraries, etc. By and large,
|
|
|
74 |
Facebook's engineering culture has tended towards choosing the best
|
|
|
75 |
tools and implementations available over standardizing on any one
|
|
|
76 |
programming language and begrudgingly accepting its inherent limitations.
|
|
|
77 |
|
|
|
78 |
Given this design choice, we were presented with the challenge of building
|
|
|
79 |
a transparent, high-performance bridge across many programming languages.
|
|
|
80 |
We found that most available solutions were either too limited, did not offer
|
|
|
81 |
sufficient datatype freedom, or suffered from subpar performance.
|
|
|
82 |
\footnote{See Appendix A for a discussion of alternative systems.}
|
|
|
83 |
|
|
|
84 |
The solution that we have implemented combines a language-neutral software
|
|
|
85 |
stack implemented across numerous programming languages and an associated code
|
|
|
86 |
generation engine that transforms a simple interface and data definition
|
|
|
87 |
language into client and server remote procedure call libraries.
|
|
|
88 |
Choosing static code generation over a dynamic system allows us to create
|
|
|
89 |
validated code that can be run without the need for
|
|
|
90 |
any advanced introspective run-time type checking. It is also designed to
|
|
|
91 |
be as simple as possible for the developer, who can typically define all
|
|
|
92 |
the necessary data structures and interfaces for a complex service in a single
|
|
|
93 |
short file.
|
|
|
94 |
|
|
|
95 |
Surprised that a robust open solution to these relatively common problems
|
|
|
96 |
did not yet exist, we committed early on to making the Thrift implementation
|
|
|
97 |
open source.
|
|
|
98 |
|
|
|
99 |
In evaluating the challenges of cross-language interaction in a networked
|
|
|
100 |
environment, some key components were identified:
|
|
|
101 |
|
|
|
102 |
\textit{Types.} A common type system must exist across programming languages
|
|
|
103 |
without requiring that the application developer use custom Thrift datatypes
|
|
|
104 |
or write their own serialization code. That is,
|
|
|
105 |
a C++ programmer should be able to transparently exchange a strongly typed
|
|
|
106 |
STL map for a dynamic Python dictionary. Neither
|
|
|
107 |
programmer should be forced to write any code below the application layer
|
|
|
108 |
to achieve this. Section 2 details the Thrift type system.
|
|
|
109 |
|
|
|
110 |
\textit{Transport.} Each language must have a common interface to
|
|
|
111 |
bidirectional raw data transport. The specifics of how a given
|
|
|
112 |
transport is implemented should not matter to the service developer.
|
|
|
113 |
The same application code should be able to run against TCP stream sockets,
|
|
|
114 |
raw data in memory, or files on disk. Section 3 details the Thrift Transport
|
|
|
115 |
layer.
|
|
|
116 |
|
|
|
117 |
\textit{Protocol.} Datatypes must have some way of using the Transport
|
|
|
118 |
layer to encode and decode themselves. Again, the application
|
|
|
119 |
developer need not be concerned by this layer. Whether the service uses
|
|
|
120 |
an XML or binary protocol is immaterial to the application code.
|
|
|
121 |
All that matters is that the data can be read and written in a consistent,
|
|
|
122 |
deterministic matter. Section 4 details the Thrift Protocol layer.
|
|
|
123 |
|
|
|
124 |
\textit{Versioning.} For robust services, the involved datatypes must
|
|
|
125 |
provide a mechanism for versioning themselves. Specifically,
|
|
|
126 |
it should be possible to add or remove fields in an object or alter the
|
|
|
127 |
argument list of a function without any interruption in service (or,
|
|
|
128 |
worse yet, nasty segmentation faults). Section 5 details Thrift's versioning
|
|
|
129 |
system.
|
|
|
130 |
|
|
|
131 |
\textit{Processors.} Finally, we generate code capable of processing data
|
|
|
132 |
streams to accomplish remote procedure calls. Section 6 details the generated
|
|
|
133 |
code and TProcessor paradigm.
|
|
|
134 |
|
|
|
135 |
Section 7 discusses implementation details, and Section 8 describes
|
|
|
136 |
our conclusions.
|
|
|
137 |
|
|
|
138 |
\section{Types}
|
|
|
139 |
|
|
|
140 |
The goal of the Thrift type system is to enable programmers to develop using
|
|
|
141 |
completely natively defined types, no matter what programming language they
|
|
|
142 |
use. By design, the Thrift type system does not introduce any special dynamic
|
|
|
143 |
types or wrapper objects. It also does not require that the developer write
|
|
|
144 |
any code for object serialization or transport. The Thrift IDL (Interface
|
|
|
145 |
Definition Language) file is
|
|
|
146 |
logically a way for developers to annotate their data structures with the
|
|
|
147 |
minimal amount of extra information necessary to tell a code generator
|
|
|
148 |
how to safely transport the objects across languages.
|
|
|
149 |
|
|
|
150 |
\subsection{Base Types}
|
|
|
151 |
|
|
|
152 |
The type system rests upon a few base types. In considering which types to
|
|
|
153 |
support, we aimed for clarity and simplicity over abundance, focusing
|
|
|
154 |
on the key types available in all programming languages, ommitting any
|
|
|
155 |
niche types available only in specific languages.
|
|
|
156 |
|
|
|
157 |
The base types supported by Thrift are:
|
|
|
158 |
\begin{itemize}
|
|
|
159 |
\item \texttt{bool} A boolean value, true or false
|
|
|
160 |
\item \texttt{byte} A signed byte
|
|
|
161 |
\item \texttt{i16} A 16-bit signed integer
|
|
|
162 |
\item \texttt{i32} A 32-bit signed integer
|
|
|
163 |
\item \texttt{i64} A 64-bit signed integer
|
|
|
164 |
\item \texttt{double} A 64-bit floating point number
|
|
|
165 |
\item \texttt{string} An encoding-agnostic text or binary string
|
|
|
166 |
\item \texttt{binary} A byte array representation for blobs
|
|
|
167 |
\end{itemize}
|
|
|
168 |
|
|
|
169 |
Of particular note is the absence of unsigned integer types. Because these
|
|
|
170 |
types have no direct translation to native primitive types in many languages,
|
|
|
171 |
the advantages they afford are lost. Further, there is no way to prevent the
|
|
|
172 |
application developer in a language like Python from assigning a negative value
|
|
|
173 |
to an integer variable, leading to unpredictable behavior. From a design
|
|
|
174 |
standpoint, we observed that unsigned integers were very rarely, if ever, used
|
|
|
175 |
for arithmetic purposes, but in practice were much more often used as keys or
|
|
|
176 |
identifiers. In this case, the sign is irrelevant. Signed integers serve this
|
|
|
177 |
same purpose and can be safely cast to their unsigned counterparts (most
|
|
|
178 |
commonly in C++) when absolutely necessary.
|
|
|
179 |
|
|
|
180 |
\subsection{Structs}
|
|
|
181 |
|
|
|
182 |
A Thrift struct defines a common object to be used across languages. A struct
|
|
|
183 |
is essentially equivalent to a class in object oriented programming
|
|
|
184 |
languages. A struct has a set of strongly typed fields, each with a unique
|
|
|
185 |
name identifier. The basic syntax for defining a Thrift struct looks very
|
|
|
186 |
similar to a C struct definition. Fields may be annotated with an integer field
|
|
|
187 |
identifier (unique to the scope of that struct) and optional default values.
|
|
|
188 |
Field identifiers will be automatically assigned if omitted, though they are
|
|
|
189 |
strongly encouraged for versioning reasons discussed later.
|
|
|
190 |
|
|
|
191 |
\subsection{Containers}
|
|
|
192 |
|
|
|
193 |
Thrift containers are strongly typed containers that map to the most commonly
|
|
|
194 |
used containers in common programming languages. They are annotated using
|
|
|
195 |
the C++ template (or Java Generics) style. There are three types available:
|
|
|
196 |
\begin{itemize}
|
|
|
197 |
\item \texttt{list<type>} An ordered list of elements. Translates directly into
|
|
|
198 |
an STL \texttt{vector}, Java \texttt{ArrayList}, or native array in scripting languages. May
|
|
|
199 |
contain duplicates.
|
|
|
200 |
\item \texttt{set<type>} An unordered set of unique elements. Translates into
|
|
|
201 |
an STL \texttt{set}, Java \texttt{HashSet}, \texttt{set} in Python, or native
|
|
|
202 |
dictionary in PHP/Ruby.
|
|
|
203 |
\item \texttt{map<type1,type2>} A map of strictly unique keys to values
|
|
|
204 |
Translates into an STL \texttt{map}, Java \texttt{HashMap}, PHP associative
|
|
|
205 |
array, or Python/Ruby dictionary.
|
|
|
206 |
\end{itemize}
|
|
|
207 |
|
|
|
208 |
While defaults are provided, the type mappings are not explicitly fixed. Custom
|
|
|
209 |
code generator directives have been added to substitute custom types in
|
|
|
210 |
destination languages (i.e.
|
|
|
211 |
\texttt{hash\_map} or Google's sparse hash map can be used in C++). The
|
|
|
212 |
only requirement is that the custom types support all the necessary iteration
|
|
|
213 |
primitives. Container elements may be of any valid Thrift type, including other
|
|
|
214 |
containers or structs.
|
|
|
215 |
|
|
|
216 |
\begin{verbatim}
|
|
|
217 |
struct Example {
|
|
|
218 |
1:i32 number=10,
|
|
|
219 |
2:i64 bigNumber,
|
|
|
220 |
3:double decimals,
|
|
|
221 |
4:string name="thrifty"
|
|
|
222 |
}\end{verbatim}
|
|
|
223 |
|
|
|
224 |
In the target language, each definition generates a type with two methods,
|
|
|
225 |
\texttt{read} and \texttt{write}, which perform serialization and transport
|
|
|
226 |
of the objects using a Thrift TProtocol object.
|
|
|
227 |
|
|
|
228 |
\subsection{Exceptions}
|
|
|
229 |
|
|
|
230 |
Exceptions are syntactically and functionally equivalent to structs except
|
|
|
231 |
that they are declared using the \texttt{exception} keyword instead of the
|
|
|
232 |
\texttt{struct} keyword.
|
|
|
233 |
|
|
|
234 |
The generated objects inherit from an exception base class as appropriate
|
|
|
235 |
in each target programming language, in order to seamlessly
|
|
|
236 |
integrate with native exception handling in any given
|
|
|
237 |
language. Again, the design emphasis is on making the code familiar to the
|
|
|
238 |
application developer.
|
|
|
239 |
|
|
|
240 |
\subsection{Services}
|
|
|
241 |
|
|
|
242 |
Services are defined using Thrift types. Definition of a service is
|
|
|
243 |
semantically equivalent to defining an interface (or a pure virtual abstract
|
|
|
244 |
class) in object oriented
|
|
|
245 |
programming. The Thrift compiler generates fully functional client and
|
|
|
246 |
server stubs that implement the interface. Services are defined as follows:
|
|
|
247 |
|
|
|
248 |
\begin{verbatim}
|
|
|
249 |
service <name> {
|
|
|
250 |
<returntype> <name>(<arguments>)
|
|
|
251 |
[throws (<exceptions>)]
|
|
|
252 |
...
|
|
|
253 |
}\end{verbatim}
|
|
|
254 |
|
|
|
255 |
An example:
|
|
|
256 |
|
|
|
257 |
\begin{verbatim}
|
|
|
258 |
service StringCache {
|
|
|
259 |
void set(1:i32 key, 2:string value),
|
|
|
260 |
string get(1:i32 key) throws (1:KeyNotFound knf),
|
|
|
261 |
void delete(1:i32 key)
|
|
|
262 |
}
|
|
|
263 |
\end{verbatim}
|
|
|
264 |
|
|
|
265 |
Note that \texttt{void} is a valid type for a function return, in addition to
|
|
|
266 |
all other defined Thrift types. Additionally, an \texttt{async} modifier
|
|
|
267 |
keyword may be added to a \texttt{void} function, which will generate code that does
|
|
|
268 |
not wait for a response from the server. Note that a pure \texttt{void}
|
|
|
269 |
function will return a response to the client which guarantees that the
|
|
|
270 |
operation has completed on the server side. With \texttt{async} method calls
|
|
|
271 |
the client will only be guaranteed that the request succeeded at the
|
|
|
272 |
transport layer. (In many transport scenarios this is inherently unreliable
|
|
|
273 |
due to the Byzantine Generals' Problem. Therefore, application developers
|
|
|
274 |
should take care only to use the async optimization in cases where dropped
|
|
|
275 |
method calls are acceptable or the transport is known to be reliable.)
|
|
|
276 |
|
|
|
277 |
Also of note is the fact that argument lists and exception lists for functions
|
|
|
278 |
are implemented as Thrift structs. All three constructs are identical in both
|
|
|
279 |
notation and behavior.
|
|
|
280 |
|
|
|
281 |
\section{Transport}
|
|
|
282 |
|
|
|
283 |
The transport layer is used by the generated code to facilitate data transfer.
|
|
|
284 |
|
|
|
285 |
\subsection{Interface}
|
|
|
286 |
|
|
|
287 |
A key design choice in the implementation of Thrift was to decouple the
|
|
|
288 |
transport layer from the code generation layer. Though Thrift is typically
|
|
|
289 |
used on top of the TCP/IP stack with streaming sockets as the base layer of
|
|
|
290 |
communication, there was no compelling reason to build that constraint into
|
|
|
291 |
the system. The performance tradeoff incurred by an abstracted I/O layer
|
|
|
292 |
(roughly one virtual method lookup / function call per operation) was
|
|
|
293 |
immaterial compared to the cost of actual I/O operations (typically invoking
|
|
|
294 |
system calls).
|
|
|
295 |
|
|
|
296 |
Fundamentally, generated Thrift code only needs to know how to read and
|
|
|
297 |
write data. The origin and destination of the data are irrelevant; it may be a
|
|
|
298 |
socket, a segment of shared memory, or a file on the local disk. The Thrift
|
|
|
299 |
transport interface supports the following methods:
|
|
|
300 |
|
|
|
301 |
\begin{itemize}
|
|
|
302 |
\item \texttt{open} Opens the tranpsort
|
|
|
303 |
\item \texttt{close} Closes the tranport
|
|
|
304 |
\item \texttt{isOpen} Indicates whether the transport is open
|
|
|
305 |
\item \texttt{read} Reads from the transport
|
|
|
306 |
\item \texttt{write} Writes to the transport
|
|
|
307 |
\item \texttt{flush} Forces any pending writes
|
|
|
308 |
\end{itemize}
|
|
|
309 |
|
|
|
310 |
There are a few additional methods not documented here which are used to aid
|
|
|
311 |
in batching reads and optionally signaling the completion of a read or
|
|
|
312 |
write operation from the generated code.
|
|
|
313 |
|
|
|
314 |
In addition to the above
|
|
|
315 |
\texttt{TTransport} interface, there is a\\
|
|
|
316 |
\texttt{TServerTransport} interface
|
|
|
317 |
used to accept or create primitive transport objects. Its interface is as
|
|
|
318 |
follows:
|
|
|
319 |
|
|
|
320 |
\begin{itemize}
|
|
|
321 |
\item \texttt{open} Opens the transport
|
|
|
322 |
\item \texttt{listen} Begins listening for connections
|
|
|
323 |
\item \texttt{accept} Returns a new client transport
|
|
|
324 |
\item \texttt{close} Closes the transport
|
|
|
325 |
\end{itemize}
|
|
|
326 |
|
|
|
327 |
\subsection{Implementation}
|
|
|
328 |
|
|
|
329 |
The transport interface is designed for simple implementation in any
|
|
|
330 |
programming language. New transport mechanisms can be easily defined as needed
|
|
|
331 |
by application developers.
|
|
|
332 |
|
|
|
333 |
\subsubsection{TSocket}
|
|
|
334 |
|
|
|
335 |
The \texttt{TSocket} class is implemented across all target languages. It
|
|
|
336 |
provides a common, simple interface to a TCP/IP stream socket.
|
|
|
337 |
|
|
|
338 |
\subsubsection{TFileTransport}
|
|
|
339 |
|
|
|
340 |
The \texttt{TFileTransport} is an abstraction of an on-disk file to a data
|
|
|
341 |
stream. It can be used to write out a set of incoming Thrift requests to a file
|
|
|
342 |
on disk. The on-disk data can then be replayed from the log, either for
|
|
|
343 |
post-processing or for reproduction and/or simulation of past events.
|
|
|
344 |
|
|
|
345 |
\subsubsection{Utilities}
|
|
|
346 |
|
|
|
347 |
The Transport interface is designed to support easy extension using common
|
|
|
348 |
OOP techniques, such as composition. Some simple utilites include the
|
|
|
349 |
\texttt{TBufferedTransport}, which buffers the writes and reads on an
|
|
|
350 |
underlying transport, the \texttt{TFramedTransport}, which transmits data with frame
|
|
|
351 |
size headers for chunking optimization or nonblocking operation, and the
|
|
|
352 |
\texttt{TMemoryBuffer}, which allows reading and writing directly from the heap
|
|
|
353 |
or stack memory owned by the process.
|
|
|
354 |
|
|
|
355 |
\section{Protocol}
|
|
|
356 |
|
|
|
357 |
A second major abstraction in Thrift is the separation of data structure from
|
|
|
358 |
transport representation. Thrift enforces a certain messaging structure when
|
|
|
359 |
transporting data, but it is agnostic to the protocol encoding in use. That is,
|
|
|
360 |
it does not matter whether data is encoded as XML, human-readable ASCII, or a
|
|
|
361 |
dense binary format as long as the data supports a fixed set of operations
|
|
|
362 |
that allow it to be deterministically read and written by generated code.
|
|
|
363 |
|
|
|
364 |
\subsection{Interface}
|
|
|
365 |
|
|
|
366 |
The Thrift Protocol interface is very straightforward. It fundamentally
|
|
|
367 |
supports two things: 1) bidirectional sequenced messaging, and
|
|
|
368 |
2) encoding of base types, containers, and structs.
|
|
|
369 |
|
|
|
370 |
\begin{verbatim}
|
|
|
371 |
writeMessageBegin(name, type, seq)
|
|
|
372 |
writeMessageEnd()
|
|
|
373 |
writeStructBegin(name)
|
|
|
374 |
writeStructEnd()
|
|
|
375 |
writeFieldBegin(name, type, id)
|
|
|
376 |
writeFieldEnd()
|
|
|
377 |
writeFieldStop()
|
|
|
378 |
writeMapBegin(ktype, vtype, size)
|
|
|
379 |
writeMapEnd()
|
|
|
380 |
writeListBegin(etype, size)
|
|
|
381 |
writeListEnd()
|
|
|
382 |
writeSetBegin(etype, size)
|
|
|
383 |
writeSetEnd()
|
|
|
384 |
writeBool(bool)
|
|
|
385 |
writeByte(byte)
|
|
|
386 |
writeI16(i16)
|
|
|
387 |
writeI32(i32)
|
|
|
388 |
writeI64(i64)
|
|
|
389 |
writeDouble(double)
|
|
|
390 |
writeString(string)
|
|
|
391 |
|
|
|
392 |
name, type, seq = readMessageBegin()
|
|
|
393 |
readMessageEnd()
|
|
|
394 |
name = readStructBegin()
|
|
|
395 |
readStructEnd()
|
|
|
396 |
name, type, id = readFieldBegin()
|
|
|
397 |
readFieldEnd()
|
|
|
398 |
k, v, size = readMapBegin()
|
|
|
399 |
readMapEnd()
|
|
|
400 |
etype, size = readListBegin()
|
|
|
401 |
readListEnd()
|
|
|
402 |
etype, size = readSetBegin()
|
|
|
403 |
readSetEnd()
|
|
|
404 |
bool = readBool()
|
|
|
405 |
byte = readByte()
|
|
|
406 |
i16 = readI16()
|
|
|
407 |
i32 = readI32()
|
|
|
408 |
i64 = readI64()
|
|
|
409 |
double = readDouble()
|
|
|
410 |
string = readString()
|
|
|
411 |
\end{verbatim}
|
|
|
412 |
|
|
|
413 |
Note that every \texttt{write} function has exactly one \texttt{read} counterpart, with
|
|
|
414 |
the exception of \texttt{writeFieldStop()}. This is a special method
|
|
|
415 |
that signals the end of a struct. The procedure for reading a struct is to
|
|
|
416 |
\texttt{readFieldBegin()} until the stop field is encountered, and then to
|
|
|
417 |
\texttt{readStructEnd()}. The
|
|
|
418 |
generated code relies upon this call sequence to ensure that everything written by
|
|
|
419 |
a protocol encoder can be read by a matching protocol decoder. Further note
|
|
|
420 |
that this set of functions is by design more robust than necessary.
|
|
|
421 |
For example, \texttt{writeStructEnd()} is not strictly necessary, as the end of
|
|
|
422 |
a struct may be implied by the stop field. This method is a convenience for
|
|
|
423 |
verbose protocols in which it is cleaner to separate these calls (e.g. a closing
|
|
|
424 |
\texttt{</struct>} tag in XML).
|
|
|
425 |
|
|
|
426 |
\subsection{Structure}
|
|
|
427 |
|
|
|
428 |
Thrift structures are designed to support encoding into a streaming
|
|
|
429 |
protocol. The implementation should never need to frame or compute the
|
|
|
430 |
entire data length of a structure prior to encoding it. This is critical to
|
|
|
431 |
performance in many scenarios. Consider a long list of relatively large
|
|
|
432 |
strings. If the protocol interface required reading or writing a list to be an
|
|
|
433 |
atomic operation, then the implementation would need to perform a linear pass over the
|
|
|
434 |
entire list before encoding any data. However, if the list can be written
|
|
|
435 |
as iteration is performed, the corresponding read may begin in parallel,
|
|
|
436 |
theoretically offering an end-to-end speedup of $(kN - C)$, where $N$ is the size
|
|
|
437 |
of the list, $k$ the cost factor associated with serializing a single
|
|
|
438 |
element, and $C$ is fixed offset for the delay between data being written
|
|
|
439 |
and becoming available to read.
|
|
|
440 |
|
|
|
441 |
Similarly, structs do not encode their data lengths a priori. Instead, they are
|
|
|
442 |
encoded as a sequence of fields, with each field having a type specifier and a
|
|
|
443 |
unique field identifier. Note that the inclusion of type specifiers allows
|
|
|
444 |
the protocol to be safely parsed and decoded without any generated code
|
|
|
445 |
or access to the original IDL file. Structs are terminated by a field header
|
|
|
446 |
with a special \texttt{STOP} type. Because all the basic types can be read
|
|
|
447 |
deterministically, all structs (even those containing other structs) can be
|
|
|
448 |
read deterministically. The Thrift protocol is self-delimiting without any
|
|
|
449 |
framing and regardless of the encoding format.
|
|
|
450 |
|
|
|
451 |
In situations where streaming is unnecessary or framing is advantageous, it
|
|
|
452 |
can be very simply added into the transport layer, using the
|
|
|
453 |
\texttt{TFramedTransport} abstraction.
|
|
|
454 |
|
|
|
455 |
\subsection{Implementation}
|
|
|
456 |
|
|
|
457 |
Facebook has implemented and deployed a space-efficient binary protocol which
|
|
|
458 |
is used by most backend services. Essentially, it writes all data
|
|
|
459 |
in a flat binary format. Integer types are converted to network byte order,
|
|
|
460 |
strings are prepended with their byte length, and all message and field headers
|
|
|
461 |
are written using the primitive integer serialization constructs. String names
|
|
|
462 |
for fields are omitted - when using generated code, field identifiers are
|
|
|
463 |
sufficient.
|
|
|
464 |
|
|
|
465 |
We decided against some extreme storage optimizations (i.e. packing
|
|
|
466 |
small integers into ASCII or using a 7-bit continuation format) for the sake
|
|
|
467 |
of simplicity and clarity in the code. These alterations can easily be made
|
|
|
468 |
if and when we encounter a performance-critical use case that demands them.
|
|
|
469 |
|
|
|
470 |
\section{Versioning}
|
|
|
471 |
|
|
|
472 |
Thrift is robust in the face of versioning and data definition changes. This
|
|
|
473 |
is critical to enable staged rollouts of changes to deployed services. The
|
|
|
474 |
system must be able to support reading of old data from log files, as well as
|
|
|
475 |
requests from out-of-date clients to new servers, and vice versa.
|
|
|
476 |
|
|
|
477 |
\subsection{Field Identifiers}
|
|
|
478 |
|
|
|
479 |
Versioning in Thrift is implemented via field identifiers. The field header
|
|
|
480 |
for every member of a struct in Thrift is encoded with a unique field
|
|
|
481 |
identifier. The combination of this field identifier and its type specifier
|
|
|
482 |
is used to uniquely identify the field. The Thrift definition language
|
|
|
483 |
supports automatic assignment of field identifiers, but it is good
|
|
|
484 |
programming practice to always explicitly specify field identifiers.
|
|
|
485 |
Identifiers are specified as follows:
|
|
|
486 |
|
|
|
487 |
\begin{verbatim}
|
|
|
488 |
struct Example {
|
|
|
489 |
1:i32 number=10,
|
|
|
490 |
2:i64 bigNumber,
|
|
|
491 |
3:double decimals,
|
|
|
492 |
4:string name="thrifty"
|
|
|
493 |
}\end{verbatim}
|
|
|
494 |
|
|
|
495 |
To avoid conflicts between manually and automatically assigned identifiers,
|
|
|
496 |
fields with identifiers omitted are assigned identifiers
|
|
|
497 |
decrementing from -1, and the language only supports the manual assignment of
|
|
|
498 |
positive identifiers.
|
|
|
499 |
|
|
|
500 |
When data is being deserialized, the generated code can use these identifiers
|
|
|
501 |
to properly identify the field and determine whether it aligns with a field in
|
|
|
502 |
its definition file. If a field identifier is not recognized, the generated
|
|
|
503 |
code can use the type specifier to skip the unknown field without any error.
|
|
|
504 |
Again, this is possible due to the fact that all datatypes are self
|
|
|
505 |
delimiting.
|
|
|
506 |
|
|
|
507 |
Field identifiers can (and should) also be specified in function argument
|
|
|
508 |
lists. In fact, argument lists are not only represented as structs on the
|
|
|
509 |
backend, but actually share the same code in the compiler frontend. This
|
|
|
510 |
allows for version-safe modification of method parameters
|
|
|
511 |
|
|
|
512 |
\begin{verbatim}
|
|
|
513 |
service StringCache {
|
|
|
514 |
void set(1:i32 key, 2:string value),
|
|
|
515 |
string get(1:i32 key) throws (1:KeyNotFound knf),
|
|
|
516 |
void delete(1:i32 key)
|
|
|
517 |
}
|
|
|
518 |
\end{verbatim}
|
|
|
519 |
|
|
|
520 |
The syntax for specifying field identifiers was chosen to echo their structure.
|
|
|
521 |
Structs can be thought of as a dictionary where the identifiers are keys, and
|
|
|
522 |
the values are strongly-typed named fields.
|
|
|
523 |
|
|
|
524 |
Field identifiers internally use the \texttt{i16} Thrift type. Note, however,
|
|
|
525 |
that the \texttt{TProtocol} abstraction may encode identifiers in any format.
|
|
|
526 |
|
|
|
527 |
\subsection{Isset}
|
|
|
528 |
|
|
|
529 |
When an unexpected field is encountered, it can be safely ignored and
|
|
|
530 |
discarded. When an expected field is not found, there must be some way to
|
|
|
531 |
signal to the developer that it was not present. This is implemented via an
|
|
|
532 |
inner \texttt{isset} structure inside the defined objects. (Isset functionality
|
|
|
533 |
is implicit with a \texttt{null} value in PHP, \texttt{None} in Python
|
|
|
534 |
and \texttt{nil} in Ruby.) Essentially,
|
|
|
535 |
the inner \texttt{isset} object of each Thrift struct contains a boolean value
|
|
|
536 |
for each field which denotes whether or not that field is present in the
|
|
|
537 |
struct. When a reader receives a struct, it should check for a field being set
|
|
|
538 |
before operating directly on it.
|
|
|
539 |
|
|
|
540 |
\begin{verbatim}
|
|
|
541 |
class Example {
|
|
|
542 |
public:
|
|
|
543 |
Example() :
|
|
|
544 |
number(10),
|
|
|
545 |
bigNumber(0),
|
|
|
546 |
decimals(0),
|
|
|
547 |
name("thrifty") {}
|
|
|
548 |
|
|
|
549 |
int32_t number;
|
|
|
550 |
int64_t bigNumber;
|
|
|
551 |
double decimals;
|
|
|
552 |
std::string name;
|
|
|
553 |
|
|
|
554 |
struct __isset {
|
|
|
555 |
__isset() :
|
|
|
556 |
number(false),
|
|
|
557 |
bigNumber(false),
|
|
|
558 |
decimals(false),
|
|
|
559 |
name(false) {}
|
|
|
560 |
bool number;
|
|
|
561 |
bool bigNumber;
|
|
|
562 |
bool decimals;
|
|
|
563 |
bool name;
|
|
|
564 |
} __isset;
|
|
|
565 |
...
|
|
|
566 |
}
|
|
|
567 |
\end{verbatim}
|
|
|
568 |
|
|
|
569 |
\subsection{Case Analysis}
|
|
|
570 |
|
|
|
571 |
There are four cases in which version mismatches may occur.
|
|
|
572 |
|
|
|
573 |
\begin{enumerate}
|
|
|
574 |
\item \textit{Added field, old client, new server.} In this case, the old
|
|
|
575 |
client does not send the new field. The new server recognizes that the field
|
|
|
576 |
is not set, and implements default behavior for out-of-date requests.
|
|
|
577 |
\item \textit{Removed field, old client, new server.} In this case, the old
|
|
|
578 |
client sends the removed field. The new server simply ignores it.
|
|
|
579 |
\item \textit{Added field, new client, old server.} The new client sends a
|
|
|
580 |
field that the old server does not recognize. The old server simply ignores
|
|
|
581 |
it and processes as normal.
|
|
|
582 |
\item \textit{Removed field, new client, old server.} This is the most
|
|
|
583 |
dangerous case, as the old server is unlikely to have suitable default
|
|
|
584 |
behavior implemented for the missing field. It is recommended that in this
|
|
|
585 |
situation the new server be rolled out prior to the new clients.
|
|
|
586 |
\end{enumerate}
|
|
|
587 |
|
|
|
588 |
\subsection{Protocol/Transport Versioning}
|
|
|
589 |
The \texttt{TProtocol} abstractions are also designed to give protocol
|
|
|
590 |
implementations the freedom to version themselves in whatever manner they
|
|
|
591 |
see fit. Specifically, any protocol implementation is free to send whatever
|
|
|
592 |
it likes in the \texttt{writeMessageBegin()} call. It is entirely up to the
|
|
|
593 |
implementor how to handle versioning at the protocol level. The key point is
|
|
|
594 |
that protocol encoding changes are safely isolated from interface definition
|
|
|
595 |
version changes.
|
|
|
596 |
|
|
|
597 |
Note that the exact same is true of the \texttt{TTransport} interface. For
|
|
|
598 |
example, if we wished to add some new checksumming or error detection to the
|
|
|
599 |
\texttt{TFileTransport}, we could simply add a version header into the
|
|
|
600 |
data it writes to the file in such a way that it would still accept old
|
|
|
601 |
log files without the given header.
|
|
|
602 |
|
|
|
603 |
\section{RPC Implementation}
|
|
|
604 |
|
|
|
605 |
\subsection{TProcessor}
|
|
|
606 |
|
|
|
607 |
The last core interface in the Thrift design is the \texttt{TProcessor},
|
|
|
608 |
perhaps the most simple of the constructs. The interface is as follows:
|
|
|
609 |
|
|
|
610 |
\begin{verbatim}
|
|
|
611 |
interface TProcessor {
|
|
|
612 |
bool process(TProtocol in, TProtocol out)
|
|
|
613 |
throws TException
|
|
|
614 |
}
|
|
|
615 |
\end{verbatim}
|
|
|
616 |
|
|
|
617 |
The key design idea here is that the complex systems we build can fundamentally
|
|
|
618 |
be broken down into agents or services that operate on inputs and outputs. In
|
|
|
619 |
most cases, there is actually just one input and output (an RPC client) that
|
|
|
620 |
needs handling.
|
|
|
621 |
|
|
|
622 |
\subsection{Generated Code}
|
|
|
623 |
|
|
|
624 |
When a service is defined, we generate a
|
|
|
625 |
\texttt{TProcessor} instance capable of handling RPC requests to that service,
|
|
|
626 |
using a few helpers. The fundamental structure (illustrated in pseudo-C++) is
|
|
|
627 |
as follows:
|
|
|
628 |
|
|
|
629 |
\begin{verbatim}
|
|
|
630 |
Service.thrift
|
|
|
631 |
=> Service.cpp
|
|
|
632 |
interface ServiceIf
|
|
|
633 |
class ServiceClient : virtual ServiceIf
|
|
|
634 |
TProtocol in
|
|
|
635 |
TProtocol out
|
|
|
636 |
class ServiceProcessor : TProcessor
|
|
|
637 |
ServiceIf handler
|
|
|
638 |
|
|
|
639 |
ServiceHandler.cpp
|
|
|
640 |
class ServiceHandler : virtual ServiceIf
|
|
|
641 |
|
|
|
642 |
TServer.cpp
|
|
|
643 |
TServer(TProcessor processor,
|
|
|
644 |
TServerTransport transport,
|
|
|
645 |
TTransportFactory tfactory,
|
|
|
646 |
TProtocolFactory pfactory)
|
|
|
647 |
serve()
|
|
|
648 |
\end{verbatim}
|
|
|
649 |
|
|
|
650 |
From the Thrift definition file, we generate the virtual service interface.
|
|
|
651 |
A client class is generated, which implements the interface and
|
|
|
652 |
uses two \texttt{TProtocol} instances to perform the I/O operations. The
|
|
|
653 |
generated processor implements the \texttt{TProcessor} interface. The generated
|
|
|
654 |
code has all the logic to handle RPC invocations via the \texttt{process()}
|
|
|
655 |
call, and takes as a parameter an instance of the service interface, as
|
|
|
656 |
implemented by the application developer.
|
|
|
657 |
|
|
|
658 |
The user provides an implementation of the application interface in separate,
|
|
|
659 |
non-generated source code.
|
|
|
660 |
|
|
|
661 |
\subsection{TServer}
|
|
|
662 |
|
|
|
663 |
Finally, the Thrift core libraries provide a \texttt{TServer} abstraction.
|
|
|
664 |
The \texttt{TServer} object generally works as follows.
|
|
|
665 |
|
|
|
666 |
\begin{itemize}
|
|
|
667 |
\item Use the \texttt{TServerTransport} to get a \texttt{TTransport}
|
|
|
668 |
\item Use the \texttt{TTransportFactory} to optionally convert the primitive
|
|
|
669 |
transport into a suitable application transport (typically the
|
|
|
670 |
\texttt{TBufferedTransportFactory} is used here)
|
|
|
671 |
\item Use the \texttt{TProtocolFactory} to create an input and output protocol
|
|
|
672 |
for the \texttt{TTransport}
|
|
|
673 |
\item Invoke the \texttt{process()} method of the \texttt{TProcessor} object
|
|
|
674 |
\end{itemize}
|
|
|
675 |
|
|
|
676 |
The layers are appropriately separated such that the server code needs to know
|
|
|
677 |
nothing about any of the transports, encodings, or applications in play. The
|
|
|
678 |
server encapsulates the logic around connection handling, threading, etc.
|
|
|
679 |
while the processor deals with RPC. The only code written by the application
|
|
|
680 |
developer lives in the definitional Thrift file and the interface
|
|
|
681 |
implementation.
|
|
|
682 |
|
|
|
683 |
Facebook has deployed multiple \texttt{TServer} implementations, including
|
|
|
684 |
the single-threaded \texttt{TSimpleServer}, thread-per-connection
|
|
|
685 |
\texttt{TThreadedServer}, and thread-pooling \texttt{TThreadPoolServer}.
|
|
|
686 |
|
|
|
687 |
The \texttt{TProcessor} interface is very general by design. There is no
|
|
|
688 |
requirement that a \texttt{TServer} take a generated \texttt{TProcessor}
|
|
|
689 |
object. Thrift allows the application developer to easily write any type of
|
|
|
690 |
server that operates on \texttt{TProtocol} objects (for instance, a server
|
|
|
691 |
could simply stream a certain type of object without any actual RPC method
|
|
|
692 |
invocation).
|
|
|
693 |
|
|
|
694 |
\section{Implementation Details}
|
|
|
695 |
\subsection{Target Languages}
|
|
|
696 |
Thrift currently supports five target languages: C++, Java, Python, Ruby, and
|
|
|
697 |
PHP. At Facebook, we have deployed servers predominantly in C++, Java, and
|
|
|
698 |
Python. Thrift services implemented in PHP have also been embedded into the
|
|
|
699 |
Apache web server, providing transparent backend access to many of our
|
|
|
700 |
frontend constructs using a \texttt{THttpClient} implementation of the
|
|
|
701 |
\texttt{TTransport} interface.
|
|
|
702 |
|
|
|
703 |
Though Thrift was explicitly designed to be much more efficient and robust
|
|
|
704 |
than typical web technologies, as we were designing our XML-based REST web
|
|
|
705 |
services API we noticed that Thrift could be easily used to define our
|
|
|
706 |
service interface. Though we do not currently employ SOAP envelopes (in the
|
|
|
707 |
authors' opinions there is already far too much repetitive enterprise Java
|
|
|
708 |
software to do that sort of thing), we were able to quickly extend Thrift to
|
|
|
709 |
generate XML Schema Definition files for our service, as well as a framework
|
|
|
710 |
for versioning different implementations of our web service. Though public
|
|
|
711 |
web services are admittedly tangential to Thrift's core use case and design,
|
|
|
712 |
Thrift facilitated rapid iteration and affords us the ability to quickly
|
|
|
713 |
migrate our entire XML-based web service onto a higher performance system
|
|
|
714 |
should the need arise.
|
|
|
715 |
|
|
|
716 |
\subsection{Generated Structs}
|
|
|
717 |
We made a conscious decision to make our generated structs as transparent as
|
|
|
718 |
possible. All fields are publicly accessible; there are no \texttt{set()} and
|
|
|
719 |
\texttt{get()} methods. Similarly, use of the \texttt{isset} object is not
|
|
|
720 |
enforced. We do not include any \texttt{FieldNotSetException} construct.
|
|
|
721 |
Developers have the option to use these fields to write more robust code, but
|
|
|
722 |
the system is robust to the developer ignoring the \texttt{isset} construct
|
|
|
723 |
entirely and will provide suitable default behavior in all cases.
|
|
|
724 |
|
|
|
725 |
This choice was motivated by the desire to ease application development. Our stated
|
|
|
726 |
goal is not to make developers learn a rich new library in their language of
|
|
|
727 |
choice, but rather to generate code that allow them to work with the constructs
|
|
|
728 |
that are most familiar in each language.
|
|
|
729 |
|
|
|
730 |
We also made the \texttt{read()} and \texttt{write()} methods of the generated
|
|
|
731 |
objects public so that the objects can be used outside of the context
|
|
|
732 |
of RPC clients and servers. Thrift is a useful tool simply for generating
|
|
|
733 |
objects that are easily serializable across programming languages.
|
|
|
734 |
|
|
|
735 |
\subsection{RPC Method Identification}
|
|
|
736 |
Method calls in RPC are implemented by sending the method name as a string. One
|
|
|
737 |
issue with this approach is that longer method names require more bandwidth.
|
|
|
738 |
We experimented with using fixed-size hashes to identify methods, but in the
|
|
|
739 |
end concluded that the savings were not worth the headaches incurred. Reliably
|
|
|
740 |
dealing with conflicts across versions of an interface definition file is
|
|
|
741 |
impossible without a meta-storage system (i.e. to generate non-conflicting
|
|
|
742 |
hashes for the current version of a file, we would have to know about all
|
|
|
743 |
conflicts that ever existed in any previous version of the file).
|
|
|
744 |
|
|
|
745 |
We wanted to avoid too many unnecessary string comparisons upon
|
|
|
746 |
method invocation. To deal with this, we generate maps from strings to function
|
|
|
747 |
pointers, so that invocation is effectively accomplished via a constant-time
|
|
|
748 |
hash lookup in the common case. This requires the use of a couple interesting
|
|
|
749 |
code constructs. Because Java does not have function pointers, process
|
|
|
750 |
functions are all private member classes implementing a common interface.
|
|
|
751 |
|
|
|
752 |
\begin{verbatim}
|
|
|
753 |
private class ping implements ProcessFunction {
|
|
|
754 |
public void process(int seqid,
|
|
|
755 |
TProtocol iprot,
|
|
|
756 |
TProtocol oprot)
|
|
|
757 |
throws TException
|
|
|
758 |
{ ...}
|
|
|
759 |
}
|
|
|
760 |
|
|
|
761 |
HashMap<String,ProcessFunction> processMap_ =
|
|
|
762 |
new HashMap<String,ProcessFunction>();
|
|
|
763 |
\end{verbatim}
|
|
|
764 |
|
|
|
765 |
In C++, we use a relatively esoteric language construct: member function
|
|
|
766 |
pointers.
|
|
|
767 |
|
|
|
768 |
\begin{verbatim}
|
|
|
769 |
std::map<std::string,
|
|
|
770 |
void (ExampleServiceProcessor::*)(int32_t,
|
|
|
771 |
facebook::thrift::protocol::TProtocol*,
|
|
|
772 |
facebook::thrift::protocol::TProtocol*)>
|
|
|
773 |
processMap_;
|
|
|
774 |
\end{verbatim}
|
|
|
775 |
|
|
|
776 |
Using these techniques, the cost of string processing is minimized, and we
|
|
|
777 |
reap the benefit of being able to easily debug corrupt or misunderstood data by
|
|
|
778 |
inspecting it for known string method names.
|
|
|
779 |
|
|
|
780 |
\subsection{Servers and Multithreading}
|
|
|
781 |
Thrift services require basic multithreading to handle simultaneous
|
|
|
782 |
requests from multiple clients. For the Python and Java implementations of
|
|
|
783 |
Thrift server logic, the standard threading libraries distributed with the
|
|
|
784 |
languages provide adequate support. For the C++ implementation, no standard multithread runtime
|
|
|
785 |
library exists. Specifically, robust, lightweight, and portable
|
|
|
786 |
thread manager and timer class implementations do not exist. We investigated
|
|
|
787 |
existing implementations, namely \texttt{boost::thread},
|
|
|
788 |
\texttt{boost::threadpool}, \texttt{ACE\_Thread\_Manager} and
|
|
|
789 |
\texttt{ACE\_Timer}.
|
|
|
790 |
|
|
|
791 |
While \texttt{boost::threads}\cite{boost.threads} provides clean,
|
|
|
792 |
lightweight and robust implementations of multi-thread primitives (mutexes,
|
|
|
793 |
conditions, threads) it does not provide a thread manager or timer
|
|
|
794 |
implementation.
|
|
|
795 |
|
|
|
796 |
\texttt{boost::threadpool}\cite{boost.threadpool} also looked promising but
|
|
|
797 |
was not far enough along for our purposes. We wanted to limit the dependency on
|
|
|
798 |
third-party libraries as much as possible. Because\\
|
|
|
799 |
\texttt{boost::threadpool} is
|
|
|
800 |
not a pure template library and requires runtime libraries and because it is
|
|
|
801 |
not yet part of the official Boost distribution we felt it was not ready for
|
|
|
802 |
use in Thrift. As \texttt{boost::threadpool} evolves and especially if it is
|
|
|
803 |
added to the Boost distribution we may reconsider our decision to not use it.
|
|
|
804 |
|
|
|
805 |
ACE has both a thread manager and timer class in addition to multi-thread
|
|
|
806 |
primitives. The biggest problem with ACE is that it is ACE. Unlike Boost, ACE
|
|
|
807 |
API quality is poor. Everything in ACE has large numbers of dependencies on
|
|
|
808 |
everything else in ACE - thus forcing developers to throw out standard
|
|
|
809 |
classes, such as STL collections, in favor of ACE's homebrewed implementations. In
|
|
|
810 |
addition, unlike Boost, ACE implementations demonstrate little understanding
|
|
|
811 |
of the power and pitfalls of C++ programming and take no advantage of modern
|
|
|
812 |
templating techniques to ensure compile time safety and reasonable compiler
|
|
|
813 |
error messages. For all these reasons, ACE was rejected. Instead, we chose
|
|
|
814 |
to implement our own library, described in the following sections.
|
|
|
815 |
|
|
|
816 |
\subsection{Thread Primitives}
|
|
|
817 |
|
|
|
818 |
The Thrift thread libraries are implemented in the namespace\\
|
|
|
819 |
\texttt{facebook::thrift::concurrency} and have three components:
|
|
|
820 |
\begin{itemize}
|
|
|
821 |
\item primitives
|
|
|
822 |
\item thread pool manager
|
|
|
823 |
\item timer manager
|
|
|
824 |
\end{itemize}
|
|
|
825 |
|
|
|
826 |
As mentioned above, we were hesitant to introduce any additional dependencies
|
|
|
827 |
on Thrift. We decided to use \texttt{boost::shared\_ptr} because it is so
|
|
|
828 |
useful for multithreaded application, it requires no link-time or
|
|
|
829 |
runtime libraries (i.e. it is a pure template library) and it is due
|
|
|
830 |
to become part of the C++0x standard.
|
|
|
831 |
|
|
|
832 |
We implement standard \texttt{Mutex} and \texttt{Condition} classes, and a
|
|
|
833 |
\texttt{Monitor} class. The latter is simply a combination of a mutex and
|
|
|
834 |
condition variable and is analogous to the \texttt{Monitor} implementation provided for
|
|
|
835 |
the Java \texttt{Object} class. This is also sometimes referred to as a barrier. We
|
|
|
836 |
provide a \texttt{Synchronized} guard class to allow Java-like synchronized blocks.
|
|
|
837 |
This is just a bit of syntactic sugar, but, like its Java counterpart, clearly
|
|
|
838 |
delimits critical sections of code. Unlike its Java counterpart, we still
|
|
|
839 |
have the ability to programmatically lock, unlock, block, and signal monitors.
|
|
|
840 |
|
|
|
841 |
\begin{verbatim}
|
|
|
842 |
void run() {
|
|
|
843 |
{Synchronized s(manager->monitor);
|
|
|
844 |
if (manager->state == TimerManager::STARTING) {
|
|
|
845 |
manager->state = TimerManager::STARTED;
|
|
|
846 |
manager->monitor.notifyAll();
|
|
|
847 |
}
|
|
|
848 |
}
|
|
|
849 |
}
|
|
|
850 |
\end{verbatim}
|
|
|
851 |
|
|
|
852 |
We again borrowed from Java the distinction between a thread and a runnable
|
|
|
853 |
class. A \texttt{Thread} is the actual schedulable object. The
|
|
|
854 |
\texttt{Runnable} is the logic to execute within the thread.
|
|
|
855 |
The \texttt{Thread} implementation deals with all the platform-specific thread
|
|
|
856 |
creation and destruction issues, while the \texttt{Runnable} implementation deals
|
|
|
857 |
with the application-specific per-thread logic. The benefit of this approach
|
|
|
858 |
is that developers can easily subclass the Runnable class without pulling in
|
|
|
859 |
platform-specific super-classes.
|
|
|
860 |
|
|
|
861 |
\subsection{Thread, Runnable, and shared\_ptr}
|
|
|
862 |
We use \texttt{boost::shared\_ptr} throughout the \texttt{ThreadManager} and
|
|
|
863 |
\texttt{TimerManager} implementations to guarantee cleanup of dead objects that can
|
|
|
864 |
be accessed by multiple threads. For \texttt{Thread} class implementations,
|
|
|
865 |
\texttt{boost::shared\_ptr} usage requires particular attention to make sure
|
|
|
866 |
\texttt{Thread} objects are neither leaked nor dereferenced prematurely while
|
|
|
867 |
creating and shutting down threads.
|
|
|
868 |
|
|
|
869 |
Thread creation requires calling into a C library. (In our case the POSIX
|
|
|
870 |
thread library, \texttt{libpthread}, but the same would be true for WIN32 threads).
|
|
|
871 |
Typically, the OS makes few, if any, guarantees about when \texttt{ThreadMain}, a C thread's entry-point function, will be called. Therefore, it is
|
|
|
872 |
possible that our thread create call,
|
|
|
873 |
\texttt{ThreadFactory::newThread()} could return to the caller
|
|
|
874 |
well before that time. To ensure that the returned \texttt{Thread} object is not
|
|
|
875 |
prematurely cleaned up if the caller gives up its reference prior to the
|
|
|
876 |
\texttt{ThreadMain} call, the \texttt{Thread} object makes a weak referenence to
|
|
|
877 |
itself in its \texttt{start} method.
|
|
|
878 |
|
|
|
879 |
With the weak reference in hand the \texttt{ThreadMain} function can attempt to get
|
|
|
880 |
a strong reference before entering the \texttt{Runnable::run} method of the
|
|
|
881 |
\texttt{Runnable} object bound to the \texttt{Thread}. If no strong references to the
|
|
|
882 |
thread are obtained between exiting \texttt{Thread::start} and entering \texttt{ThreadMain}, the weak reference returns \texttt{null} and the function
|
|
|
883 |
exits immediately.
|
|
|
884 |
|
|
|
885 |
The need for the \texttt{Thread} to make a weak reference to itself has a
|
|
|
886 |
significant impact on the API. Since references are managed through the
|
|
|
887 |
\texttt{boost::shared\_ptr} templates, the \texttt{Thread} object must have a reference
|
|
|
888 |
to itself wrapped by the same \texttt{boost::shared\_ptr} envelope that is returned
|
|
|
889 |
to the caller. This necessitated the use of the factory pattern.
|
|
|
890 |
\texttt{ThreadFactory} creates the raw \texttt{Thread} object and a
|
|
|
891 |
\texttt{boost::shared\_ptr} wrapper, and calls a private helper method of the class
|
|
|
892 |
implementing the \texttt{Thread} interface (in this case, \texttt{PosixThread::weakRef})
|
|
|
893 |
to allow it to make add weak reference to itself through the
|
|
|
894 |
\texttt{boost::shared\_ptr} envelope.
|
|
|
895 |
|
|
|
896 |
\texttt{Thread} and \texttt{Runnable} objects reference each other. A \texttt{Runnable}
|
|
|
897 |
object may need to know about the thread in which it is executing, and a Thread, obviously,
|
|
|
898 |
needs to know what \texttt{Runnable} object it is hosting. This interdependency is
|
|
|
899 |
further complicated because the lifecycle of each object is independent of the
|
|
|
900 |
other. An application may create a set of \texttt{Runnable} object to be reused in different threads, or it may create and forget a \texttt{Runnable} object
|
|
|
901 |
once a thread has been created and started for it.
|
|
|
902 |
|
|
|
903 |
The \texttt{Thread} class takes a \texttt{boost::shared\_ptr} reference to the hosted
|
|
|
904 |
\texttt{Runnable} object in its constructor, while the \texttt{Runnable} class has an
|
|
|
905 |
explicit \texttt{thread} method to allow explicit binding of the hosted thread.
|
|
|
906 |
\texttt{ThreadFactory::newThread} binds the objects to each other.
|
|
|
907 |
|
|
|
908 |
\subsection{ThreadManager}
|
|
|
909 |
|
|
|
910 |
\texttt{ThreadManager} creates a pool of worker threads and
|
|
|
911 |
allows applications to schedule tasks for execution as free worker threads
|
|
|
912 |
become available. The \texttt{ThreadManager} does not implement dynamic
|
|
|
913 |
thread pool resizing, but provides primitives so that applications can add
|
|
|
914 |
and remove threads based on load. This approach was chosen because
|
|
|
915 |
implementing load metrics and thread pool size is very application
|
|
|
916 |
specific. For example some applications may want to adjust pool size based
|
|
|
917 |
on running-average of work arrival rates that are measured via polled
|
|
|
918 |
samples. Others may simply wish to react immediately to work-queue
|
|
|
919 |
depth high and low water marks. Rather than trying to create a complex
|
|
|
920 |
API abstract enough to capture these different approaches, we
|
|
|
921 |
simply leave it up to the particular application and provide the
|
|
|
922 |
primitives to enact the desired policy and sample current status.
|
|
|
923 |
|
|
|
924 |
\subsection{TimerManager}
|
|
|
925 |
|
|
|
926 |
\texttt{TimerManager} allows applications to schedule
|
|
|
927 |
\texttt{Runnable} objects for execution at some point in the future. Its specific task
|
|
|
928 |
is to allows applications to sample \texttt{ThreadManager} load at regular
|
|
|
929 |
intervals and make changes to the thread pool size based on application policy.
|
|
|
930 |
Of course, it can be used to generate any number of timer or alarm events.
|
|
|
931 |
|
|
|
932 |
The default implementation of \texttt{TimerManager} uses a single thread to
|
|
|
933 |
execute expired \texttt{Runnable} objects. Thus, if a timer operation needs to
|
|
|
934 |
do a large amount of work and especially if it needs to do blocking I/O,
|
|
|
935 |
that should be done in a separate thread.
|
|
|
936 |
|
|
|
937 |
\subsection{Nonblocking Operation}
|
|
|
938 |
Though the Thrift transport interfaces map more directly to a blocking I/O
|
|
|
939 |
model, we have implemented a high performance \texttt{TNonBlockingServer}
|
|
|
940 |
in C++ based on \texttt{libevent} and the \texttt{TFramedTransport}. We
|
|
|
941 |
implemented this by moving all I/O into one tight event loop using a
|
|
|
942 |
state machine. Essentially, the event loop reads framed requests into
|
|
|
943 |
\texttt{TMemoryBuffer} objects. Once entire requests are ready, they are
|
|
|
944 |
dispatched to the \texttt{TProcessor} object which can read directly from
|
|
|
945 |
the data in memory.
|
|
|
946 |
|
|
|
947 |
\subsection{Compiler}
|
|
|
948 |
The Thrift compiler is implemented in C++ using standard \texttt{lex}/\texttt{yacc}
|
|
|
949 |
lexing and parsing. Though it could have been implemented with fewer
|
|
|
950 |
lines of code in another language (i.e. Python Lex-Yacc (PLY) or \texttt{ocamlyacc}), using C++
|
|
|
951 |
forces explicit definition of the language constructs. Strongly typing the
|
|
|
952 |
parse tree elements (debatably) makes the code more approachable for new
|
|
|
953 |
developers.
|
|
|
954 |
|
|
|
955 |
Code generation is done using two passes. The first pass looks only for
|
|
|
956 |
include files and type definitions. Type definitions are not checked during
|
|
|
957 |
this phase, since they may depend upon include files. All included files
|
|
|
958 |
are sequentially scanned in a first pass. Once the include tree has been
|
|
|
959 |
resolved, a second pass over all files is taken that inserts type definitions
|
|
|
960 |
into the parse tree and raises an error on any undefined types. The program is
|
|
|
961 |
then generated against the parse tree.
|
|
|
962 |
|
|
|
963 |
Due to inherent complexities and potential for circular dependencies,
|
|
|
964 |
we explicitly disallow forward declaration. Two Thrift structs cannot
|
|
|
965 |
each contain an instance of the other. (Since we do not allow \texttt{null}
|
|
|
966 |
struct instances in the generated C++ code, this would actually be impossible.)
|
|
|
967 |
|
|
|
968 |
\subsection{TFileTransport}
|
|
|
969 |
The \texttt{TFileTransport} logs Thrift requests/structs by
|
|
|
970 |
framing incoming data with its length and writing it out to disk.
|
|
|
971 |
Using a framed on-disk format allows for better error checking and
|
|
|
972 |
helps with the processing of a finite number of discrete events. The\\
|
|
|
973 |
\texttt{TFileWriterTransport} uses a system of swapping in-memory buffers
|
|
|
974 |
to ensure good performance while logging large amounts of data.
|
|
|
975 |
A Thrift log file is split up into chunks of a specified size; logged messages
|
|
|
976 |
are not allowed to cross chunk boundaries. A message that would cross a chunk
|
|
|
977 |
boundary will cause padding to be added until the end of the chunk and the
|
|
|
978 |
first byte of the message are aligned to the beginning of the next chunk.
|
|
|
979 |
Partitioning the file into chunks makes it possible to read and interpret data
|
|
|
980 |
from a particular point in the file.
|
|
|
981 |
|
|
|
982 |
\section{Facebook Thrift Services}
|
|
|
983 |
Thrift has been employed in a large number of applications at Facebook, including
|
|
|
984 |
search, logging, mobile, ads and the developer platform. Two specific usages are discussed below.
|
|
|
985 |
|
|
|
986 |
\subsection{Search}
|
|
|
987 |
Thrift is used as the underlying protocol and transport layer for the Facebook Search service.
|
|
|
988 |
The multi-language code generation is well suited for search because it allows for application
|
|
|
989 |
development in an efficient server side language (C++) and allows the Facebook PHP-based web application
|
|
|
990 |
to make calls to the search service using Thrift PHP libraries. There is also a large
|
|
|
991 |
variety of search stats, deployment and testing functionality that is built on top
|
|
|
992 |
of generated Python code. Additionally, the Thrift log file format is
|
|
|
993 |
used as a redo log for providing real-time search index updates. Thrift has allowed the
|
|
|
994 |
search team to leverage each language for its strengths and to develop code at a rapid pace.
|
|
|
995 |
|
|
|
996 |
\subsection{Logging}
|
|
|
997 |
The Thrift \texttt{TFileTransport} functionality is used for structured logging. Each
|
|
|
998 |
service function definition along with its parameters can be considered to be
|
|
|
999 |
a structured log entry identified by the function name. This log can then be used for
|
|
|
1000 |
a variety of purposes, including inline and offline processing, stats aggregation and as a redo log.
|
|
|
1001 |
|
|
|
1002 |
\section{Conclusions}
|
|
|
1003 |
Thrift has enabled Facebook to build scalable backend
|
|
|
1004 |
services efficiently by enabling engineers to divide and conquer. Application
|
|
|
1005 |
developers can focus on application code without worrying about the
|
|
|
1006 |
sockets layer. We avoid duplicated work by writing buffering and I/O logic
|
|
|
1007 |
in one place, rather than interspersing it in each application.
|
|
|
1008 |
|
|
|
1009 |
Thrift has been employed in a wide variety of applications at Facebook,
|
|
|
1010 |
including search, logging, mobile, ads, and the developer platform. We have
|
|
|
1011 |
found that the marginal performance cost incurred by an extra layer of
|
|
|
1012 |
software abstraction is far eclipsed by the gains in developer efficiency and
|
|
|
1013 |
systems reliability.
|
|
|
1014 |
|
|
|
1015 |
\appendix
|
|
|
1016 |
|
|
|
1017 |
\section{Similar Systems}
|
|
|
1018 |
The following are software systems similar to Thrift. Each is (very!) briefly
|
|
|
1019 |
described:
|
|
|
1020 |
|
|
|
1021 |
\begin{itemize}
|
|
|
1022 |
\item \textit{SOAP.} XML-based. Designed for web services via HTTP, excessive
|
|
|
1023 |
XML parsing overhead.
|
|
|
1024 |
\item \textit{CORBA.} Relatively comprehensive, debatably overdesigned and
|
|
|
1025 |
heavyweight. Comparably cumbersome software installation.
|
|
|
1026 |
\item \textit{COM.} Embraced mainly in Windows client softare. Not an entirely
|
|
|
1027 |
open solution.
|
|
|
1028 |
\item \textit{Pillar.} Lightweight and high-performance, but missing versioning
|
|
|
1029 |
and abstraction.
|
|
|
1030 |
\item \textit{Protocol Buffers.} Closed-source, owned by Google. Described in
|
|
|
1031 |
Sawzall paper.
|
|
|
1032 |
\end{itemize}
|
|
|
1033 |
|
|
|
1034 |
\acks
|
|
|
1035 |
|
|
|
1036 |
Many thanks for feedback on Thrift (and extreme trial by fire) are due to
|
|
|
1037 |
Martin Smith, Karl Voskuil and Yishan Wong.
|
|
|
1038 |
|
|
|
1039 |
Thrift is a successor to Pillar, a similar system developed
|
|
|
1040 |
by Adam D'Angelo, first while at Caltech and continued later at Facebook.
|
|
|
1041 |
Thrift simply would not have happened without Adam's insights.
|
|
|
1042 |
|
|
|
1043 |
\begin{thebibliography}{}
|
|
|
1044 |
|
|
|
1045 |
\bibitem{boost.threads}
|
|
|
1046 |
Kempf, William,
|
|
|
1047 |
``Boost.Threads'',
|
|
|
1048 |
\url{http://www.boost.org/doc/html/threads.html}
|
|
|
1049 |
|
|
|
1050 |
\bibitem{boost.threadpool}
|
|
|
1051 |
Henkel, Philipp,
|
|
|
1052 |
``threadpool'',
|
|
|
1053 |
\url{http://threadpool.sourceforge.net}
|
|
|
1054 |
|
|
|
1055 |
\end{thebibliography}
|
|
|
1056 |
|
|
|
1057 |
\end{document}
|