Uber's Microservice Journey: A Four-Year Round Trip to Insanity
Why is this DesignPatterns Architecture meme funny?
Level 1: Too Many Cooks
Imagine you’re trying to cook a big pot of soup, but instead of one chef, you have one thousand chefs in the kitchen, each in charge of adding one tiny ingredient. One chef adds a pinch of salt, another chef throws in one piece of carrot, another is just responsible for a single potato slice, and so on. Sure, each chef has a very small, simple job, but coordinating all 1000 of them becomes a total nightmare. The soup might end up cold or way too salty because there were just too many people involved and nobody could keep track of what everyone was doing. Anybody watching this from the outside would immediately think, “That’s way too many cooks!” And indeed, after trying it, the head chef would come back and admit, “Yep, having that many cooks was a bad idea.” In this meme, Uber is like that head chef: they tried to split their software into an absurd number of tiny pieces (microservices), and later realized it was, well, insane. It’s funny because the outcome was so predictable – even kids can understand that if you have too many helpers at once, they might ruin the task instead of finishing it.
Level 2: Tiny Parts, Huge Headache
This meme highlights an important lesson in how to design software systems, using Uber’s story as an example. Uber’s engineers tried out a strategy called microservices, which means instead of one big application (called a monolith), you build lots of little applications that each do one specific thing. In practical terms, in 2016 Uber wasn’t running one giant Uber app on their servers – they were running thousands of tiny apps, each responsible for a different part of the overall service. For instance, one microservice might handle user profiles, another calculates fares, another handles payments, another finds drivers, another sends out notifications, and so on. These microservices all talk to each other over the network to work together and fulfill a user’s request (like getting you a ride). By doing this, Uber effectively turned its platform into a distributed system – meaning they had lots of separate programs on different computers, all communicating over the network (inside Uber’s data centers) to act as one big Uber application.
At first glance, microservices sound really clever. Each part of the software can be worked on independently by a separate team, and if you need to update or fix the payments system, you can redeploy just that one microservice without touching the others. In theory this can make development faster and scaling easier for a big company. That’s why around 2016 a lot of tech companies were breaking apart monoliths into microservices – it was the hot new approach, promising more flexibility and the ability for many teams to work in parallel without stepping on each other’s toes.
But the joke here is about overdoing that idea. Uber took it to an extreme – “thousands of microservices” is an almost unbelievable number. The tweet implies that back in 2016 Uber bragged about this fact, and outsiders immediately thought, “Uh, that sounds kind of crazy.” (It’s like telling people you disassembled your car’s engine into 1000 separate mini-engines; most folks would respond, “Are you sure that’s a good idea?!”) Fast forward to 2020, and even Uber admits it was indeed too much. The phrase “architectural overkill” in the title of the meme means the way they structured their system was unnecessarily complex for what they needed. In plainer terms, Uber realized they didn’t actually need that many separate services, and having so many was making everything harder to manage rather than easier.
Why would having thousands of microservices be hard to manage? Imagine you’re a new developer at Uber and something goes wrong in the app – say a ride request is failing for some reason. You’d have to figure out which of the many little services in the chain is causing the problem. A single Uber request might pass through dozens of these microservices (for example: a request hits the “Ride Request” service, which calls the “Pricing” service, which calls the “Payments” service, then the “Notifications” service, and so on). If any one of those is slow or broken, the whole chain breaks down. Hunting down the problem becomes like following a trail of breadcrumbs through a forest: you have to check the logs and status of each service one by one to find where things went wrong. In a traditional single-app (monolithic) system, all the logic is in one place, so it can be easier to trace the issue. With microservices, the information is scattered, and that debugging process turns into a huge headache when the number of services is so high.
There’s also a lot of overhead introduced by running so many separate pieces. Overhead here means extra work and complexity that comes with the approach. For microservices, that includes things like making sure all these services can find and talk to each other (you need a way for Service A to locate Service B on the network), handling network issues (what if one service is down or slow? you need timeouts and retries), and deploying updates to each service. Uber had to build a ton of infrastructure to support their microservices. Think about DevOps (development operations) tasks: instead of deploying one big application, they had to deploy and manage 1000+ smaller applications. Each one needed monitoring (is it up? is it throwing errors?), scaling (maybe some services needed more servers to handle load), and security considerations. That’s a massive effort. It’s a bit like the difference between maintaining one big machine versus maintaining a thousand little machines – keeping track of all the parts, oiling them, fixing them, it becomes a very complex job.
We use the term technical debt to describe the accumulated problems that result from quick fixes or design choices that trade short-term gains for long-term pains. Here, having so many microservices became a kind of technical debt for Uber. It was initially done to improve things (scale and speed of development), but it introduced new problems that engineers later had to address, essentially “paying back” that debt with a lot of maintenance work. And over-engineering means designing a solution that's far more complicated than necessary. In hindsight, Uber over-engineered their system by slicing it into so many pieces. They likely solved problems that didn’t exist yet or went with an ultra-granular design because it was the trendy thing to do, and later realized most of those tiny services weren’t worth the trouble they caused.
In simple terms, this meme is a reality check about architecture trade-offs. Microservices can be powerful and useful, but if you push the idea too far, the complexity can overwhelm the benefits. Uber’s case shows that splitting an app into too many parts can actually slow you down and make development and troubleshooting harder – the exact opposite of what was intended. It’s a bit ironic: the technique that was supposed to help them handle growth and scale ended up creating its own new kind of scaling problem (scaling the complexity of the system). The punchline is basically, “Everyone said it was crazy to have that many microservices, and they were right – even Uber had to admit it later.” The lesson for developers is to be moderate and thoughtful with architecture. Use microservices when they make sense, but don’t chop things into tiny pieces just for bragging rights. Sometimes, keeping it simpler (with a monolith or just a reasonable number of services) makes life much easier in the long run.
Level 3: Spaghetti at Scale
Uber in 2016: "We have thousands of microservices."
Everyone: "That sounds insane."
Uber in 2020: "It turns out that was insane."
This tweet-style meme nails a shared industry saga: the rise and stumble of extreme microservice sprawl. Back in 2016, microservices were the shiny new toy in software architecture. Uber proudly announced that they’d broken their backend system into thousands of microservices – essentially thousands of tiny apps all cooperating (over the network) to run the Uber platform. It was a bragging right at the time; having a microservice for every little thing was seen as cutting-edge architecture, a sign you were operating at “Uber scale.” Most of the tech world’s reaction was a mix of awe and apprehension. Seasoned engineers muttered, “that sounds insane,” while perhaps some less experienced folks thought this was the secret sauce to being the next Uber or Netflix. The meme highlights that just a few years later (by 2020), even Uber’s own engineering leaders recognized this was architectural overkill. In other words, the hype met reality, and reality gave Uber a bit of a slap.
Why did everyone suspect it was insane in the first place? Because experienced developers have seen this movie before. The situation smelled a lot like the old Service-Oriented Architecture (SOA) craze from the mid-2000s – which many veterans recall became overly complicated and fell out of favor. Breaking a system into services is supposed to make it more modular and scalable, but there’s a law of diminishing returns. Uber overshot the sweet spot by a mile (or rather, by a thousand services). It became a textbook case of over-engineering: using an extremely complex design for a problem that didn’t truly require it. Sure, microservices can solve certain scaling issues: teams can work independently without stepping on each other’s code, different parts of the app can scale separately (for example, the Payments service can scale up without impacting the Rides service), and deployments can be more frequent and isolated for each piece. These are real benefits. But beyond a certain point, each additional service adds more overhead than value. Uber turned the dial up to 11 and ended up with a tangled mess of interdependent services – what many call a “distributed monolith.” That term means you get all the mess of a monolithic “big ball of mud” codebase, but now it’s spread out over network calls and hidden behind dozens of APIs. They had hoped for a clean, modular design, but in practice it turned into a well-organized chaotic web scattered across microservices. In 2016, outsiders intuitively knew that managing thousands of anything in software is a nightmare. You don’t get a gold medal just because your architecture diagram looks like a bowl of spaghetti; more often you get 3 AM incident calls and bleary-eyed engineers.
On top of that, the way Uber’s teams were organized played a role in the sprawl. Conway’s Law famously says that a software’s design tends to mimic the communication structure of the organization that builds it. Uber was a fast-growing company with many teams, and often each team built and owned its own microservice (or several). As new teams and new features came along, so did new services. It’s easy for each little feature to justify its own service, and in a few years this led to the proliferation of hundreds upon hundreds of services. The architecture grew organically into a monster of microservices partly because every division of the company carved out its own piece of the system. In hindsight, nobody stepped back early enough to ask, “Do we really need this many separate pieces?” The result was a lot of duplication and complexity creeping in as the system evolved. When you zoomed out, it wasn’t a carefully planned city of services — it was an exploding microservice metropolis with congested service traffic and confusing street maps.
By 2020, Uber essentially had to admit, “Yep, that was a bit much.” The meme’s punchline is Uber saying “It turns out that was insane” – a humble reversal from their earlier pride. In those four years, all the predicted pain points likely came true. Dependency chaos? Check. Imagine one Uber feature (say, calculating a fare and confirming a ride) now requires 20 different microservices to talk to each other; a single slow or broken service in that chain could cascade and break the whole feature. Engineers probably found themselves chasing ghost bugs through a thicket of services: for example, a ride request might fail not because the ride-matching logic was wrong, but because some downstream LocationService or NotificationService timed out. Debugging hell? Absolutely. An on-call developer might have to run grep across log files on 50 different servers just to trace a single user’s request – basically doing detective work with clues scattered all over the city. Tracking down why “the app is acting weird” could feel like searching for a needle in a haystack of needles. Uber had to build extensive observability tools (centralized logging, metrics dashboards, distributed tracing systems like Jaeger) just so they could follow the path of a single request as it weaved through dozens of microservices. When something went wrong at 2 AM, the poor engineer on duty needed strong coffee and a giant dependency graph to even know where to start. It’s the kind of on-call situation that gives even hardened backend veterans nightmares.
Another pain: operational complexity went through the roof. Each microservice needs to be deployed, monitored, scaled, and kept secure individually. Uber’s ops and infrastructure teams had to manage a huge fleet of services, which is a lot more complicated than managing one big application. They needed robust container orchestration (think Kubernetes or an in-house equivalent) to schedule and run all these services on their servers. They needed service discovery mechanisms so that Service #123 can reliably find Service #456 when it needs to call it. They had to implement load balancing, rate limiting, and fault tolerance (like circuit breakers to stop cascading failures) for each inter-service call. It’s doable – companies like Uber and Netflix ended up creating entire platforms and libraries to deal with this – but it’s enormously expensive in engineering time. You basically create a whole support ecosystem (and dedicated teams) just to keep the microservice zoo running smoothly. For every microservice that was theoretically “independent,” there was a ton of glue code, config, and DevOps work behind the scenes ensuring that independence didn’t turn into chaos. It’s like maintaining a fleet of a thousand tiny specialized robots instead of a few big sturdy robots – possible, but you need a big maintenance crew and a lot of automation to keep them all in line.
Crucially, the supposed payoff of microservices is faster development and deployment, since each team can move at its own pace. But when you have too many, the coordination overhead can start to nullify those gains. Imagine trying to make a simple change to a feature that touches 5 or 6 services: you might need to update and deploy all of them in a compatible way and ensure none of the other dozens of services that integrate with them break. Without extremely careful boundaries and communication, you get version mismatches and integration bugs galore. I wouldn’t be surprised if Uber discovered that some of those thousands of services were actually tightly coupled under the hood – change one, and you unintentionally break another – basically the opposite of the loose coupling that microservices aim for. This is the technical debt coming back to bite. All those nifty services became a form of debt: they gained short-term flexibility at the cost of long-term complexity that now has to be paid down. By 2020, engineers might have joked that they need a microservice just to keep track of all the other microservices. It’s a classic case where the solution became as problematic as the original issues it was meant to solve.
For seasoned engineers, this meme is both hilarious and a bit cathartic because it confirms what the skeptics long suspected. It’s the classic tale of architecture trade-offs: there’s no silver bullet. You gain some benefits, but you pay for them in other ways. Uber’s case shows the extreme end of the microservices spectrum – essentially a cautionary tale now told in tech blogs and conference talks. We’ve since seen a pendulum swing back: the mantra shifted from “Break everything into microservices, no matter what” to “Maybe start with a monolith (or a modest number of services) and only split into microservices when you truly need to.” In practical terms, that means most companies now aim for a much smaller, more manageable number of services with well-defined boundaries (say a few dozen at most, not thousands). The meme uses Uber’s experience as a punchline that encapsulates this hard-earned wisdom. Thousands of microservices? It turned out to be, in plain terms, crazy. The tweet is funny because of the bluntness of that admission – it’s a big “I told you so” from the programming community. Sometimes less is more in software, and simplicity beats complexity. Uber learned that the hard way, and the rest of us got a meme-worthy lesson out of it.
Level 4: Combinatorial Explosion
This meme hints at some deep distributed-systems theory underlying Uber’s microservice mania. By splitting an application into thousands of independent services, Uber embarked on an extreme experiment in distributed computing – and fundamental laws kicked in. One critical concept here is the combinatorial explosion of interactions: as you increase the number of components, the potential communication paths between them grows dramatically. In the limit, a network of N services could have on the order of $N^2$ connections if each talks with many others. With N in the thousands, managing all those interactions verges on intractable. We’re talking about a dependency graph so large and tangled that it starts to resemble a spaghetti bowl of nodes and edges. It's a complexity that scales faster than linearly – almost exponentially – with each new service added.
Consider basic reliability math: if each microservice in a chain has a probability p of succeeding, the probability of an entire transaction succeeding is the product of all those probabilities. With dozens of services involved in fulfilling one user request, even high individual reliability (say 99.9% uptime each) can compound into frequent failures. For example, if 100 services must work in sequence,
$$P_{\text{success}} = (0.999)^{100} \approx 0.9049,$$
meaning a chain of 100 calls at 99.9% success each yields only about a 90% chance the whole thing works. Scale that up to hundreds or thousands of interactions and you see how system reliability plunges without heroic engineering efforts. Uber’s architecture had to confront this harsh math daily. The humor of the tweet belies a serious truth: more moving parts means more points of failure and more ways for the whole to break down. To keep things running, Uber’s engineers would have needed extremely robust fallbacks and redundancy. In fact, the whole idea of graceful degradation (ensuring the system tolerates partial failures) becomes a daily concern when you have such a plethora of services.
Then there’s the famous CAP theorem from distributed systems theory. It states that a distributed data system can only reliably provide at most two out of three guarantees: Consistency, Availability, and Partition tolerance. Uber’s thousands of microservices, each likely with its own data or state, effectively form a huge distributed data system. When network issues or partitions happen – and with so many services, something’s always flaky somewhere – you’re forced into tough choices. For high availability, Uber might allow some inconsistency (the system keeps working even if some services are briefly out of sync, a form of eventual consistency). But that introduces lag and complexity: data might not sync up across all those services instantly, leading to weird edge-case bugs that are nightmares to debug. In a system this distributed, coordinating a single action (like a user pressing “Request Ride” and everything that follows) can require orchestrating state across multiple services. That’s when you invoke the spirits of tricky distributed algorithms like two-phase commit or the saga pattern to handle multi-service transactions. These algorithms are academically fascinating, but they’re notoriously hard to implement at large scale without making developers tear their hair out at 3 AM. It’s as if Uber’s architecture dared the theoretical limits: “Let’s see what happens when we apply these concepts at maximum scale.”
We also have the classic Fallacies of Distributed Computing on full display. One such fallacy is “the network is reliable” – in reality, networks will fail, packets get lost, services time out. With thousands of microservices, network calls aren’t occasional; they’re the backbone of every user interaction, meaning these failures are a constant headache. Another fallacy: “latency is zero.” In truth, every hop between microservices adds a bit of delay. Chain enough services together and those delays add up, potentially slowing down user requests. Uber’s microservice web had to contend with these physical truths. Essentially, they created a gigantic asynchronous, distributed state machine – one with no single observer who can see the whole state. In theoretical terms, the global state of Uber’s system was fragmented into many pieces. No single service or person could easily comprehend the entire system’s state at a given moment. That’s like having an extremely high-entropy system: highly disordered and unpredictable unless you consider the entire system, which is nearly impossible to do in real-time. The result is that debugging becomes a Herculean task (as we’ll see in simpler terms later), because to diagnose an issue you must piece together partial information from many subsystems that were all operating concurrently.
In summary, the meme underscores a kind of “architectural overkill” that pushes against fundamental complexity limits. Breaking systems into smaller parts is a core strategy in software design (think modularization, separation of concerns), but when taken to an extreme, the benefits can invert. Past a certain point, the diminishing returns hit hard: overhead from coordination, communication, and maintaining consistency starts to dominate any gains you got from splitting things up. Uber’s microservice experiment ventured into a regime where the coordination complexity became borderline chaotic. From a theoretical perspective, calling that approach “insane” is a tongue-in-cheek way of saying they ran afoul of the inherent constraints of distributed architecture. It’s as if the laws of computer science were sitting back and saying, “You can’t cheat complexity, my friend.” In the end, even Uber had to acknowledge that no matter how advanced your engineering, there’s a limit to how many moving parts you can juggle before the system (and your engineers) just can’t cope.
Description
A screenshot of a tweet from Ben Sandofsky (@sandofsky). The tweet, posted on April 7, 2020, has a dark background and white text. It reads: 'Uber in 2016: “We have thousands of microservices.” Everyone: “That sounds insane.” Uber in 2020: “It turns out that was insane.”'. This meme captures the arc of the microservices hype cycle, where Uber was a prominent advocate for fine-grained service decomposition. For experienced engineers, this is a humorous and relatable commentary on architectural trends. It highlights the industry's shift from enthusiastically adopting microservices to solve monolith problems, to later recognizing the immense operational complexity and maintenance overhead that comes from having too many services. The joke resonates with senior developers who have learned that architectural purity often loses to pragmatism and that there are significant trade-offs with any distributed system
Comments
7Comment deleted
Having thousands of microservices isn't an architecture; it's a distributed monolith with a network-call-based stack trace that requires a dedicated SRE team just to debug 'hello world'
Microservices were fun until we noticed the new architecture diagram and the company org chart were the same spaghetti - except only one of them supports retries and circuit breakers
The four-year journey from "our distributed tracing dashboard looks like abstract art" to "maybe we should have just used Postgres and a load balancer."
Ah yes, the classic "thousands of microservices" era - when every team wanted their own service, every function deserved its own repo, and debugging a single request required tracing through more hops than a cross-country flight with layovers. Uber's 2,200+ microservices became the cautionary tale that finally made it acceptable to admit that maybe, just maybe, not every CRUD operation needs its own Kubernetes deployment, service mesh, and dedicated on-call rotation. Turns out the real monolith was the distributed system we made along the way
Uber's microservices odyssey: proving that 1000 endpoints just redistribute the monolith's tech debt across the network
Turns out "a thousand services" is Latin for O(n^2) failure modes and an on‑call rotation that implements CAP: pick Consistency, Availability, or sleep
Microservices maturity check: if you need a graph database to answer “who calls what,” you’re not doing architecture - you’re doing archaeology