From fcbb347d29658962cf1934690960485a56431465 Mon Sep 17 00:00:00 2001 From: Sally McGrath Date: Sun, 8 Jan 2023 19:58:40 +0000 Subject: [PATCH] doing copy edits in content folder then will move up this is just so I can see what I am doing --- website/content/primers/README.md | 10 + .../1-reliable-rpcs/index.md | 137 ++++++++ .../1-reliable-rpcs/rpcs.webp | Bin 0 -> 36160 bytes .../2-state/index.md | 294 +++++++++++++++++ .../2-state/leader-follower-diagram.png | Bin 0 -> 54777 bytes .../3-scaling-stateless-services/index.md | 112 +++++++ .../index.md | 45 +++ .../index.md | 63 ++++ .../README.md | 0 .../content/primers/troubleshooting/README.md | 18 + .../content/primers/troubleshooting/primer.md | 308 ++++++++++++++++++ 11 files changed, 987 insertions(+) create mode 100644 website/content/primers/README.md create mode 100644 website/content/primers/distributed-software-systems-architecture/1-reliable-rpcs/index.md create mode 100644 website/content/primers/distributed-software-systems-architecture/1-reliable-rpcs/rpcs.webp create mode 100644 website/content/primers/distributed-software-systems-architecture/2-state/index.md create mode 100644 website/content/primers/distributed-software-systems-architecture/2-state/leader-follower-diagram.png create mode 100644 website/content/primers/distributed-software-systems-architecture/3-scaling-stateless-services/index.md create mode 100644 website/content/primers/distributed-software-systems-architecture/4-asynchronous-work-and-pipelines/index.md create mode 100644 website/content/primers/distributed-software-systems-architecture/5-distributed-locking-and-distributed-consensus/index.md rename primers/distributed-software-systems-architecture/_index.md => website/content/primers/distributed-software-systems-architecture/README.md (100%) create mode 100644 website/content/primers/troubleshooting/README.md create mode 100644 website/content/primers/troubleshooting/primer.md diff --git a/website/content/primers/README.md b/website/content/primers/README.md new file mode 100644 index 000000000..f7e068e00 --- /dev/null +++ b/website/content/primers/README.md @@ -0,0 +1,10 @@ ++++ +title="Primers" +date="28 Dec 2022 12:22:11 BST" ++++ + +## Reading, Building, and Working + +Our programme has three strands of work in every sprint. We give our fellows plenty of documentation, books, and papers to read and discuss during office hours. Mentors have contributed these primers to support the study strand. + +There are also links to related projects to explore the ideas presented here. diff --git a/website/content/primers/distributed-software-systems-architecture/1-reliable-rpcs/index.md b/website/content/primers/distributed-software-systems-architecture/1-reliable-rpcs/index.md new file mode 100644 index 000000000..1664355d5 --- /dev/null +++ b/website/content/primers/distributed-software-systems-architecture/1-reliable-rpcs/index.md @@ -0,0 +1,137 @@ ++++ +title="1. Reliable RPCs" ++++ + +# 1 + +## Reliable RPCs {#section-1-reliable-rpcs} + +### About Remote Procedure Calls (RPCs) {#about-remote-procedure-calls-rpcs} + +The Remote Procedure Call is the fundamental building block of distributed systems: they are how you can run a procedure - i.e. a piece of code - on another machine. Without RPCs we cannot build an efficient distributed system: they are essential to the existence of GMail, Netflix, Spotify, Facebook, and all other large-scale distributed software systems. Keeping RPCs flowing reliably is a significant part of most infrastructure engineering jobs. + +RPCs aren’t the same as [HTTP requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Overview). HTTP is a text-based protocol, oriented towards resources or entities. RPCs are oriented towards calling a specific function. RPCs are highly structured; normally the request and response are encoded in efficient binary formats which are not human-readable. However these structured formats can be used to transmit data between computers using much less bandwidth than JSON or other text formats. Structured RPCs are also more efficient to parse than text, meaning that CPU usage will be lower. It depends on the logic of the specific application, but parsing of requests and encoding of responses often uses a very significant percentage of the computing resources needed by a web service. + +HTTP APIs are usually limited to CRUD (Create, Read, Update, Delete) operations; RPCs can perform any kind of operation. If you want to efficiently integrate two systems that you control, RPCs are a good choice. If you want to provide an API for use by developers outside of your organisation then HTTP APIs are generally a better choice, because they are simpler to develop against and all programming languages provide good HTTP support. + +![alt_text](./rpcs.webp "diagram shows caller and callee machine exchange from linked paper") + +This diagram is from [Nelson and Birell’s classic paper](http://birrell.org/andrew/papers/ImplementingRPC.pdf) on implementing RPCs. Both client and server use _stubs_ to define the structure of the request and the response. + +There is also an RPCRuntime library, which takes care of the mechanics of locating the server machine, transmitting the request, and waiting for the response. + +Today, two of the most popular RPC frameworks are [Thrift](https://thrift.apache.org/) and gRPC. Thrift was developed at Meta (Facebook) and is popular in the Java ecosystem. gRPC was developed at Google and supports a variety of languages, but is most associated with the Go programming language. + +The [gRPC quickstart guide for Go](https://grpc.io/docs/languages/go/quickstart/) explains how to define a simple gRPC service definition (this is the API that your program serves), compile it - which generates the user stubs and service stubs shown in the diagram above - and use it in your program. + +### RPCs: What Could Go Wrong? {#rpcs-what-could-go-wrong} + +The idea of RPCs is not complicated. The real challenge of distributed systems is dealing with the problems that unreliable networks create. Since the 1990s programmers have talked about the [Eight Fallacies of Distributed Computing](https://web.archive.org/web/20171107014323/http://blog.fogcreek.com/eight-fallacies-of-distributed-computing-tech-talk/) - in other words, things that most programmers wrongly assume are true. + +## The most important fallacies are: + +> 1. [The network is reliable](#fallacy-1-the-network-is-reliable) +> 2. [Latency is zero](#fallacy-2-latency-is-zero) +> 3. [Bandwidth is infinite](#fallacy-3-bandwidth-is-infinite) +> 4. [The network is secure](#fallacy-4-the-network-is-secure) + +### Fallacy 1: The network is reliable {#fallacy-1-the-network-is-reliable} + +> All networks that really exist, that are configured and accessed by human beings, served from machines sitting in a building, connected by cables and powered by electricity, are unreliable, because any of these things can break. In reality, your network is not reliable. + +#### Unreliable networks, errors, and retries {#unreliable-networks-errors-and-retries} + +Sometimes communication from a client will fail to reach the server, or the server’s response will fail to return to the client, even though the work requested was completed. + +RPCs can fail for other reasons. Sometimes servers will reject requests because they are overloaded and do not have the capacity to serve additional requests. + +Graceful shutdowns of servers can cause client errors. All servers have a lifecycle: they are launched, they begin serving, and at some point, they will stop serving and be replaced. In a distributed system, we usually have multiple servers - so we can gracefully replace servers by sending requests elsewhere. Servers that are about to shut down can send error codes to clients attempting to begin new requests (the HTTP/2 [GOAWAY ](https://httpwg.org/specs/rfc7540.html#GOAWAY)code is an example of this). The client is expected to try another available server. + +All client-server code needs to deal with errors. A common method is to _retry_ a request that failed, by re-sending it to a different server. Retries are a useful strategy but there are pitfalls. These considerations can be grouped under: Status codes, Idempotency, and Limits. + +##### Status codes {#status-codes} + +The first question to ask when retrying a request is whether it is a request that could possibly succeed. Errors returned by the gRPC library to a client include a [status code](https://grpc.github.io/grpc/core/md_doc_statuscodes.html) - these are somewhat similar to HTTP status codes. The status code can be generated either by the server or by the client (for example, if the client can’t connect to the server the client will generate an `UNAVAILABLE` status code and return it to the calling code). Some of these codes indicate that a request is never likely to succeed - for example,` INVALID_ARGUMENT` means that the request is malformed in some way - while others, like `UNAVAILABLE` or `RESOURCE_EXHAUSTED `might succeed if retried. + +##### Idempotency {#idempotency} + +The second question to ask is whether it is safe to retry the request or not. Some kinds of requests are[ idempotent](https://betterprogramming.pub/architecting-distributed-systems-the-importance-of-idempotence-138722a6b88e): this means that the result will be the same, whether you make the request once or multiple times. For example, setting an attribute to a specific value is idempotent, but adding a number to the existing value is not. Non-idempotent requests can’t be safely retried. This means that it’s best to design your systems with idempotent APIs wherever possible. Services can use unique request IDs to deduplicate requests, changing a non-idempotent API into one that may be safely retried. + +##### Limits {#limits} + +It is very important to limit the number of retries that a client makes to a service. If a service becomes overwhelmed with load and each of its clients repeatedly retries failing requests then it becomes a vicious cycle. The service will not be able to recover without intervention. Groups of continuously-retrying clients can create an effect similar to a [Distributed Denial of Service](https://www.cloudflare.com/en-gb/learning/ddos/what-is-a-ddos-attack/) (DDoS) attack. + +A maximum of three retries is a reasonable rule of thumb for interactive workloads, in other words, when a human is waiting for a response. By the time your system has retried three times the user is likely to try again or to give up, so there is no point in making further attempts. However, some systems are not interactive, such as [batch jobs](https://en.wikipedia.org/wiki/Batch_processing) or [data pipelines](https://www.snowflake.com/guides/data-pipeline). In these systems it is reasonable to attempt more than three retries, after a long delay. + +There are lots of ways to limit retries: by layer, by duration between attempts, and with circuit breakers. + +##### Layers {#layers} + +Systems may implement retrying at multiple levels. For example, it is not uncommon to see client retries combined with retries at a loadbalancing layer. This has a multiplicative effect: if both levels retry failed requests three times, then this can result in nine retries. It is best to limit the use of retries to one layer of your stack and to be consistent about where they are performed. + +##### Duration Between Attempts {#duration-between-attempts} + +Clients should increase the duration of their waits between retries exponentially: double it for the second retry attempt, again for the third, and so on. This technique is called _exponential backoff_. In cases where a backend service is briefly unavailable, the use of exponential backoff by its clients means that the backend service is less likely to be overwhelmed with load when it comes back up. + +Jitter means a short variable wait before retrying. It is good practice to use jitter when retrying. The jitter, or wait time, is randomly chosen, between a minimum and maximum value. The idea of introducing such jitter is to avoid overwhelming backend servers with a coordinated ‘echo’ of an original spike of errors, which could cause a further overload. + +Use jitter and exponential backoff together r when implementing retries. Many programming languages include libraries that implement exponential backoff with jitter - for example, see [GoLang’s go-retry library](https://pkg.go.dev/gopkg.in/retry.v1). Some loadbalancers, such as [Envoy Proxy](https://www.envoyproxy.io/docs/envoy/latest/faq/load_balancing/transient_failures), also allow you to configure these retry strategies at the loadbalancer layer. + +###### Circuit Breakers {#circuit-breakers} + +A very useful pattern for client-server requests is the [Circuit Breaker](https://martinfowler.com/bliki/CircuitBreaker.html). Use circuit breakers to limit potentially-dangerous retries. According to Martin Fowler: “The basic idea behind the circuit breaker is very simple. You wrap a protected function call in a circuit breaker object, which monitors for failures. Once the failures reach a certain threshold, the circuit breaker trips, and all further calls to the circuit breaker return with an error, without the protected call being made at all. Usually you'll also want some kind of monitor alert if the circuit breaker trips.” + +### Fallacy 2: Latency is zero {#fallacy-2-latency-is-zero} + +> All real networks are connected over real physical distances. The distance between nodes is like the travel time between cities on Google Maps: the route, mode of transport, amount of traffic, and distance, all affect the travel time. Latency means the time taken for a request to traverse the network. Network latency is travel time. + +#### Variable latency and deadlines {#variable-latency-and-deadlines} + +Latency can be highly variable, particularly over long distances. Processing time at the server can also vary a lot, which matters because latency as perceived by clients is the sum of network latency and application processing time at the server. + +High latency has implications for clients. It is always a bad idea to wait indefinitely for a response from a server. It is entirely possible to end up waiting forever. For example, if the server ‘hangs up’ and your TCP connection does not have keepalives enabled, you will wait indefinitely. When this happens, no useful work gets done and it can result in problems that are difficult to track down, because a client that waits indefinitely generates no errors. + +If some abnormal condition means that many clients in a system are experiencing long waits for some service (even a small and trivial one) it can result in widespread problems as everything slows down waiting for the slowest service. Again, this is generally difficult to troubleshoot. When everything is slow, the source of that slowness is not obvious - it can be quite hard to pinpoint the reason. + +Instead, define a specific deadline for every RPC request and deal with it as an error when the deadline is past. This is a much better approach because it is usually very easy to find errors in monitoring and in logs. When our systems are not operating correctly, it is very important to be able to quickly find the problem and solve it. + +Set deadlines much higher than typical response latencies. In general, the deadline should be higher than your typical 99.9% response time - this means that only one in 1000 requests would be slower than the deadline in normal operation. + +You should be able to find out what your observed response latencies are either by using your monitoring system or by analysing application logs. Your organisation may run an open-source monitoring tool such as [Prometheus ](https://prometheus.io/)or [Graphite](https://grafana.com/oss/graphite/), or you may use a SaaS monitoring system such as [New Relic](https://newrelic.com/) or [DataDog](https://www.datadoghq.com/). All of these systems allow you to [instrument your application](https://alex.dzyoba.com/blog/go-prometheus-service/) so that you can understand runtime behaviour, such as response latencies and error rates. + +It is very important to monitor your observed error rates and latencies and to investigate if you see a significant number of timeouts. The threshold you set here depends on the degree of reliability required for your application, but a typical threshold for a high-traffic application would be more than 0.01% of requests timing out, or failing for any reason. + +You should also monitor whether your observed request latencies are approaching the deadline you have set. For example, you might configure your monitoring system to alert if the 99th [percentile](https://en.wikipedia.org/wiki/Percentile) latency (i.e. the slowest 1% of requests) exceeds 80% of your configured request deadline. The reason for this is that request latencies may drift higher as changes are made to systems over time. If request latencies become so slow that many requests begin to take longer than the deadline, then your system is doing wasted work. Clients will retry failed requests, and your system may become overloaded as a result. AWS’s account of their [DynamoDB Service Disruption](https://aws.amazon.com/message/5467D2/) in 2015 is a good description of this phenomenon. + +### Fallacy 3: Bandwidth is infinite {#fallacy-3-bandwidth-is-infinite} + +> All systems that actually exist in the world have limits. Computers do not have infinite memory. Networks do not have infinite bandwidth. All the nodes in your system are physically real and all real things are finite, or limited. + +#### Limited bandwidth {#limited-bandwidth} + +Network bandwidth is not unlimited. Passing very large requests or responses between machines may be unreliable. As we saw above, it is helpful to set deadlines on requests. However, it is possible that the time to process a very large request might exceed an RPC deadline. Some services also attempt to buffer incoming requests before beginning to process them. Because no computer has infinite memory, it is common (and good practice) to limit the size of buffered requests. However, very large requests may exceed the configured buffer size and fail. In most cases, very large requests bodies are not legitimate requests. Where large amounts of data must be transferred, it is worth considering a specialised protocol (such as [tus, which has a Go implementation](https://pkg.go.dev/github.com/tus/tusd?utm_source=godoc)). + +### Fallacy 4: The network is secure {#fallacy-4-the-network-is-secure} + +> Your network exists in the world and interacts with other networks, systems, and actors, with their own objectives. Many of these objectives conflict with your own, and some are hostile. Networks can never be assumed to be secure. + +#### mTLS + +You should assume that all network links are being eavesdropped, and therefore by default encrypt all data on the wire. This is modern best practice. Applications should use methods such as [mTLS ](https://www.cloudflare.com/en-gb/learning/access-management/what-is-mutual-tls/)to secure communications between clients and servers. + +Here’s how you do [mTLS in Go](https://venilnoronha.io/a-step-by-step-guide-to-mtls-in-go), although in a real production environment you also need to manage the creation, distribution and renewal of keys. Here’s how [Zendesk does mTLS key management](https://zendesk.engineering/implementing-mtls-and-securing-apache-kafka-at-zendesk-10f309db208d). + +--- + +## Questions {#questions} + +- ​​What is latency? Why is it important? +- Why is idempotency a desirable property in a web service? +- When should I use RPCs, as opposed to simple HTTP requests? + +## Project work for this section {#project-work-for-this-section} + +See: + +- [https://github.com/CodeYourFuture/immersive-go-course/tree/main/http-auth](https://github.com/CodeYourFuture/immersive-go-course/tree/main/http-auth) [should have been done in prework] +- [https://github.com/CodeYourFuture/immersive-go-course/tree/main/grpc-client-server](https://github.com/CodeYourFuture/immersive-go-course/tree/main/grpc-client-server) diff --git a/website/content/primers/distributed-software-systems-architecture/1-reliable-rpcs/rpcs.webp b/website/content/primers/distributed-software-systems-architecture/1-reliable-rpcs/rpcs.webp new file mode 100644 index 0000000000000000000000000000000000000000..c6eb5685faa0aec6fc39ea776ace1daf8ea4dff5 GIT binary patch literal 36160 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 F0007nA>;r6 literal 0 HcmV?d00001 diff --git a/website/content/primers/distributed-software-systems-architecture/2-state/index.md b/website/content/primers/distributed-software-systems-architecture/2-state/index.md new file mode 100644 index 000000000..10d1d1241 --- /dev/null +++ b/website/content/primers/distributed-software-systems-architecture/2-state/index.md @@ -0,0 +1,294 @@ ++++ +title="2. State" ++++ + +# 2 + +## State + +**State*ful* and state*less*** + +> Components in distributed systems are often divided into stateful and stateless services. Stateless services don’t store any state between serving requests. Stateful services, such as databases and caches, do store state between requests. State _stays_. + +When we need to scale up a stateless service we simply run more instances and loadbalance between them. Scaling up a stateful service is different: we need to deal with splitting up or replicating our data, and keeping things in sync. + +### Caching {#caching} + +Caches are an example of a stateful service. A [cache service](https://aws.amazon.com/caching/) is a high-performance storage service that stores only a subset of data, generally the data most recently accessed by your service. A cache can generally serve data faster than the primary data store (for example, a database). A cache stores a small set of data in RAM whereas a database stores all of your data on disk, which is slower. + +However, caches are not _durable_. When an instance of your cache service restarts, it does not hold any data. Until the cache has filled with a useful _working set_ of data, all requests will be _cache misses_, meaning that they need to be served from the primary datastore. The _hit rate_ is the percentage of cacheable data that can be served from the cache. Hit rate is an important aspect of cache performance which should be monitored. + +There are different strategies for getting data into your cache. + +#### Lazy Loading + +The most common strategy is _lazy loading_: your application always tries to load data from the cache first when it needs it. If it isn’t in the cache, the data is fetched from the primary data store and copied into the cache. + +#### Write through + +You can also use a _write through_ strategy: every time your application writes data, it writes it to the cache at the same time as writing it to the datastore. However, when using this strategy, you have to deal with cases where the data isn’t in the cache (for instance, via lazy loading). Read about the [pros and cons](https://docs.aws.amazon.com/AmazonElastiCache/latest/mem-ug/Strategies.html#Strategies.WriteThrough) of these cache-loading strategies. + +### Why a cache service? + +Cache services, such as [memcached](https://www.memcached.org/) or [redis](https://redis.io/), are very often used in web application architectures to improve read performance, as well as to increase system throughput (the number of requests that can be served given a particular hardware configuration). + +#### Sticky sessions + +You can also cache data in your application layer – but this adds complexity, because in order to be effective, requests affecting the same set of data must be routed to the same instance of your application each time. This means that your loadbalancer has to support the use of _[sticky sessions](https://www.linode.com/docs/guides/configuring-load-balancer-sticky-session/)._ + +#### Restarts + +In-application caches are less effective when your application is restarted frequently, because restarting your application means that all cached data is lost and the cache will be _cold_: it does not have a useful working set of data. In many organisations, web applications get restarted very frequently. There are two main reasons for this. + +1. Deployment of new code. Many organisations using modern [CI/CD (Continuous Integration/Continuous Delivery)](https://en.wikipedia.org/wiki/CI/CD) deploy new code on every change, or if not on every change, many times a day. +2. The use of _[autoscaling](https://en.wikipedia.org/wiki/Autoscaling)_. Autoscaling is the use of automation to adjust the number of running instances in a software service. It is very common to use autoscaling with stateless services, particularly when running on a cloud provider. Autoscaling is an easy way to scale your service up to serve a peak load, while not paying for unused resources at other times. However, autoscaling also means that the lifespan of instances of your service will be reduced. + +The use of a separate cache service means that the stateless web application layer can be deployed frequently and can autoscale according to workload while still using a cache effectively. Cache services typically are not deployed frequently and are less likely to use autoscaling. + +#### Hazards of using cache services + +Operations involving cache services must be done carefully. **Any operation that causes all your caches to restart will leave your application with an entirely _cold_ cache** - a cache with no data in it. All reads that your cache would normally have served will go to your primary datastore. + +There are multiple possible outcomes in this scenario. + +##### First case + +In the first case, your primary datastore can handle the increased read load and the only consequence for your application will be an increase in duration of requests served. Your application will also consume additional system resources: it will hold more open transactions to your primary datastore than usual, and it will have more requests in flight than usual. Over time your cache service fills with a useful working set of data and everything returns to normal. Some systems offer a feature called _[cache warmup](https://github.com/facebook/mcrouter/wiki/Cold-cache-warm-up-setup)_ to speed this process up. + +##### Second case + +In the second case, even though your datastore can handle the load, your application cannot handle the increase in in-flight requests. It may run out of resources such as worker threads, datastore connections, CPU, or RAM. (This depends on the specific web application framework you are using.) Applications in this state will serve errors or fail to respond to requests in a timely fashion. + +##### Third case + +In the third case, your primary datastore is unable to handle the entire load, and some requests to your datastore begin to fail or to time out. As discussed in the previous section, you should be setting a deadline for your datastore requests. In the absence of deadlines, your application will wait indefinitely for your overloaded datastore, and is likely to run out of system resources and then grind to a halt, until it is restarted. Setting deadlines allows your application to serve what requests it can without grinding to a halt. After some time your cache will fill and your application should return to normal. + +Read about an incident at Slack involving failure of a caching layer: [Slack’s Incident on 2-22-22](https://slack.engineering/slacks-incident-on-2-22-22/). + +### Cache Invalidation + +> There are two hard things in computer science: cache invalidation, naming things, and off-by-one errors. + +Cache invalidation means removing or updating an entry in a cache. + +Cache invalidation is hard because caches are optimised towards fast reads, without synchronising with the primary datastore. If a cached item is updated then the application must either tolerate stale cached data, or update all cache replicas that reference the updated item. + +Caches generally support specifying a Time-To-Live (TTL). After the TTL passes, or expires, the item is removed from the cache and must be fetched again from the main datastore. This lets you put an upper limit on how stale cached data may be. + +It is useful to add some ‘jitter’ when specifying TTLs. This means varying the TTL duration slightly. For example, instead of always using a 60 second TTL, you might randomly choose a duration in the range 54 to 66 seconds, up to ten percent higher or lower. This reduces the likelihood of load spikes on backend systems as a result of coordinated waves of cache evictions. + +#### Immutable data + +One simple strategy to manage cache invalidation problems is to avoid updating data where possible. Instead, we can create new versions of the data. + +For example, if we are building an application that has profile pictures for users, when the user updates their profile picture we: + +1. create a new profile picture with a new unique ID +2. update the user data to refer to the new profile picture rather than the old one. + +Doing this means that there is no need to invalidate the old cached profile picture: we just stop referring to it. Eventually, the old picture will be removed from the cache, as the cache removes entries that have not been accessed recently. + +In this strategy the profile picture data is immutable; it never changes. However, the user data _does_ change, meaning that cached copies must be invalidated, or must have a short TTL so that users will see new profile pictures in a reasonable period of time. + +Read more about [Ways to Maintain Cache Consistency](https://redis.com/blog/three-ways-to-maintain-cache-consistency/) in this article. + +### Scaling Caches + +#### Replicated Caches + +In very large systems, it may not be possible to serve all cache reads from one instance of a cache. Caches can run out of memory, connections, network capacity, or CPU. And with only one cache instance, if we lose or even update the instance, we will lose all of our cache capacity if we lose that single instance, or if we need to update it. + +We can solve these problems by running multiple cache instances. We could split the caches up according to the type of data to be cached. For example, in a collaborative document-editing system, we might have one cache for Users, one cache for Documents, one cache for Comments, and so on. This works, but we may still have more requests for one or more of these data types than a single cache instance can handle. + +A way to solve this problem is to replicate the caches. In a replicated cache setup, we would have two or more caches serving the same data. For instance, we might choose to run three replicas of the Users cache. Reads for Users data can go to any of the three replicas. However, when Users data is updated, we must either: + +- invalidate all three instances of the Users cache +- tolerate stale data until the TTL expires (as described above) + +##### The cost of invalidation + +The need to invalidate all instances of the Users cache adds cost to every write operation. It costs us in more latency, because we must wait for the slowest cache instance to respond. It costs us in higher use of bandwidth and other computing resources, because we have to do work on every cache instance that stores the data being invalidated. This cost increases the more replicated instances we run. + +##### Inconsistency is always a possibility + +There is another complication: what happens if we write to the Users database table, but cannot connect to one or more of the cache servers? In practice, this can happen and it means that inconsistency between datastores and caches is always a possibility in distributed systems. + +There is further discussion of this problem below in the section on [CAP theorems](#the-cap-theorem). + +#### Sharded Caches + +Another approach to scaling cache systems is to shard the data instead of replicating it. This means that your data is split across multiple machines, instead of each instance of your cache storing all of the cached items. This is a good choice when the working set of recently accessed data is too large for any one machine. Each machine hosts a _partition_ of the data set. In this setup there is usually a router service that is responsible for proxying cache requests to the correct instance: the instance that stores the data to be accessed. + +Data sharding can be vertical or horizontal. In vertical sharding, we store different fields on different machines (or sets of machines). In horizontal sharding, we split the data up by rows. + +In the case of horizontal sharding, we can shard algorithmically or dynamically. + +##### Algorithmic sharding + +Algorithmic sharding means the shard to store a given row is determined by a function. + +##### Dynamic sharding + +Dynamic sharding means that the system maintains a lookup table that tracks where data ranges are stored. + +Read [Introduction to Distributed Data Storage by Quentin Truong](https://towardsdatascience.com/introduction-to-distributed-data-storage-2ee03e02a11d) for more on sharding. + +#### Problems with sharding + +A simple algorithmic sharding implementation might route requests to one of _N_ routers using a modulo operation: `cache_shard = id % num_shards`. The problem is that whenever a shard is added or removed, most of the keys will now be routed to a different cache server. This is equivalent to restarting all of our caches at once and starting cold. As discussed above, this is bad for performance, and potentially could cause an outage. + +This problem is usually solved using a technique called [consistent hashing](https://en.wikipedia.org/wiki/Consistent_hashing). A consistent hash function maps a key to a data partition, and it has the very useful property that when the number of partitions changes, most keys do not get remapped to a different partition. This means that we can add and remove cache servers safely without risking reducing our cache hit rate by too much. Consistent hashing is a very widely used technique for load balancing across stateful services. + +You can read about [how Pinterest scaled its Cache Infrastructure](https://medium.com/pinterest-engineering/scaling-cache-infrastructure-at-pinterest-422d6d294ece) - it’s a useful example of a real architecture. They use an open-source system called [mcrouter](https://github.com/facebook/mcrouter/wiki) to horizontally scale their use of memcached. Mcrouter is one of the most widely-used distributed cache solutions in industry. + +### Questions + +- What is a cache hit rate and why is it important? +- What do we mean by ‘cold’ or ‘warm’ when we discuss caches? +- Why do we use consistent hashing when we shard data? +- When should we consider sharding a cache rather than replicating it? +- Why do we need cache invalidation? +- What is a TTL used for in caching? +- Why should we cache in a standalone cache service, as opposed to within our application? + +### Databases + +Most web applications we build include at least one database. Databases don’t have to be distributed systems: you can run a database on one machine. However, there are reasons that you might want to run a distributed database across two or more machines. The logic is similar to the rationale for running more than one cache server, as described above. + +1. **Reliability:** you don’t want to have an interruption in service if your database server experiences a hardware failure, power failure, or network failure. +2. **Capacity:** you might run a distributed database to handle more load than a single database instance can serve. + +To do either of these things we must _replicate_ the data, which means to copy the data to at least one other database instance. + +#### The CAP Theorem + +> **C**onsistency **A**vailability **P**artition tolerance + +The [CAP Theorem](https://en.wikipedia.org/wiki/CAP_theorem) is a computer science concept that states that any distributed data store can provide at most **two** of the following three properties: + +1. Consistency: every read should receive the most recently written data or else an error +2. Availability: every request receives a response, but there is no guarantee that it is based on the most recently written data +3. (Network) Partition tolerance: the system continues to operate even if network connectivity between some or all of the computers running the distributed data store is disrupted + +This seems complicated, but let’s break it down. + +##### Network Partition + +Imagine that you are running your service in two datacenters, and the fiber optic cables between them are dug up. This is a network partition. + +A network partition means that your computers cannot all communicate over the network. Your servers won’t be able to communicate with servers in the opposite datacenter. Configuration problems, or simply too much network traffic can also cause network partitions. + +Network partitions do happen, and there is no way that you can avoid this unpleasant fact of life. So in practice, distributed datastores have to choose between being _consistent_ when network failures occur, or being _available_. It’s not a matter of choosing any two properties: you must choose either consistency and network partition tolerance _or_ availability and network partition tolerance. + +##### Consistency and availability + +Choosing consistency means that your application won’t be available on one side of the partition (or it might be read-only and serving old, stale data). Choosing availability means that your application will remain available, including for writes, but when the network partition is healed, you need a way of merging the writes that happened on different sides of the partition. + +The CAP Theorem is a simplified model of distributed datastores, and it has been [criticised](https://martin.kleppmann.com/2015/05/11/please-stop-calling-databases-cp-or-ap.html) on that basis. It remains a reasonably useful model for learning about distributed datastore concepts. + +In general, replicated traditional datastores like MySQL choose consistency in the event of network partitions; [NoSQL datastores](https://www.mongodb.com/nosql-explained) such as Cassandra and Riak tend to choose availability. + +(However, the behaviour of systems in the event of network failure can also vary based on how that specific datastore is configured.) + +#### Leader/Follower Datastore Replication + +The most straightforward distributed datastore is the leader/follower datastore, also known as primary/secondary or single leader replication. The aim is to increase the availability and durability of the data. In other words, if we lose one of the datastore machines, we should not lose data and we should still be able to run the system. + +##### Synchronous Replication + +To do something synchronously means to do something at the same time as something else. + +In synchronous replication, the datastore client sends all writes to the leader. The leader has one or more followers. For every write, the leader sends those writes to its followers, and waits for all its followers to acknowledge completion of the write before the leader sends an acknowledgement to the client. + +Think of a race where all the runners are tied together with a rope. Leader and followers commit the data as part of the same database operation. Reads can go either to the leader, or to a follower, depending on how the datastore is configured. Reading from followers means that the datastore can serve a higher read load, which can be useful in applications that are serving a lot of traffic. + +There is one problem with synchronous replication: availability. Not only must the leader be available, but _all of the followers_ must be available as well. This is a problem: the system’s availability for writes will actually be lower than a single machine, because the chances of one of _N_ machines being unavailable are by definition higher than the chances of one machine being unavailable, because we have to add up the probabilities of downtime. + +For example, if one machine has 99.9% uptime and 0.1% downtime, and we have three machines, then we would expect the availability for all three together to be closer to 99.7% (i.e. 100% - 3 x 0.1%). Adding more replicas makes this problem worse. Response time is also a problem with synchronous replication, because the leader has to wait for all followers. This means that the system cannot commit writes faster than the slowest follower. + +![alt_text](./assets/leader-follower-diagram.png "leader follower diagram") + +###### Asynchronous and Semisynchronous Replication + +We can solve these performance and availability problems by not requiring the leader to write to _all_ the followers before returning to the client. Asynchronous replication means that the leader can commit the transaction before replicating to any followers. Semisynchronous replication means that the leader must confirm the write at a subset of its followers before committing the transaction. + +

>>>>> gd2md-html alert: inline image link here (to images/image3.png). Store image on your image server and adjust path/filename/extension if necessary.
(Back to top)(Next alert)
>>>>>

+ +![alt_text](images/image3.png "image_tooltip") + +Many databases provide configuration settings to control how many replicas need to acknowledge a write before the write can be considered complete. [MySQL](https://dev.mysql.com/doc/refman/8.0/en/replication.html), for example, defaults to asynchronous replication. MySQL can also support fully synchronous replication as described in the previous section, or semisynchronous replication, which means that at least one follower has committed every write before the leader considers the write complete. + +However, asynchronous and semisynchronous replication are no free lunch. If using these forms of replication, followers can have data that is _behind_ the data that the leader stores. Data on followers can be different to the leader and can also vary between followers. This means that a client might write a value to the datastore and then read back an older value for that same field from a follower. A client might also read the same data twice from two different followers and see an older value on the second read. This can result in display of incorrect data, or application bugs. + +###### Transaction ID {#transaction-id} + +This problem can be solved by use of a transaction ID. Basically, the client has to track transaction IDs for their writes and reads and send that value with their reads to followers. Followers don’t return data until their replication has caught up with the transaction ID that the client specifies. This involves changing your application code, which is inconvenient. One solution to this problem is using a proxy (another program that relays requests and responses) between your application and your database. ProxySQL is a proxy that is designed to work with replicated MySQL databases, and it [supports tracking transaction IDs ](https://proxysql.com/blog/proxysql-gtid-causal-reads/)in this way. + +#### Time and Ordering in Distributed Systems {#time-and-ordering-in-distributed-systems} + +The kind of transaction IDs being discussed here are _logical timestamps_. Logical timestamps aren’t the same as system time, or time as we would read it from a clock. They are an ascending sequence of IDs that can be compared. Given two logical timestamps, we can tell if one event happened before the other event or not: this is what lets us solve our problems with data consistency above. + +Logical timestamps create an ordering based on communications in distributed systems. + +When the database leader sends transaction data, it sends a message along with it. The message is a transaction ID, or logical timestamp. A database replica can order its data in time – tell which data is current – using the transaction IDs. + +Why not just use system time to figure out ordering in distributed systems? The reason is that in a distributed system we normally have no guarantees that different computers’ system clocks are in sync. We can use [Network Time Protocol ](https://en.wikipedia.org/wiki/Network_Time_Protocol)(NTP) to synchronise within a few milliseconds, but it is not perfect - and challenges can arise with respect to leap seconds or other problems. Some specialised systems do use special hardware clocks to keep systems synchronised (such as [Google’s Spanner datastore](https://cloud.google.com/spanner/docs/true-time-external-consistency)), but this is unusual. + +Further Reading on Time, Database Replication and Failover: + +- [There is No Now: Problems with simultaneity in distributed systems](https://queue.acm.org/detail.cfm?id=2745385) +- [Database Replication Explained. Part 1 — Single Leader Replication | by Zixuan Zhang | Towards Data Science](https://towardsdatascience.com/database-replication-explained-5c76a200d8f3) +- [DB Replication (II): Failure recovery fundamentals](https://www.brainstobytes.com/db-replication-i-introduction-to-database-replicationdb-replication-ii-failure-recovery-fundamentals/) + +#### Questions: {#questions} + +- What are the challenges associated with recovering from a failed database replica? + +### Sharding {#sharding} + +As we saw with caches, replication can help us to scale our read load for databases. However, at a certain scale, sharding becomes necessary. The general considerations are quite similar to sharding a caching layer. Read [Understanding Database Sharding](https://www.digitalocean.com/community/tutorials/understanding-database-sharding) for more detail. + +A common sharded datastore used in industry is Vitess, originally developed at YouTube but available as an open-source project. It is an [orchestration layer](https://www.databricks.com/glossary/orchestration) designed for operating a massively sharded fleet of MySQL database instances and doing so reliably. It is worth reading some of the Vitess documentation: + +- [https://vitess.io/docs/14.0/overview/whatisvitess/](https://vitess.io/docs/14.0/overview/whatisvitess/) +- [https://vitess.io/docs/14.0/overview/architecture/](https://vitess.io/docs/14.0/overview/architecture/) +- [https://vitess.io/docs/14.0/overview/history/](https://vitess.io/docs/14.0/overview/history/) +- [https://vitess.io/docs/14.0/overview/scalability-philosophy/](https://vitess.io/docs/14.0/overview/scalability-philosophy/) + +#### Questions: {#questions} + +- Why are smaller data shards recommended? +- What kind of changes might you need to make to your application before moving to a sharded datastore? +- What is a cell? What happens if a cell fails? + +### Project work for this section {#project-work-for-this-section} + +[https://github.com/CodeYourFuture/immersive-go-course/tree/main/memcached-clusters](https://github.com/CodeYourFuture/immersive-go-course/tree/main/memcached-clusters) + +#### Stateful Services: Additional Reading {#stateful-services-additional-reading} + +Discuss these papers in office hours with Laura Nolan. + +Some of these are academic papers. If you aren’t used to reading academic writing, take some time to read [How to read and understand a scientific article](https://violentmetaphors.files.wordpress.com/2018/01/how-to-read-and-understand-a-scientific-article.pdf) first. + +[Dynamo: Amazon’s Highly Available Key-value Store](https://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf): This paper from 2007 describes how Dynamo, Amazon’s scalable K/V store which they originally developed to support shopping carts, works. The core of the system is consistent hashing, as described earlier in this section. + +- What kind of sharding is this, algorithmic or dynamic? + +[Amazon DynamoDB: A Scalable, Predictably Performant, and Fully Managed NoSQL Database Service](https://www.usenix.org/system/files/atc22-elhemali.pdf): This much more recent paper shows DynamoDB’s evolution into a Cloud service, with quite different demands to the original shopping cart use case. + +- Describe two ways in which this architecture differs from the original architecture described in the 2007 paper above, and why. + +[The Google File System](https://static.googleusercontent.com/media/research.google.com/en//archive/gfs-sosp2003.pdf): Classic paper from 2003 describing Google’s first distributed file system architecture. + +- GFS uses a different kind of sharding model to DynamoDB - is it algorithmic or dynamic? Why do you think GFS’s designers chose that model? + +[Bigtable: A Distributed Storage System for Structured Data](https://static.googleusercontent.com/media/research.google.com/en//archive/bigtable-osdi06.pdf): Paper which builds on the Google File System (GFS) paper about how Google built their K/V store, Bigtable, on top of GFS. + +- What constraints did GFS’s properties impose on the BigTable design? + +[After the Retrospective: The 2017 Amazon S3 Outage](https://www.gremlin.com/blog/the-2017-amazon-s-3-outage/): You can read this analysis of an AWS S3 storage service outage. + +- Draw a rough diagram of the S3 architecture showing what happened. + +## diff --git a/website/content/primers/distributed-software-systems-architecture/2-state/leader-follower-diagram.png b/website/content/primers/distributed-software-systems-architecture/2-state/leader-follower-diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd98cb8b1de2316def8ec3cc9a2a416c7d94cf5 GIT binary patch literal 54777 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 In a program, sometimes we need to lock a resource. Think about a physical device, like a printer: we only want one program to print at a time. Locking applies to lots of other kinds of resources too, often when we need to update multiple pieces of data in consistent ways in a multi-threaded context. + +### How to do distributed locking + +We need to be able to do locking in distributed systems as well. Read Martin Kleppmann’s article [How to do distributed locking](https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html). + +### What are the two main reasons to do distributed locking? + +It turns out that in terms of computer science, distributed locking is theoretically equivalent to reliably electing a single leader (like in database replication, for example). It is also logically the same as determining the exact order that a sequence of events on different machines in a distributed system occur. All of these are useful. + +### RAFT + +An algorithm that you should know about is the RAFT distributed consensus protocol: read [https://raft.github.io/raft.pdf](https://raft.github.io/raft.pdf). + +- Under what circumstances is a RAFT cluster available? +- What does the leader do? +- Is there always a leader? +- How is a leader elected? +- What happens if one replica falls behind the leader, i.e. the leader has committed transactions that the replica doesn’t have? +- What happens if a server is replaced? + +Read about the operational characteristics of distributed consensus algorithms in [Managing Critical State.](https://sre.google/sre-book/managing-critical-state/) + +- What are the scaling limitations of distributed consensus algorithms? +- How can we scale read-heavy workloads? + +### Project work for this section {#project-work-for-this-section} + +See [raft-otel](https://github.com/CodeYourFuture/immersive-go-course/tree/main/raft-otel) project. +This project is an opportunity to explore the RAFT distributed consensus protocol through the medium of distributed tracing. + +## Further Optional Reading {#further-optional-reading} + +Discuss any of these pieces at office hours with Laura. + +[Dan Luu’s list of postmortems](https://github.com/danluu/post-mortems) +: includes a lot of interesting stories of real-world distributed systems failure + +[Jeff Hodges' Notes on Distributed Systems for Young Bloods](https://www.somethingsimilar.com/2013/01/14/notes-on-distributed-systems-for-young-bloods/) +: is a very practical take on distributed systems topics. + +[Alvaro Videla's blog post and talks about learning distributed systems](https://alvaro-videla.com/2015/12/learning-about-distributed-systems.html) +: are accessible and well organised. + +[Marc Brooker’s blog](https://brooker.co.za/blog/) +: is full of interesting pieces, which are very approachable. + +[The SRE Book](https://sre.google/sre-book/table-of-contents/) +: is available in full online and is worth reading - it addresses many aspects of operating distributed software systems. + +Aphyr (Kyle Kingsbury) +: provides detailed [notes for a distributed systems class he teaches.](https://github.com/aphyr/distsys-class) + +[A Distributed Systems Reading List](https://dancres.github.io/Pages/) +: will keep you reading excellent distributed systems papers for many many months. diff --git a/primers/distributed-software-systems-architecture/_index.md b/website/content/primers/distributed-software-systems-architecture/README.md similarity index 100% rename from primers/distributed-software-systems-architecture/_index.md rename to website/content/primers/distributed-software-systems-architecture/README.md diff --git a/website/content/primers/troubleshooting/README.md b/website/content/primers/troubleshooting/README.md new file mode 100644 index 000000000..64360aa41 --- /dev/null +++ b/website/content/primers/troubleshooting/README.md @@ -0,0 +1,18 @@ ++++ +title="Troubleshooting Primer" +author="Laura Nolan and Radha Kumari" +date="28 Dec 2022 12:22:11 BST" ++++ + +# Troubleshooting Primer + +## About this document {#about-this-document} + +This document is a crash course in troubleshooting. It is aimed at people with some knowledge of computing and programming, but without significant professional experience in operating distributed software systems. This document does not aim to prepare readers to be oncall or be an incident responder. It aims primarily to describe the skills needed to make progress in day-to-day software operations work, which often involves a lot of troubleshooting in development and test environments. + +## Learning objectives: + +- Explain troubleshooting and how it differs from debugging +- Name common troubleshooting methods +- Experience working through some example scenarios. +- Use commonly used tools to troubleshoot diff --git a/website/content/primers/troubleshooting/primer.md b/website/content/primers/troubleshooting/primer.md new file mode 100644 index 000000000..c3a73a4f0 --- /dev/null +++ b/website/content/primers/troubleshooting/primer.md @@ -0,0 +1,308 @@ ++++ +title="Troubleshooting Primer" ++++ + +# Troubleshooting Primer + +## About this document {#about-this-document} + +This document is a crash course in troubleshooting. It is aimed at people with some knowledge of computing and programming, but without significant professional experience in operating distributed software systems. This document does not aim to prepare readers to be oncall or be an incident responder. It aims primarily to describe the skills needed to make progress in day-to-day software operations work, which often involves a lot of troubleshooting in development and test environments. + +## Learning objectives: + +- Explain troubleshooting and how it differs from debugging +- Name common troubleshooting methods +- Experience working through some example scenarios. +- Use commonly used tools to troubleshoot + +## Troubleshooting Versus Debugging {#troubleshooting-versus-debugging} + +Troubleshooting is related to debugging, but isn’t the same thing. + +Debugging relates to a codebase into which the debugger has full visibility. In general, the scope of the debugging is limited to a single program; in rare cases it might extend to libraries or dependent systems. In general, debugging techniques involve the use of debuggers, logging, and code inspection. + +In troubleshooting we investigate and resolve problems in complex systems. The troubleshooter may not know which subsystem the fault originates with. The troubleshooter may not know the full set of relationships and dependencies in the system exhibiting the fault, and they may not be able to inspect all of the source code (or familiarity with the codebases involved). + +We can use debugging to fix a system of limited complexity that we know completely. When many programs interact in a complex system that we can’t know completely, we must use troubleshooting. + +Examples of troubleshooting: + +- finding the cause of unexpectedly high load in a distributed system +- finding why packets are being dropped in a network between two hosts +- determining why a program is failing to run correctly on a host. + +## General Troubleshooting Methods {#general-troubleshooting-methods} + +Because troubleshooting involves a wide variety of systems, some of which may be unknown, we cannot create a comprehensive troubleshooting curriculum. Troubleshooting is not just learning a bank of answers but learning how to ask, and answer, questions methodically. Troubleshooters use documentation, tooling, logic, and discussion to analyse system behaviours and close in on problems. Being able to recognize gaps in your knowledge and to fill them is a key troubleshooting skill. Filling in these gaps becomes easier the wider your knowledge becomes over time. + +There are some approaches to troubleshooting that are generally useful: + +- Defining the problem +- Understanding the request path +- Bisecting the problem space +- Generating hypotheses and proving or disproving them - this step is iterative, in other words, you keep doing this until you have found the problem + +### Defining the problem {#defining-the-problem} + +To troubleshoot a problem, you must be able to understand it and describe it. This is a starting point for discussion with technical and non-technical stakeholders. + +Begin your notes as you begin your investigation. Take lots of notes, and record each step you take in your journey towards understanding. If things get difficult, it’s valuable to have a record of what your theories were, how you tested them, and what happened. The first step is to make your first definition of the problem, which you will update very often. + +This step is necessary because problem reports are often vague and poorly-defined. It’s unusual for _reports_ of a problem to accurately _diagnose_ the problem. More often, they describe symptoms. Where reports do attribute causes, this is often without systematic reasoning and might be completely wrong. + +For example, a user might report that your website is slow. You verify this using your monitoring systems, which display graphs of page load times showing an increase in the past day. Based on your knowledge of the system, you know that slow requests can happen when the database is having issues. Using tooling (such as a [monitoring system](https://www.rabbitmq.com/monitoring.html), or[ logging slow queries](https://severalnines.com/blog/how-identify-postgresql-performance-issues-slow-queries/)) you can verify whether the database is indeed having performance issues, and from this you have your base definition of the problem. You can share this, and use it to build upon. We still do not know \_why \_the database latency is high, but it is a specific problem that we can investigate. + +Often we do not know the actual cause of the problem, we just see the impact on other systems. It is like a rock thrown into a lake. The rock is out of sight, but we can still see its impact, as ripples on the water. The ripples help us guess the size and location of the rock in the lake. + +- What do we already know about the issue? +- Can we describe the problem to others? +- Can we find proof that validates our assumptions? +- Can we reproduce the issue? +- What are the effects of the issue? +- Can we see the effects in a graph somewhere? +- Is it affecting all or only some operations? + +It can be difficult to untangle cause and effect in computer systems. A database seeming slow may be an effect \_caused \_by additional load exhausting database resources. Another commonly-observed example is systems experiencing spikes in the number of incoming requests due to retries when there are problems with an application. If an application is very slow or serving errors, clients and users will naturally retry, creating unusually high load on the system. The additional load may cause further problems; but some other problem has triggered the application’s incorrect behaviour. + +### Understanding the request path {#understanding-the-request-path} + +Often the first challenge in any troubleshooting exercise is to figure out what is supposed to be happening in the system that is exhibiting a fault. + +First figure out what is _meant_ to be happening, and then determine what is _actually_ happening. + +Like when you order food, and the driver turns up at the wrong address. Where in the flow did things divert from what was expected? + +- Was the wrong address provided? +- Was the address read incorrectly? +- Are there two identically named streets? +- Has GPS broken? + +Making a mental model of the request path helps you navigate through the issue and narrow down the component(s) in the request path that might be having issues. It can be helpful to sketch an actual diagram on paper to help you build your mental model. + +Depending on the situation, there may be documentation that can help you understand the intended operation of your system and build that mental model. Open-source software often comes with extensive manuals. In-house developed systems may also have documentation. However, often you need to use troubleshooting tools and experimentation to understand what is happening (there is a short catalogue of useful tools in a later section). + +### Finding the cause of the problem: generating and validating hypotheses {#finding-the-cause-of-the-problem-generating-and-validating-hypotheses} + +Once we determine and observe that there is a problem, we have supporting evidence to say that the [happy path](https://en.wikipedia.org/wiki/Happy_path) is not being taken. However, we still do not understand the cause, or how to fix it. + +Once we believe that we know what might be causing the problem, we now have a hypothesis. We want to use our skills, or the skills of others, to verify that what we think is the problem, is actually the problem. With this we can mitigate or fix the problem. + +#### Looking for causes, or “what happened earlier” {#looking-for-causes-or-“what-happened-earlier”} + +When a problem has a distinct ‘starting time’, it can be worth checking for changes or events that occurred at that time. Not all problems are a consequence of recent events or changes - sometimes latent problems in systems that have existed for years can be triggered. However, changes (such as deployments and configuration updates) are the most common cause of problems in software systems, so recent changes and events should not be ignored. + +For example, we have a graph that lines up with us getting alerted at 13:00, but we can see that the graph started getting worse at 12:00. If you leave a tap running, it isn’t a problem at first, but eventually the sink overflows. The alert is when the sink overflows, the _cause_ is leaving the tap running. + +So when we have a better understanding of when things started to get worse, we need to see if anything changed around the same time. + +- Did a deploy go out? +- Was a feature flag changed? +- Was there a sudden increase in traffic? +- Did a server vanish? + +We can see that a deployment went out at 12:00. This feels like a hypothesis to dig into. + +#### Examining possible causes {#examining-possible-causes} + +So now we can find the changes that were included in that deployment. Either ourselves or a subject matter expert can help confirm that the changes might be related to the problem. + +#### Testing solutions {#testing-solutions} + +If we believe that the recent change might be the problem, then we may be able to revert the change. In stateless systems reverting recent changes is generally low-risk. Reverting changes to stateful systems must only be done with great care: an older codebase may not be able to read data written by a newer version of the system and data loss or corruption may result. However, in most production software systems, the great majority of changes that are made are safe to revert, and it is often the quickest way to fix a problem. + +If reverting a recent change makes the problem go away, then it is a very strong signal that those changes were indeed the cause of the issue (although coincidences do occur). It does not explicitly prove or disprove our hypothesis that the recent change was the problem. + +The next step is to examine those changes more closely and determine exactly how they caused the alert, which would definitively prove the hypothesis. + +It is often easier to disprove a hypothesis than to prove it. For example, if one of the changes in the recent deployment (that we just rolled back) introduced a dependency on a new service, and we hypothesise that this new service’s performance might be the cause of the problem, we can inspect the monitoring dashboard for that microservice. If the monitoring shows that the new service is performing well (low latency and low error rates) then the hypothesis would be disproved. We can then generate new hypotheses and try to [falsify](https://en.wikipedia.org/wiki/Falsifiability) those. + +### Finding problems by iteratively reducing the possibility space {#finding-problems-by-iteratively-reducing-the-possibility-space} + +It is not always possible to track down problems by taking the fast path of looking for recent breaking changes. In this case, we need to use our knowledge of the system (including information that we gain by using debugging tools) to zero in on parts of our system that are not behaving as they should be. + +_All swans are white vs no swan is black_ + +It is more efficient to find a way to disprove your hypothesis or falsify your proposition, if you can. This is because you only need to disprove something once to discard it, but you may _apparently_ verify a hypothesis many times in many different ways and still be wrong. + +Every time we disprove a hypothesis we reduce the scope of our problem, which is helpful. We close in on something. + +For example, let us imagine that we are troubleshooting an issue in a system that sends notifications to users at a specific times. We know that the system works as follows: + +1. The user configures a notification using a web service +2. The notification configuration is written into a database +3. A scheduler reads from that database and then sends a push notification to the user’s device + +In this situation, the problem could be in any of these steps. Perhaps: + +1. The web service silently failed to write to the database for some reason +2. The database lost data due to a hardware failure +3. The scheduler had a bug and failed to operate correctly +4. The push notification could not be sent to the user for some reason; perhaps network-related + +A good place to start would be by checking the scheduler program’s logs to determine whether it did attempt to send the notification or not. In order to do this, you would need some specifics about the request, such as a user ID, in order to identify the correct log lines. + +Finding the log line (or not finding it) will tell you if the scheduler attempted to send the notification. If the scheduler did attempt to send the notification, then it eliminates database write failures, data loss, and some types of scheduler bug from your search, and indicates that you should focus on what happens on the part of the request path involving the scheduler sending the notification. You may find error details in the scheduler log lines. + +If you do not find a log line for the specific request in question - and you do see log lines relating to other notifications around that same time - then it should signal you to examine the first three steps in the process and iterate. Checking whether the notification configuration is present in the database would further reduce the space of possible problems. + +### USE Method {#use-method} + +[Brendan Gregg’s USE](https://www.brendangregg.com/usemethod.html) (Utilisation, Saturation, Errors) method is particularly helpful for troubleshooting performance problems. Slowness and performance problems can be more difficult to debug than full breakage of some component, because when something is completely down (unresponsive and not serving requests) it’s generally more obvious than something being slow. + +Performance problems are generally a result either of some component in the system being highly utilised or saturated, or of errors somewhere that are going unnoticed. + +#### Bottlenecks {#bottlenecks} + +Imagine a wine bottle and a wide tumbler of water, both holding the same amount of water. Turn the bottle and the glass upside down. The water in the glass falls at once. The water in the bottle empties more slowly. It sits above the narrow neck of the bottle; the speed of the pour is limited by the capacity of the bottle neck. + +When a component - either physical, such as CPU or network, or logical, such as locks or cloud API quota - is too highly utilised, other parts of the system will end up waiting for the heavily-loaded component. This occurs because of queuing: when a system is under very heavy load, requests cannot usually be served quickly and on average, will have to wait. The heavily loaded component is known as a bottleneck. + +The performance problem then tends to spread beyond the original bottleneck. The clients of the bottleneck system serve requests more slowly (because they are waiting for the bottleneck system), and this in turn affects clients of those systems. This is why heavy utilisation and saturation are very important signals in your systems. + +## Scenarios {#scenarios} + +### Slack Client Crashes {#slack-client-crashes} + +Let’s look at [Slack's Secret STDERR Messages](https://brendangregg.com/blog/2021-08-27/slack-crashes-secret-stderr.html) by Brendan Gregg. This is a great piece not only because of Gregg’s expertise but because of how clearly Gregg describes his process. + +Here, Gregg is attempting to troubleshoot why his Slack client seems to be crashing. He doesn’t know much about the inner workings of the software, and he doesn’t have the code, so he has to treat it as a black box. However, in this case, he does have a fairly clear probable location of the fault: the Slack program itself. + +He knows that the program is exiting, so he starts there. He has a false start looking for a [core dump](https://linux-audit.com/understand-and-configure-core-dumps-work-on-linux/) to attach a debugger to, but this doesn’t work out so he moves on to try a tool called [exitsnoop](https://manpages.ubuntu.com/manpages/jammy/en/man8/exitsnoop-bpfcc.8.html). Exitsnoop is an eBBF-based tool that traces process termination. Gregg finds that the Slack client is exiting because it receives a SIGABRT signal (which is generally handled by termination). + +Gregg still doesn’t know why the program is receiving this signal. He tries some more tracing tools - trying to get a stack trace - but draws a blank. He moves on to looking for logs instead. + +He has no idea where Slack logs to, so he uses the lsof tool. Specifically, he runs + +``` +lsof -p `pgrep -n slack` | grep -i log +``` + +This command line does the following: + +1. pgrep -n slack: find the process ID of the most recently started slack process. +2. `pgrep -n slack`: the use of backticks in a command line means ‘run the commands in the backticks first, and then substitute the result +3. lsof -p `pgrep -n slack`: this runs lsof -p <PID of the most recently running slack process>. lsof with the ‘-p’ flag lists all the open files that belong to the given PID. +4. grep -i log: this searches for the word ‘log’ in the text that the command is given. The ‘-i’ flag just makes it case insensitive. +5. |: this is the pipe symbol. It takes the output of the commands to the left, and send it as input to the command on the right. + +The overall command line searches for any file that the most recently started slack process has open, with the word ‘log’ (case insensitive) in the name. + +This sort of command line is a result of the [UNIX tools philosophy](https://www.linuxtopia.org/online_books/gnu_linux_tools_guide/the-unix-tools-philosophy.html): commandline tools should be flexible and composable using mechanisms like the pipe (|). This also means that in a Linux/UNIX system, there are often many ways to achieve the same result. Likewise, Linux largely makes system state available to administrators. System state is all the state of the running kernel and its resources - such as which files are being held open by which process, which TCP/IP sockets are listening, which processes are running, and so forth. This state is generally exposed via the [/proc filesystem](https://www.kernel.org/doc/html/latest/filesystems/proc.html), as well as a variety of commandline tools. + +Gregg doesn’t find any log files, but he realises that the logs might still exist, having been opened by another slack process. He tries a program called [pstree](https://man7.org/linux/man-pages/man1/pstree.1.html) which displays the entire tree of slack processes. It turns out that there are a number of slack processes, and he tries lsof again with the oldest. This time he finds log files. + +Gregg is an expert troubleshooter, but we see that he attempts and abandons several methods for understanding the reason for this program’s failure. This is fairly typical of troubleshooting, unfortunately. He is also using his knowledge of core Linux concepts - processes, open files - to increase his knowledge about how the slack program works. + +Once more, Gregg finds that the slack program logs don’t reveal the reason for the program’s crashing. However, he notices that the [stderr stream](https://www.howtogeek.com/435903/what-are-stdin-stdout-and-stderr-on-linux/) isn’t present in the log files. + +Gregg knows of another tool, shellsnoop, that he uses to read the stderr stream. + +Here he finds an error: + +``` +/snap/slack/42/usr/lib/x86_64-linux-gnu/gdk-pixbuf-2.0/2.10.0/loaders/libpixbufloader-png.so: cannot open shared object file: No such file or directory (gdk-pixbuf-error-quark, 5) +``` + +This error log indicates that the slack process tried to dynamically load a [shared code module](https://programs.wiki/wiki/linux-system-programming-static-and-dynamic-libraries.html) that didn’t exist. Resolving that issue resolves the failures. + +Gregg concludes by pointing out that the tool opensnoop could have led him straight to this answer; but of course, that’s much easier to know in hindsight. + +### Broken Load Balancers {#broken-load-balancers} + +Let’s look at the [broken loadbalancer scenario](https://hostedgraphite1.wordpress.com/2018/03/01/spooky-action-at-a-distance-how-an-aws-outage-ate-our-load-balancer/) from Hosted Graphite. + +On a Friday night, Hosted Graphite engineers start receiving reports of trouble from their monitoring indicating that their website was intermittently becoming unavailable. Their periodic checks to the website and graphs rendering API were both intermittently timing out. + +They begin to gather more information to find out what might be happening. They note an + +overall drop in all traffic across all their ingestion endpoints which suggested there might + +be a network connectivity issue at play. When looking through the impact from canaries vs HTTP API endpoint traffic, they notice that canaries ingestion traffic is affected only in certain AWS regions but the HTTP API ingestion is affected regardless of the location. To add further confusion, some of their internal services also start to report timeouts. + +There are conflicting information but all of the events indicate an AWS connectivity issue + +and they decide to follow it through. + +Digging further, they realise that the internal services having issues are relying on S3 (another AWS service) and that their AWS dependent integrations are also severely impacted. At this point AWS status page is reporting connectivity issues both in us-east-1 and us-west-2 region which is even more confusing as they cannot comprehend "_how_" an AWS outage could affect how they serve their website when it’s neither hosted on AWS nor physically located anywhere near the affected regions. + +**One of the hardest problems during any incident is differentiating cause(s) from symptoms.** + +So they start looking into the only service they are using which was hosted on AWS, [Route53 health checks](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/welcome-health-checks.html) for their own (self-hosted) load balancers. These Route53 health checks were configured to ensure that traffic was only routed to healthy load balancers to server production traffic and unhealthy ones were removed from the DNS entry. The health check logs indicate failures from many locations. They don’t know if this was a cause or a symptom, so they disable the route53 health checks, to either confirm or rule out that theory. Unfortunately, disabling the health checks didn’t resolve the issue so they continue digging for clues. + +It is at this point, the AWS incident gets resolved and they notice that their traffic rate starts to recover with it, further confirming that the AWS outage was the trigger for this incident. Now they know what happened but not why. + +They find two visualisations from their load balancing logs which help them paint a clear + +picture of what happened. The first graph shows a sharp rise in the active connections through their load balancing tier, followed by a flat line exactly during the time period of most impact and then a decline towards the end of the incident. This explains the SSL handshake woes they noticed as any new connection won't be accepted by their load balancers once + +the maximum connection limit was reached. + +This still doesn't explain where these connections were originating from as they didn't see any increase in the number of requests received. This is where the second visualisation comes into picture. This graph shows the average time it took for hosts from different ISPs to send a full request over time and the top row represents requests from AWS hosts. These requests were taking up to 5 seconds to make a full request while other ISPs remained largely unaffected. At this point they finally crack the case. + +The AWS hosts from the affected regions were experiencing connectivity issues which significantly slowed down their connections to the load balancers. As a result, these hosts were hogging all the available connections until they hit a connection limit in their load balancers, causing hosts in other locations to be unable to create a new connection. + +## Tools {#tools} + +There is no exhaustive list of troubleshooting tools. We use whatever is both available and best-suited for the problem at hand. Sometimes that’s a general-purpose tool – like Linux OS-level tooling or TCP packet dumps – and sometimes it’s something system-specific, like application-specific counters, or logging. + +In many cases, Google is your friend. It is a starting point for understanding what an error message may mean and what may have caused the error. It is also a good way to find new observability tools (e.g. searching for things like ‘linux how to debug full disk’ will generally throw up some tutorials). + +However: do not spend too long trying to find information in this way. Google is your friend, but it is not your only friend. It is easy to fall into a rabbit hole and lose your entire day to Googling, so give yourself some time limits. Set an alarm for 90 minutes. Write up your journey with the problem so far and take it to a more senior engineer. They will appreciate the work you have already done on describing and exploring the problem. + +System-specific monitoring can help you: does the system you are investigating export metrics (statistics exposed by a software program for purposes of analysis and alerting)? Does it have a status page, or a command-line tool that you can use to understand its state? + +Examples: + +- [https://www.haproxy.com/blog/exploring-the-haproxy-stats-page/](https://www.haproxy.com/blog/exploring-the-haproxy-stats-page/) +- [https://vitess.io/docs/13.0/user-guides/configuration-basic/monitoring/](https://vitess.io/docs/13.0/user-guides/configuration-basic/monitoring/) +- You many have system-specific dashboards built in Grafana, Prometheus, a SaaS provider like Honeycomb or Datadog, or another monitoring tool + +Loadbalancers and datastore statistics are particularly useful - these can often help you to determine whether problems exist upstream (towards backends or servers) or downstream (towards frontends, or clients) of your loadbalancers/datastores. Understanding this can help you narrow down the scope of your investigations. + +Logs are also a great source of information, although they can also be a source of plentiful red herrings. Many organisations use a tool such as Splunk or ELK to centralise logs for searching and analysis. + +There is a fairly long list of tools below. You don’t need to be an expert in all of these, but it is worth knowing that they exist, what they do in broad strokes, and where to get more information (man pages, google). Try running all of these + +You should be familiar with basic Linux tooling such as: + +- [perf](https://www.brendangregg.com/perf.html) +- [strace](https://man7.org/linux/man-pages/man1/strace.1.html) +- ltrace +- top, htop +- sar +- netstat +- lsof +- kill +- df, du, iotop +- ps, pstree +- the[ /proc/](https://www.kernel.org/doc/html/latest/filesystems/proc.html) filesystem +- dmesg, location of system logfiles - generally /var/syslog/, journalctl +- tools like cat, less, grep, sed, and awk are invaluable for working with lengthy logs or text output from tools +- jq is useful for parsing and formatting JSON + +For debugging network or connectivity issues, you should know tools like: + +- dig (for DNS) +- traceroute +- tcpdump and wireshark +- netcat + +[curl](https://curl.se/docs/manpage.html) is invaluable for reproducing and understanding problems with HTTP servers, including issues with certificates. + +[Man pages](https://www.kernel.org/doc/man-pages/) can be super useful when you are looking for more information and available options for any of the common Linux tools. + +[eBPF ](https://ebpf.io/)is a newer technology that lets you insert traces into the Linux OS, making everything visible. A lot of observability tools use eBPF under the hood, or you can write your own eBPF programs. + +# Related Reading {#related-reading} + +[https://github.com/iovisor/bcc/blob/master/docs/tutorial.md](https://github.com/iovisor/bcc/blob/master/docs/tutorial.md) + +[https://netflixtechblog.com/linux-performance-analysis-in-60-000-milliseconds-accc10403c55](https://netflixtechblog.com/linux-performance-analysis-in-60-000-milliseconds-accc10403c55) + +[https://linuxtect.com/linux-strace-command-tutorial/](https://linuxtect.com/linux-strace-command-tutorial/) + +[https://www.brendangregg.com/perf.html](https://www.brendangregg.com/perf.html) + +[https://danielmiessler.com/study/tcpdump/](https://danielmiessler.com/study/tcpdump/) + +[https://www.lifewire.com/wireshark-tutorial-4143298](https://www.lifewire.com/wireshark-tutorial-4143298)