- Hello there,/
- Posts/
- Paper 04: Scale-Free Networks — Why Systems Drift Toward a Few Services Everyone Depends On/
Paper 04: Scale-Free Networks — Why Systems Drift Toward a Few Services Everyone Depends On
Table of Contents

I. Abstract #
Splitting software into small services promises that load spreads out and failures stay put. The Barabási-Albert network model says otherwise. When a network keeps adding members that attach to crowded spots, most services end up with a few links while a small group holds hundreds. Nobody designs that group; when one of them stalls, failure travels backward to everyone waiting on it. This paper covers the math, the backward spread, and three ways to live with it: asynchronous calls, hard resource partitions per caller, and cutting off early when that service slows.
II. The “Split It Up and Load Spreads Out” Promise vs. the Real Graph #
Architecture slides have one underrated strength: every box is the same size. Redraw a system that has run for a few years by who really calls whom, and the picture usually changes: a few boxes have arrows coming in from almost every other box. Those boxes are my subject.
Each box is a service: software on a server doing one job, such as login or payments. Each arrow is a dependency: one service must call another to finish its work. The number of arrows attached to a box is its link count.
Microservices means splitting a system into many small services that call each other over the network. It replaces the monolith: one block of software holding every feature. The promise: split things up, and load and risk spread out.
That promise pictures an Erdős-Rényi random network: every pair of services is equally likely to be linked. Then every service has close to the average link count, and “the average service” means something.
Real systems usually differ: new services do not link at random, so a few core services end up carrying the whole organization. Each is a single point of failure: if it breaks, everything breaks, the very thing microservices were meant to remove. Why? Two rules suffice.
III. Two Rules Are Enough to Create a Hub #
The Barabási-Albert model (1999) reproduces this with two rules:
- The network keeps adding members. New services keep arriving.
- New links favor crowded spots. A new service attaches to the services everyone else already uses.
Rule two is preferential attachment: the more links you have, the easier the next one. Its everyday version is the colleague who knows everything. Everyone who asked him recommends him to newcomers, so he gets asked even more, even if others are just as capable.
Suppose a food-delivery app has a login service, auth. Every team needs to know who is using the app, so each calls auth. Each time that happens, auth becomes the more obvious choice for the next team. No meeting ever decided “make auth a single point of failure”. There were just forty small meetings, each sensibly concluding: reuse what exists.
In graph terms, each service is a node, and a node with far more links than the rest is a hub.
Run the two rules long enough and link counts follow a power law1: the more links, the rarer, but never rare enough to vanish, so a few giant hubs always exist. Here, ten times the links is about a thousand times rarer. Such a network is scale-free: no “typical size” represents its nodes.
So the average stops being representative. With 2 dependencies per new service, a 200-service network averages 4 links, and its largest service has about 30. At 20,000 services the average is still 4, but the largest has hundreds (382 in Section VI). Sizing machines by “the average service” fails exactly where it is most dangerous: at the hub.
Early arrivals also keep a lasting lead: in the simulation, service 100 ends with 28 links, service 10,000 with 3.
Going deeper: from two rules to the long tail
In words: the chance a new link lands on service $i$ is that service’s share of all links.
- $k_i$: current link count of service $i$.
- $\sum_j k_j$: total link count, with each link counted at both ends.
Worked numbers: 40 of 400 links gives 10%; a new service with 2 links gets 0.5%.
$$ \frac{\partial k_i}{\partial t} = \frac{m\,k_i}{2mt} = \frac{k_i}{2t} \quad\Rightarrow\quad k_i(t) = m\sqrt{t / t_i} $$In words: the pull is proportional to current links but divided by the network’s age, so links rise with the square root of time, not like compound interest; an older service keeps its lead ratio.
- $m$: links per new service; after $t$ steps the total is about $2mt$.
- $t$: step count, equal to the number of services so far. $t_i$: the step when service $i$ joined.
Worked numbers with $m = 2$, $t = 20{,}000$: service 100 has $2\sqrt{200} \approx 28$ links, service 10,000 has $2\sqrt{2} \approx 3$, as in the simulation.
$$ P(k) = \frac{2m(m+1)}{k(k+1)(k+2)} \approx \frac{2m^2}{k^3} $$In words: the share of services with exactly $k$ links falls with the cube of $k$: a power law with exponent 3.
- $P(k)$: share of services with exactly $k$ links.
Worked numbers with $m = 2$: $P(4) = 12/120 = 10\%$; the simulation gives 9.6%.
The largest service has about $m\sqrt{N}$ links for $N$ services: 28 for 200, 283 for 20,000 (simulated medians: 33 and 352). Variance, which measures spread around the average, keeps rising with the logarithm of $N$: about 17, 32, 45 for 200, 2,000, 20,000 services.
IV. From Network Model to Your System: The Mapping #
The table needs one more concept: a synchronous call. That is when service A calls service B and waits for the answer before doing anything else, like staying on the line during a phone call.
| In the network model | At the office | In a software system |
|---|---|---|
| Node | One person | One service or one database |
| Link | Having to ask someone once | One synchronous call, or one database query |
| Links pointing into a node | People waiting on one person | Services calling this one synchronously |
| Preferential attachment | “Just ask him” gets passed around | A new team attaches to the service every other team already uses |
| Hub | The colleague who knows everything | God service: every business flow and every major incident goes through it |
Age is not the only factor. The Bianconi-Barabási model (2001) adds fitness: each node’s own usefulness, multiplied into its chance of being picked. So auth, built later, still pulls dependencies faster than an older reporting service, since everyone must log in.
That is why splitting a God service usually fails. Splitting the code takes a few sprints; splitting the reason everyone needs it is harder, because afterward everyone still needs to log in.
How Does a Service Nobody Calls Get Its First Caller #
The original Barabási-Albert model ignores direction, but software dependencies have one: A calling B does not mean B calls A. Count only inbound links and a service that just went live scores 0, so under rule two nobody ever calls it.
Price (1976) fixes this by giving every node a small initial attractiveness2. In a real system, that is whatever gets a service noticed before it is popular. It may be a line in the internal service directory, a demo, or solving a problem someone has today.
Together, these mechanisms pile callers onto a few nodes, and a hub forms without anyone deciding. What happens when it stalls?
V. When a Hub Stalls, Which Way Does Failure Travel #
A scale-free network shrugs off random failures, because a randomly failed node is usually small; hit the hub and the network falls apart. In the food-delivery app, a failed reviews service hides the reviews; a failed auth stops all orders.
The Hub’s Load Ceiling #
Every hub has a hard ceiling: how many requests it handles at once. A request is one message to a service, like the app asking auth “is this user logged in?”. Past that ceiling, requests queue or get rejected. The ceiling usually comes from a connection pool, or from the thread pool described below. A connection pool is a fixed-size set of database connections, opened in advance and shared, like a bank branch with a fixed number of counters.
Suppose auth handles at most 200 requests at once, and at the lunch peak each caller keeps about 5 requests waiting on it. 200 divided by 5 is 40: auth can handle 40 callers. Then service 41 goes live, say meal recommendations. No alert fires: the dashboard shows auth at 45% CPU, since requests waiting in line use almost no CPU.
Failure Runs Backward, Not Sideways #
If I had to guess which way failure spreads, I would say along the arrows, like water through a pipe. I would be off by exactly 180 degrees.
In Motter and Lai’s cascade model (2002), written for networks like power grids, a failed node’s load shifts to other nodes along new paths: failure spreads sideways. Microservices have no such detour, since no other service can do the login job. When the hub freezes, requests to it just wait.
A thread is a line of work running in parallel inside a server; a thread pool is a service’s fixed set of threads. When auth freezes, each request to the promotions service calls auth and holds a thread. Soon the whole pool is full, and the promotions service takes nothing more, not even work that needs no auth. Then its own callers run out of threads, and so on up the chain.
At the office, it is the afternoon the colleague who knows everything spends in meetings: whoever waits on him stalls, and so does whoever waits on them. The example breaks at one point: a person can say “I’ll answer this afternoon”, while a synchronous call holds the caller until its maximum wait time runs out.
So failure runs backward against the dependency direction. One name for this is synchronous backpressure. Backpressure is pressure pushed upstream: a slow receiver forces the sender to slow down, here through the waiting synchronous calls themselves. For a hub, the callers upstream are most of the system.
Section VI tests the chain’s first link: does a hub really appear on its own?
VI. Simulation: A Hub Appears Among 20,000 Services #
I simulated a Barabási-Albert network of 20,000 services, each new one bringing 2 dependencies, with nothing tuned to force the result. The network averages 4.0 links and 80% of services have 4 or fewer, yet the largest service has 382, nearly 100 times the mean. No line of code says “create a hub”; one appears anyway, and it is the first service added.
How to read the chart: both axes are log scale, each major tick 10 times the last; horizontal is link count, vertical is the share of services with that count. Orange is the data, dashed white the best-fit straight line. A power law shows as a straight line sloping down; its steepness here is 2.82 (a slope of −2.82), near the theoretical 3. Buckets are log-spaced, since even spacing shrinks the tail, where hubs live, to a few noisy points.

Going deeper: the simulation code
The code builds the network, prints the numbers above, fits a straight line on log-log axes for the exponent, and draws the chart.
| |
Output: mean 4.0; largest 382; 80%; exponent 2.82, a little below 3 because the network is finite and the tail has few points.
VII. Three Ways to Live With Hubs, and What Each Costs #
Hubs are an arithmetic consequence of preferential attachment, so the three measures below do not remove them; they route around them. Question for your engineering team: which service takes the most synchronous calls, and what is its load ceiling?
Move the hub into infrastructure with EDA and CQRS. EDA is an architecture where services announce “this just happened” through a message broker instead of calling and waiting. A message broker is intermediary software that receives, stores and forwards messages; Kafka and RabbitMQ are examples.
CQRS, often paired with EDA, separates the write path from the read path so they stop competing for resources. The price is eventual consistency: data across services will match, but only after a delay.
This sounds like tidying up but is really moving house: the hub does not vanish, it moves into the broker. The gain: a broker is built for this load; Kafka, for instance, appends to disk sequentially and holds a far higher ceiling than a business service. Senders drop off a message and move on, so no thread is held.
Isolate with bulkheads. A bulkhead is a wall dividing a ship’s hull into sealed compartments, so one breach does not sink the ship. In software, it splits the hub’s thread pool into fixed shares, one per calling service. A caller that fails and floods requests uses up only its share; the other 39 keep running.
Stop the backward spread with circuit breakers. A circuit breaker is a switch on the caller side. When the hub is too slow or keeps failing, the switch trips so the caller returns an error at once instead of waiting. It blocks the backward spread most directly, cutting exactly where threads get held.
| Measure | What you get | What it costs |
|---|---|---|
| EDA / CQRS | Hub moves into infrastructure with a far higher ceiling; nobody waits | Eventual consistency; the broker is a new central dependency that needs careful operation |
| Bulkheads | Exactly bounded impact when one caller has trouble | Preallocated resources sometimes sit idle; capacity is planned per caller |
| Circuit breakers | Cuts the backward spread, keeps callers’ thread pools free | Users get a fast error instead of a long wait; a fallback plan is needed for when it trips |
VIII. References #
- Barabási, A.-L., & Albert, R. (1999). Emergence of Scaling in Random Networks.
- Bianconi, G., & Barabási, A.-L. (2001). Competition and multiscaling in evolving networks.
- Clauset, A., Shalizi, C. R., & Newman, M. E. J. (2009). Power-Law Distributions in Empirical Data.
- Motter, A. E., & Lai, Y.-C. (2002). Cascade-based attacks on complex networks.
- Price, D. d. S. (1976). A general theory of bibliometric and other cumulative advantage processes.
Clauset, Shalizi and Newman (2009) showed that many distributions called power laws fail statistical tests. This paper uses the power law as an approximate model of concentration on a few nodes, not as a claim that any real dependency graph has exactly that shape. ↩︎
Price wrote the model for citation networks between scientific papers, which are directed, and used an initial attractiveness of 1; the form with any positive constant is a later generalization. ↩︎