📄 Page
1
Brendan Burns Designing Distributed Systems PATTERNS AND PARADIGMS FOR SCALABLE, RELIABLE SERVICES
📄 Page
2
(This page has no text content)
📄 Page
3
Brendan Burns Designing Distributed Systems Patterns and Paradigms for Scalable, Reliable Services Boston Farnham Sebastopol TokyoBeijing
📄 Page
4
978-1-491-98364-5 [LSI] Designing Distributed Systems by Brendan Burns Copyright © 2018 Brendan Burns. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com/safari). For more information, contact our corporate/insti‐ tutional sales department: 800-998-9938 or corporate@oreilly.com. Editor: Angela Rufino Production Editor: Colleen Cole Copyeditor: Gillian McGarvey Proofreader: Christina Edwards Indexer: WordCo Indexing Services, Inc. Interior Designer: David Futato Cover Designer: Randy Comer Illustrator: Rebecca Demarest February 2018: First Edition Revision History for the First Edition 2018-02-20: First Release See http://oreilly.com/catalog/errata.csp?isbn=9781491983645 for release details. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Designing Distributed Systems, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. While the publisher and the author have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the author disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.
📄 Page
5
Table of Contents Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii 1. Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 A Brief History of Systems Development 1 A Brief History of Patterns in Software Development 2 Formalization of Algorithmic Programming 3 Patterns for Object-Oriented Programming 3 The Rise of Open Source Software 3 The Value of Patterns, Practices, and Components 4 Standing on the Shoulders of Giants 4 A Shared Language for Discussing Our Practice 5 Shared Components for Easy Reuse 5 Summary 6 Part I. Single-Node Patterns Motivations 7 Summary 8 2. The Sidecar Pattern. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 An Example Sidecar: Adding HTTPS to a Legacy Service 11 Dynamic Configuration with Sidecars 12 Modular Application Containers 14 Hands On: Deploying the topz Container 14 Building a Simple PaaS with Sidecars 15 Designing Sidecars for Modularity and Reusability 16 Parameterized Containers 17 Define Each Container’s API 17 iii
📄 Page
6
Documenting Your Containers 18 Summary 19 3. Ambassadors. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Using an Ambassador to Shard a Service 22 Hands On: Implementing a Sharded Redis 23 Using an Ambassador for Service Brokering 25 Using an Ambassador to Do Experimentation or Request Splitting 26 Hands On: Implementing 10% Experiments 27 4. Adapters. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 Monitoring 32 Hands On: Using Prometheus for Monitoring 33 Logging 34 Hands On: Normalizing Different Logging Formats with Fluentd 35 Adding a Health Monitor 36 Hands On: Adding Rich Health Monitoring for MySQL 37 Part II. Serving Patterns Introduction to Microservices 41 5. Replicated Load-Balanced Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Stateless Services 45 Readiness Probes for Load Balancing 46 Hands On: Creating a Replicated Service in Kubernetes 47 Session Tracked Services 48 Application-Layer Replicated Services 49 Introducing a Caching Layer 49 Deploying Your Cache 50 Hands On: Deploying the Caching Layer 51 Expanding the Caching Layer 53 Rate Limiting and Denial-of-Service Defense 54 SSL Termination 54 Hands On: Deploying nginx and SSL Termination 55 Summary 57 6. Sharded Services. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 Sharded Caching 59 Why You Might Need a Sharded Cache 60 The Role of the Cache in System Performance 61 Replicated, Sharded Caches 62 iv | Table of Contents
📄 Page
7
Hands On: Deploying an Ambassador and Memcache for a Sharded Cache 63 An Examination of Sharding Functions 66 Selecting a Key 67 Consistent Hashing Functions 68 Hands On: Building a Consistent HTTP Sharding Proxy 69 Sharded, Replicated Serving 70 Hot Sharding Systems 70 7. Scatter/Gather. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Scatter/Gather with Root Distribution 74 Hands On: Distributed Document Search 75 Scatter/Gather with Leaf Sharding 76 Hands On: Sharded Document Search 77 Choosing the Right Number of Leaves 78 Scaling Scatter/Gather for Reliability and Scale 79 8. Functions and Event-Driven Processing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81 Determining When FaaS Makes Sense 82 The Benefits of FaaS 82 The Challenges of FaaS 82 The Need for Background Processing 83 The Need to Hold Data in Memory 83 The Costs of Sustained Request-Based Processing 84 Patterns for FaaS 84 The Decorator Pattern: Request or Response Transformation 85 Hands On: Adding Request Defaulting Prior to Request Processing 86 Handling Events 87 Hands On: Implementing Two-Factor Authentication 87 Event-Based Pipelines 89 Hands On: Implementing a Pipeline for New-User Signup 89 9. Ownership Election. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Determining If You Even Need Master Election 94 The Basics of Master Election 95 Hands On: Deploying etcd 97 Implementing Locks 98 Hands On: Implementing Locks in etcd 100 Implementing Ownership 101 Hands On: Implementing Leases in etcd 102 Handling Concurrent Data Manipulation 103 Table of Contents | v
📄 Page
8
Part III. Batch Computational Patterns 10. Work Queue Systems. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 A Generic Work Queue System 109 The Source Container Interface 110 The Worker Container Interface 112 The Shared Work Queue Infrastructure 113 Hands On: Implementing a Video Thumbnailer 115 Dynamic Scaling of the Workers 117 The Multi-Worker Pattern 118 11. Event-Driven Batch Processing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121 Patterns of Event-Driven Processing 122 Copier 122 Filter 123 Splitter 124 Sharder 125 Merger 127 Hands On: Building an Event-Driven Flow for New User Sign-Up 128 Publisher/Subscriber Infrastructure 129 Hands On: Deploying Kafka 130 12. Coordinated Batch Processing. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133 Join (or Barrier Synchronization) 134 Reduce 135 Hands On: Count 136 Sum 137 Histogram 137 Hands On: An Image Tagging and Processing Pipeline 138 13. Conclusion: A New Beginning?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 143 Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 vi | Table of Contents
📄 Page
9
Preface Who Should Read This Book At this point, nearly every developer is a developer or consumer (or both) of dis‐ tributed systems. Even relatively simple mobile applications are backed with cloud APIs so that their data can be present on whatever device the customer happens to be using. Whether you are new to developing distributed systems or an expert with scars on your hands to prove it, the patterns and components described in this book can transform your development of distributed systems from art to science. Reusable components and patterns for distributed systems will enable you to focus on the core details of your application. This book will help any developer become better, faster, and more efficient at building distributed systems. Why I Wrote This Book Throughout my career as a developer of a variety of software systems from web search to the cloud, I have built a large number of scalable, reliable distributed sys‐ tems. Each of these systems was, by and large, built from scratch. In general, this is true of all distributed applications. Despite having many of the same concepts and even at times nearly identical logic, the ability to apply patterns or reuse components is often very, very challenging. This forced me to waste time reimplementing systems, and each system ended up less polished than it might have otherwise been. The recent introduction of containers and container orchestrators fundamentally changed the landscape of distributed system development. Suddenly we have an object and interface for expressing core distributed system patterns and building reusable containerized components. I wrote this book to bring together all of the practitioners of distributed systems, giving us a shared language and common stan‐ dard library so that we can all build better systems more quickly. vii
📄 Page
10
The World of Distributed Systems Today Once upon a time, people wrote programs that ran on one machine and were also accessed from that machine. The world has changed. Now, nearly every application is a distributed system running on multiple machines and accessed by multiple users from all over the world. Despite their prevalence, the design and development of these systems is often a black art practiced by a select group of wizards. But as with everything in technology, the world of distributed systems is advancing, regularizing, and abstracting. In this book I capture a collection of repeatable, generic patterns that can make the development of reliable distributed systems more approachable and efficient. The adoption of patterns and reusable components frees developers from reimplementing the same systems over and over again. This time is then freed to focus on building the core application itself. Navigating This Book This book is organized into a 4 parts as follows: Chapter 1, Introduction Introduces distributed systems and explains why patterns and reusable compo‐ nents can make such a difference in the rapid development of reliable distributed systems. Part I, Single-Node Patterns Chapters 2 through 4 discuss reusable patterns and components that occur on individual nodes within a distributed system. It covers the side-car, adapter, and ambassador single-node patterns. Part II, Serving Patterns Chapters 8 and 9 cover multi-node distributed patterns for long-running serving systems like web applications. Patterns for replicating, scaling, and master elec‐ tion are discussed. Part III, Batch Computational Patterns Chapters 10 through 12 cover distributed system patterns for large-scale batch data processing covering work queues, event-based processing, and coordinated workflows. If you are an experienced distributed systems engineer, you can likely skip the first couple of chapters, though you may want to skim them to understand how we expect these patterns to be applied and why we think the general notion of distributed sys‐ tem patterns is so important. Everyone will likely find utility in the single-node patterns as they are the most generic and most reusable patterns in the book. viii | Preface
📄 Page
11
Depending on your goals and the systems you are interested in developing, you can choose to focus on either large-scale big data patterns, or patterns for long-running servers (or both). The two parts are largely independent from each other and can be read in any order. Likewise, if you have extensive distributed system experience, you may find that some of the early patterns chapters (e.g., Part II on naming, discovery, and load balancing) are redundant with what you already know, so feel free to skim through to gain the high-level insights—but don’t forget to look at all of the pretty pictures! Conventions Used in This Book The following typographical conventions are used in this book: Italic Indicates new terms, URLs, email addresses, filenames, and file extensions. Constant width Used for program listings, as well as within paragraphs to refer to program ele‐ ments such as variable or function names, databases, data types, environment variables, statements, and keywords. Constant width bold Shows commands or other text that should be typed literally by the user. Constant width italic Shows text that should be replaced with user-supplied values or by values deter‐ mined by context. This icon signifies a tip, suggestion, or general note. This icon indicates a warning or caution. Online Resources Though this book describes generally applicable distributed system patterns, it expects that readers are familiar with containers and container orchestration systems. Preface | ix
📄 Page
12
If you don’t have a lot of pre-existing knowledge about these things, we recommend the following resources: • https://docker.io • https://kubernetes.io • https://dcos.io Using Code Examples Supplemental material (code examples, exercises, etc.) is available for download at https://github.com/brendandburns/designing-distributed-systems. This book is here to help you get your job done. In general, if example code is offered with this book, you may use it in your programs and documentation. You do not need to contact us for permission unless you’re reproducing a significant portion of the code. For example, writing a program that uses several chunks of code from this book does not require permission. Selling or distributing a CD-ROM of examples from O’Reilly books does require permission. Answering a question by citing this book and quoting example code does not require permission. Incorporating a signifi‐ cant amount of example code from this book into your product’s documentation does require permission. We appreciate, but do not require, attribution. An attribution usually includes the title, author, publisher, and ISBN. For example: “Designing Distributed Systems by Brendan Burns (O’Reilly). Copyright 2018 Brendan Burns, 978-1-491-98364-5.” If you feel your use of code examples falls outside fair use or the permission given above, feel free to contact us at permissions@oreilly.com. O’Reilly Safari Safari (formerly Safari Books Online) is a membership-based training and reference platform for enterprise, government, educators, and individuals. Members have access to thousands of books, training videos, Learning Paths, interac‐ tive tutorials, and curated playlists from over 250 publishers, including O’Reilly Media, Harvard Business Review, Prentice Hall Professional, Addison-Wesley Profes‐ sional, Microsoft Press, Sams, Que, Peachpit Press, Adobe, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kaufmann, IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders, McGraw-Hill, Jones & Bartlett, and Course Technology, among others. x | Preface
📄 Page
13
For more information, please visit http://oreilly.com/safari. How to Contact Us Please address comments and questions concerning this book to the publisher: O’Reilly Media, Inc. 1005 Gravenstein Highway North Sebastopol, CA 95472 800-998-9938 (in the United States or Canada) 707-829-0515 (international or local) 707-829-0104 (fax) We have a web page for this book, where we list errata, examples, and any additional information. You can access this page at http://bit.ly/designing-distributed-systems. To comment or ask technical questions about this book, send email to bookques‐ tions@oreilly.com. For more information about our books, courses, conferences, and news, see our web‐ site at http://www.oreilly.com. Find us on Facebook: http://facebook.com/oreilly Follow us on Twitter: http://twitter.com/oreillymedia Watch us on YouTube: http://www.youtube.com/oreillymedia Acknowledgments I’d like to thank my wife Robin and my children for everything they do to keep me happy and sane. To all of the people along the way who took the time to help me learn all of these things, many thanks! Also thanks to my parents for that first SE/30. Preface | xi
📄 Page
14
(This page has no text content)
📄 Page
15
CHAPTER 1 Introduction Today’s world of always-on applications and APIs have availability and reliability requirements that would have been required of only a handful of mission critical services around the globe only a few decades ago. Likewise, the potential for rapid, viral growth of a service means that every application has to be built to scale nearly instantly in response to user demand. These constraints and requirements mean that almost every application that is built—whether it is a consumer mobile app or a back‐ end payments application—needs to be a distributed system. But building distributed systems is challenging. Often, they are one-off bespoke solu‐ tions. In this way, distributed system development bears a striking resemblance to the world of software development prior to the development of modern object-oriented programming languages. Fortunately, as with the development of object-oriented lan‐ guages, there have been technological advances that have dramatically reduced the challenges of building distributed systems. In this case, it is the rising popularity of containers and container orchestrators. As with the concept of objects within object- oriented programming, these containerized building blocks are the basis for the development of reusable components and patterns that dramatically simplify and make accessible the practices of building reliable distributed systems. In the following introduction, we give a brief history of the developments that have led to where we are today. A Brief History of Systems Development In the beginning, there were machines built for specific purposes, such as calculating artillery tables or the tides, breaking codes, or other precise, complicated but rote mathematical applications. Eventually these purpose-built machines evolved into general-purpose programmable machines. And eventually they evolved from running 1
📄 Page
16
one program at a time to running multiple programs on a single machine via time- sharing operating systems, but these machines were still disjoint from each other. Gradually, machines came to be networked together, and client-server architectures were born so that a relatively low-powered machine on someone’s desk could be used to harness the greater power of a mainframe in another room or building. While this sort of client-server programming was somewhat more complicated than writing a program for a single machine, it was still fairly straightforward to understand. The client(s) made requests; the server(s) serviced those requests. In the early 2000s, the rise of the internet and large-scale datacenters consisting of thousands of relatively low-cost commodity computers networked together gave rise to the widespread development of distributed systems. Unlike client-server architec‐ tures, distributed system applications are made up of multiple different applications running on different machines, or many replicas running across different machines, all communicating together to implement a system like web-search or a retail sales platform. Because of their distributed nature, when structured properly, distributed systems are inherently more reliable. And when architected correctly, they can lead to much more scalable organizational models for the teams of software engineers that built these systems. Unfortunately, these advantages come at a cost. These distributed systems can be significantly more complicated to design, build, and debug correctly. The engi‐ neering skills needed to build a reliable distributed system are significantly higher than those needed to build single-machine applications like mobile or web frontends. Regardless, the need for reliable distributed systems only continues to grow. Thus there is a corresponding need for the tools, patterns, and practices for building them. Fortunately, technology has also increased the ease with which you can build dis‐ tributed systems. Containers, container images, and container orchestrators have all become popular in recent years because they are the foundation and building blocks for reliable distributed systems. Using containers and container orchestration as a foundation, we can establish a collection of patterns and reusable components. These patterns and components are a toolkit that we can use to build our systems more reli‐ ably and efficiently. A Brief History of Patterns in Software Development This is not the first time such a transformation has occurred in the software industry. For a better context on how patterns, practices, and reusable components have previ‐ ously reshaped systems development, it is helpful to look at past moments when simi‐ lar transformations have taken place. 2 | Chapter 1: Introduction
📄 Page
17
Formalization of Algorithmic Programming Though people had been programming for more than a decade before its publication in 1962, Donald Knuth’s collection, The Art of Computer Programming (Addison- Wesley Professional), marks an important chapter in the development of computer science. In particular, the books contain algorithms not designed for any specific computer, but rather to educate the reader on the algorithms themselves. These algo‐ rithms then could be adapted to the specific architecture of the machine being used or the specific problem that the reader was solving. This formalization was important because it provided users with a shared toolkit for building their programs, but also because it showed that there was a general-purpose concept that programmers should learn and then subsequently apply in a variety of different contexts. The algorithms themselves, independent of any specific problem to solve, were worth understanding for their own sake. Patterns for Object-Oriented Programming Knuth’s books represent an important landmark in the thinking about computer pro‐ gramming, and algorithms represent an important component in the development of computer programming. However, as the complexity of programs grew, and the number of people writing a single program grew from the single digits to the double digits and eventually to the thousands, it became clear that procedural programming languages and algorithms were insufficient for the tasks of modern-day program‐ ming. These changes in computer programming led to the development of object- oriented programming languages, which elevated data, reusability, and extensibility to peers of the algorithm in the development of computer programs. In response to these changes to computer programming, there were changes to the patterns and practices for programming as well. Throughout the early to mid-1990s, there was an explosion of books on patterns for object-oriented programming. The most famous of these is the “gang of four” book, Design Patterns: Elements of Reusable Object-Oriented Programming by Erich Gamma et al. (Addison-Wesley Professional). Design Patterns gave a common language and framework to the task of program‐ ming. It described a series of interface-based patterns that could be reused in a variety of contexts. Because of advances in object-oriented programming and specifically interfaces, these patterns could also be implemented as generic reusable libraries. These libraries could be written once by a community of developers and reused repeatedly, saving time and improving reliability. The Rise of Open Source Software Though the concept of developers sharing source code has been around nearly since the beginning of computing, and formal free software organizations have been in existence since the mid-1980s, the very late 1990s and the 2000s saw a dramatic A Brief History of Patterns in Software Development | 3
📄 Page
18
increase in the development and distribution of open source software. Though open source is only tangentially related to the development of patterns for distributed sys‐ tems, it is important in the sense that it was through the open source communities that it became increasingly clear that software development in general and distributed systems development in particular are community endeavors. It is important to note that all of the container technology that forms the foundation of the patterns described in this book has been developed and released as open source software. The value of patterns for both describing and improving the practice of distributed devel‐ opment is especially clear when you look at it from this community perspective. What is a pattern for a distributed system? There are plenty of instructions out there that will tell you how to install specific dis‐ tributed systems (such as a NoSQL database). Likewise, there are recipes for a specific collection of systems (like a MEAN stack). But when I speak of patterns, I’m referring to general blueprints for organizing distributed systems, without mandating any specific technology or application choices. The purpose of a pattern is to provide general advice or structure to guide your design. The hope is that such patterns will guide your thinking and also be generally applicable to a wide variety of applications and environments. The Value of Patterns, Practices, and Components Before spending any of your valuable time reading about a series of patterns that I claim will improve your development practices, teach you new skills, and—let’s face it —change your life, it’s reasonable to ask: “Why?” What is it about the design patterns and practices that can change the way that we design and build software? In this sec‐ tion, I’ll lay out the reasons I think this is an important topic, and hopefully convince you to stick with me for the rest of the book. Standing on the Shoulders of Giants As a starting point, the value that patterns for distributed systems offer is the oppor‐ tunity to figuratively stand on the shoulders of giants. It’s rarely the case that the problems we solve or the systems we build are truly unique. Ultimately, the combina‐ tion of pieces that we put together and the overall business model that the software enables may be something that the world has never seen before. But the way the sys‐ tem is built and the problems it encounters as it aspires to be reliable, agile, and scala‐ ble are not new. This, then, is the first value of patterns: they allow us to learn from the mistakes of others. Perhaps you have never built a distributed system before, or perhaps you have never built this type of distributed system. Rather than hoping that a colleague has some experience in this area or learning by making the same mistakes that others 4 | Chapter 1: Introduction
📄 Page
19
have already made, you can turn to patterns as your guide. Learning about patterns for distributed system development is the same as learning about any other best prac‐ tice in computer programming. It accelerates your ability to build software without requiring that you have direct experience with the systems, mistakes, and firsthand learning that led to the codification of the pattern in the first place. A Shared Language for Discussing Our Practice Learning about and accelerating our understanding of distributed systems is only the first value of having a shared set of patterns. Patterns have value even for experienced distributed system developers who already understand them well. Patterns provide a shared vocabulary that enables us to understand each other quickly. This understand‐ ing forms the basis for knowledge sharing and further learning. To better understand this, imagine that we both are using the same object to build our house. I call that object a “Foo” while you call that object a “Bar.” How long will we spend arguing about the value of a Foo versus that of a Bar, or trying to explain the differing properties of Foo and Bar until we figure out that we’re speaking about the same object? Only once we determine that Foo and Bar are the same can we truly start learning from each other’s experience. Without a common vocabulary, we waste time in arguments of “violent agreement” or in explaining concepts that others understand but know by another name. Conse‐ quently, another significant value of patterns is to provide a common set of names and definitions so that we don’t waste time worrying about naming, and instead get right down to discussing the details and implementation of the core concepts. I have seen this happen in my short time working on containers. Along the way, the notion of a sidecar container (described in Chapter 2 of this book) took hold within the container community. Because of this, we no longer have to spend time defining what it means to be a sidecar and can instead jump immediately to how the concept can be used to solve a particular problem. “If we just use a sidecar” … “Yeah, and I know just the container we can use for that.” This example leads to the third value of patterns: the construction of reusable components. Shared Components for Easy Reuse Beyond enabling people to learn from others and providing a shared vocabulary for discussing the art of building systems, patterns provide another important tool for computer programming: the ability to identify common components that can be implemented once. If we had to create all of the code that our programs use ourselves, we would never get done. Indeed, we would barely get started. Today, every system ever written stands on the shoulders of thousands if not hundreds of thousands of years of human The Value of Patterns, Practices, and Components | 5
📄 Page
20
effort. Code for operating systems, printer drivers, distributed databases, container runtimes, and container orchestrators—indeed, the entirety of applications that we build today are built with reusable shared libraries and components. Patterns are the basis for the definition and development of such reusable compo‐ nents. The formalization of algorithms led to reusable implementations of sorting and other canonical algorithms. The identification of interface-based patterns gave rise to a collection of generic, object-oriented libraries implementing those patterns. Identifying core patterns for distributed systems enables us to to build shared com‐ mon components. Implementing these patterns as container images with HTTP- based interfaces means they can be reused across many different programming languages. And, of course, building reusable components improves the quality of each component because the shared code base gets sufficient usage to identify bugs and weaknesses, and sufficient attention to ensure that they get fixed. Summary Distributed systems are required to implement the level of reliability, agility, and scale expected of modern computer programs. Distributed system design continues to be more of a black art practiced by wizards than a science applied by laypeople. The identification of common patterns and practices has regularized and improved the practice of algorithmic development and object-oriented programming. It is this book’s goal to do the same for distributed systems. Enjoy! 6 | Chapter 1: Introduction