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