Here is the paper.
My goal of learning is to understand TAO data store, and also review TCP/UDP networking. I like to prepare system design, and it is so important for me to read one or two papers, understand one system design like graph database - TAO.
I need to start from somewhere as a researcher, system design is not too tough for me to build strong interest on. I should not treat it as a stepping stone for me to get a job from Facebook. I should think it as my career, and I should prepare myself to be a better researcher, and also engineer.
Compared to crafting skills, system design is also very challenge and interesting project to work on.
We structure our paper to emphasize the themes that
emerge at three different deployment scales. Our read heavy workload and wide fan-out is the primary concern when we have one cluster of servers. As it becomes
necessary to scale to multiple front-end clusters, we address data replication between these clusters. Finally, we
describe mechanisms to provide a consistent user experience as we spread clusters around the world. Operational complexity and fault tolerance is important at
all scales. We present salient data that supports our design decisions and refer the reader to work by Atikoglu
et al. [8] for a more detailed analysis of our workload. At
a high-level, Figure 2 illustrates this final architecture in
which we organize co-located clusters into a region and
designate a master region that provides a data stream to
keep non-master regions up-to-date.
...
3.1 Reducing Latency
We provision hundreds of memcached servers in a
cluster to reduce load on databases and other services.
Items are distributed across the memcached servers
through consistent hashing [22]. Thus web servers have
to routinely communicate with many memcached servers
to satisfy a user request. As a result, all web servers
communicate with every memcached server in a short
period of time. This all-to-all communication pattern
can cause incast congestion [30] or allow a single server
to become the bottleneck for many web servers. Data
replication often alleviates the single-server bottleneck
but leads to significant memory inefficiencies in the
common case.
We reduce latency mainly by focusing on the
memcache client, which runs on each web server. This
client serves a range of functions, including serialization, compression, request routing, error handling, and
request batching. Clients maintain a map of all available
servers, which is updated through an auxiliary configuration system.
Parallel requests and batching:
We structure our web application code to minimize the number of network
round trips necessary to respond to page requests. We
construct a directed acyclic graph (DAG) representing
the dependencies between data. A web server uses this
DAG to maximize the number of items that can be
fetched concurrently. On average these batches consist
of 24 keys per request2.
Client-server communication:
Memcached servers do
not communicate with each other. When appropriate,
we embed the complexity of the system into a stateless
client rather than in the memcached servers. This greatly
simplifies memcached and allows us to focus on making
it highly performant for a more limited use case. Keeping the clients stateless enables rapid iteration in the
software and simplifies our deployment process. Client
logic is provided as two components: a library that can
be embedded into applications or as a standalone proxy
named mcrouter. This proxy presents a memcached
server interface and routes the requests/replies to/from
other servers.
a standalone proxy named mcrouter - mcrouter - what is the proxy name?
Clients use UDP and TCP to communicate with
memcached servers. We rely on UDP for get requests to
reduce latency and overhead. Since UDP is connectionless, each thread in the web server is allowed to directly
communicate with memcached servers directly, bypassing mcrouter, without establishing and maintaining a connection thereby reducing the overhead. The UDP
implementation detects packets that are dropped or received out of order (using sequence numbers) and treats
them as errors on the client side. It does not provide
any mechanism to try to recover from them. In our infrastructure, we find this decision to be practical. Under peak load, memcache clients observe that 0.25% of
get requests are discarded. About 80% of these drops
are due to late or dropped packets, while the remainder
are due to out of order delivery. Clients treat get errors as cache misses, but web servers will skip inserting entries into memcached after querying for data to
avoid putting additional load on a possibly overloaded
network or server.
For reliability, clients perform set and delete operations over TCP through an instance of mcrouter running on the same machine as the web server. For operations where we need to confirm a state change (updates
and deletes) TCP alleviates the need to add a retry mechanism to our UDP implementation.
Web servers rely on a high degree of parallelism and
over-subscription to achieve high throughput. The high
memory demands of open TCP connections makes it
prohibitively expensive to have an open connection between every web thread and memcached server without
some form of connection coalescing via mcrouter. Coalescing these connections improves the efficiency of
the server by reducing the network, CPU and memory
resources needed by high throughput TCP connections.
Figure 3 shows the average, median, and 95th percentile
latencies of web servers in production getting keys over
UDP and through mcrouter via TCP. In all cases, the
standard deviation from these averages was less than
1%. As the data show, relying on UDP can lead to a
20% reduction in latency to serve requests.
For reliability, clients perform set and delete operations over TCP through an instance of mcrouter running on the same machine as the web server. For operations where we need to confirm a state change (updates
and deletes) TCP alleviates the need to add a retry mechanism to our UDP implementation.
Web servers rely on a high degree of parallelism and
over-subscription to achieve high throughput. The high
memory demands of open TCP connections makes it
prohibitively expensive to have an open connection between every web thread and memcached server without
some form of connection coalescing via mcrouter. Coalescing these connections improves the efficiency of
the server by reducing the network, CPU and memory
resources needed by high throughput TCP connections.
Figure 3 shows the average, median, and 95th percentile
latencies of web servers in production getting keys over
UDP and through mcrouter via TCP. In all cases, the
standard deviation from these averages was less than
1%. As the data show, relying on UDP can lead to a
20% reduction in latency to serve requests.
Incast congestion: Memcache clients implement flow control mechanisms to limit incast congestion. When a client requests a large number of keys, the responses
can overwhelm components such as rack and cluster
switches if those responses arrive all at once. Clients
therefore use a sliding window mechanism [11] to control the number of outstanding requests. When the client
receives a response, the next request can be sent. Similar
to TCP’s congestion control, the size of this sliding window grows slowly upon a successful request and shrinks
when a request goes unanswered. The window applies
to all memcache requests independently of destination;
whereas TCP windows apply only to a single stream.
...
3.2.3 Replication Within Pools
Within some pools, we use replication to improve the latency and efficiency of memcached servers. We choose
to replicate a category of keys within a pool when (1)
the application routinely fetches many keys simultaneously, (2) the entire data set fits in one or two memcached
servers and (3) the request rate is much higher than what
a single server can manage.
We favor replication in this instance over further dividing the key space. Consider a memcached server
holding 100 items and capable of responding to 500k
requests per second. Each request asks for 100 keys.
The difference in memcached overhead for retrieving
100 keys per request instead of 1 key is small. To scale
the system to process 1M requests/sec, suppose that we
add a second server and split the key space equally between the two. Clients now need to split each request for
100 keys into two parallel requests for ∼50 keys. Consequently, both servers still have to process 1M requests
per second. However, if we replicate all 100 keys to multiple servers, a client’s request for 100 keys can be sent
to any replica. This reduces the load per server to 500k
requests per second. Each client chooses replicas based
on its own IP address. This approach requires delivering
invalidations to all replicas to maintain consistency.