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