Segment collects events from its customers' websites and apps and forwards them to the tools those customers use: analytics products, ad platforms, data warehouses. Each of those tools is a "destination". In 2018 Alexandra Noonan wrote Goodbye Microservices, describing how Segment had split destination delivery into well over a hundred services, and then merged them back into one.
The post became one of the most cited arguments in the microservices debate. Read closely, it's less "microservices are bad" and more a precise account of which costs grew with every service added, for a small team, and which design changes removed them.
What they had
Version one: one queue. An API accepted events and put them on a single distributed queue. Workers took each event and sent it to every destination the customer had enabled, one after another. Retries went back onto the same queue.
The problem was head-of-line blocking: if one destination was slow or down, its retries and timeouts held up delivery to every other destination.
Version two: a service per destination. To isolate destinations from each other, the team gave each one its own queue and its own service, with a router copying each event to the queues of the destinations that needed it. A slow destination now only backed up its own queue. This is textbook fault isolation, and it worked for that purpose.
%% caption: Versions two and three. Version two isolated destinations with separate services; version three replaced the queues with Centrifuge and put the code back together.
flowchart TD
subgraph v2 ["Version two: a queue and a service per destination"]
R["Router"] --> Q1[["Queue: destination A"]] --> S1["Service A"]
R --> Q2[["Queue: destination B"]] --> S2["Service B"]
R --> Q3[["... 140+ queues"]] --> S3["... 140+ services"]
end
subgraph v3 ["Version three: one service"]
C["Centrifuge<br/>per-destination delivery"]:::hot --> M["One destination service<br/>monorepo, one dependency set"]
end
v2 ~~~ v3