Arhitectura datelor și a sistemelor, de la zero Lecția 8 / 80

Trei studii de caz despre 'ar fi trebuit să începem mai simplu'

Stripe rămânând pe Postgres mult mai mult decât se aștepta lumea, poziția Shopify pe monolit și majestuosul monolit Basecamp. Pledoarie pentru a nu supra-inginerii devreme.

Lecția anterioară a expus simptomele care îți spun că arhitectura pe un singur server nu mai e de ajuns. Mesajul implicit era liniștitor: arhitectura pe care ai construit-o în lecțiile 1-6 e ok până nu mai e, și vom petrece restul cursului învățându-te cum s-o evoluezi.

Lecția asta e cealaltă jumătate a mesajului. Majoritatea echipelor își supra-ingineresc prima arhitectură. Apucă microservices, baze de date distribuite, Kubernetes, service mesh-uri, event sourcing și CQRS, toate înainte de a avea încărcarea care să justifice ceva din toate astea. Rezultatul e ani de povară operațională pentru capacități pe care nu le folosesc, talent pe care nu-l pot angaja și complexitate pe care n-o pot debug-a la trei dimineața.

Trei companii sunt utile aici, pentru că toate au rezistat trendului, în public, cu articole inginerești detaliate, și la scări mult mai mari decât a ta. Stripe a rămas pe Postgres mult mai mult decât se aștepta industria. Shopify a dus un monolit Rails până în zona merchanților de mai multe miliarde de dolari. Basecamp a scris un manifest despre asta.

Dacă prima ta reacție e „ei bine, sunt cazuri speciale”, restul lecției e pentru tine. Majoritatea arhitecturilor sunt cazuri speciale. Întrebarea e dacă a ta e specială într-un fel care justifică complexitatea sau dacă, ca majoritatea echipelor, copiezi un tipar care a fost construit ca să rezolve o problemă pe care n-o ai.

Cazul 1: Stripe și pariul lung pe Postgres

Stripe procesează plăți pentru o fracțiune semnificativă din comerțul de pe internet. E un workload în timp real, care mișcă bani, sensibil la latență, intens auditat și reglementat financiar. După înțelepciunea convențională a perioadei 2015-2020, ăsta e exact tipul de workload pe care ar trebui să-l pui pe un data store distribuit construit la comandă. NoSQL, sharded, eventual consistent, cu un strat de coordonare făcut în casă.

Nu e ce a făcut Stripe. Stripe a rulat, și la momentul scrierii în 2026 încă rulează în mare parte, grosul datelor sale tranzacționale de producție pe un set mic de instanțe Postgres îngrijite cu atenție. Sharded, da, cu propriul lor strat de sharding deasupra. Reglate intens. Replicate între availability zones. Dar tot Postgres. Tot SQL. Tot ACID. Tot o bază de date relațională pe care un administrator de baze de date din 2005 ar recunoaște-o.

Două articole inginerești merită citite integral ca să înțelegi pariul. Primul e „Online Migrations at Scale” (https://stripe.com/blog/online-migrations), care descrie tiparele pe care Stripe le folosește ca să evolueze o schemă Postgres în timp ce servește miliarde de dolari de trafic. E companionul practic al simptomului 6 din lecția anterioară. Al doilea e seria de talk-uri „Ringpop, sharding, and database operations” pe care echipa de date de la Stripe le-a ținut la conferințe între 2018 și 2020, care descriu stratul de sharding pe care l-au construit deasupra Postgres, nu sub el.

Lecțiile care reies din povestea Stripe:

  • Postgres la dimensiunea potrivită, cu grija potrivită, susține încărcări enorme. Plafonul e mult mai sus decât presupun majoritatea echipelor pentru că majoritatea echipelor n-au văzut niciodată o instanță Postgres reglată corect, cu hardware adecvat, query-uri sensibile, indexuri bune și un operator competent. Au văzut doar versiunea care e prea mică pentru sarcină.
  • Costul mutării de pe modelul relațional e uriaș. Renunți la SQL, tranzacții, joinuri, chei străine, ecosistemul bogat de unelte, înțelepciunea cumulată din patruzeci de ani de administrare de baze de date și capacitatea de a angaja ingineri care deja știu cum funcționează. În schimb primești throughput la scriere cu prețul consistenței. Pentru majoritatea workload-urilor, schimbul e prost.
  • Costul investiției în scalare verticală și operațiuni bune de bază de date e mic în comparație. Un inginer senior de baze de date e scump, dar nu atât de scump ca echipa de care ai avea nevoie ca să operezi un store distribuit construit la comandă.

Mișcarea Stripe care e cel mai greu de copiat e disciplina. Au păstrat sistemul simplu chiar și când era la modă să-l faci complicat și au investit în munca plictisitoare, neatractivă, de a face Postgres să funcționeze la scara lor. Rezultatul e o platformă de plăți care, în termeni de complexitate arhitecturală, seamănă mai mult cu un startup din 2010 decât cu un unicorn din 2020.

Cazul 2: Shopify și monolitul modular

Shopify e o platformă de e-commerce. Găzduiește milioane de comercianți, procesează sute de miliarde de dolari de gross merchandise volume pe an și rulează trafic de Black Friday care, pentru un weekend pe an, depășește vârfurile anuale ale majorității celorlalte site-uri. E, după orice măsură, un sistem distribuit la scară mare.

E și, structural, un monolit Rails. Sau mai exact, a fost pentru cea mai mare parte a vieții sale, iar mutarea de la „monolit” spre „monolit modular” a fost o călătorie deliberată, lentă, publică și educațională. Referința canonică e articolul Shopify Engineering „Deconstructing the Monolith: Designing Software That Maximizes Developer Productivity” (https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity).

Argumentul Shopify e cam așa: problema cu un monolit nu e că codul trăiește într-un singur repository sau rulează într-un singur proces. Problema e când codului îi lipsesc granițele interne. Un monolit „big ball of mud”, unde orice modul poate apela orice alt modul, unde preocupările de domeniu se scurg între fișiere, unde o schimbare în billing necesită o schimbare în shipping, e cu adevărat greu de scalat, atât tehnic, cât și organizațional. Dar asta nu e o proprietate a monoliților. E o proprietate a codului prost.

Un monolit modular păstrează deploy-ul simplu (o aplicație, un grup de procese, o conductă de deploy) în timp ce impune granițe interne care arată mult ca granițe de servicii. Shopify folosește gem-uri Ruby intern, cu API-uri publice explicite, și tratează comunicarea inter-modul ca pe un apel de rețea, chiar și când e doar un apel de metodă. Rezultatul e că modulele pot fi raționate independent, evoluate independent și, eventual, dacă e necesar, extrase în servicii separate. Dar până când acea necesitate apare, echipa se bucură de simplitatea operațională a unui singur deploy.

Lecțiile:

  • Monolitul e o alegere de deploy, nu un păcat de organizare a codului. Poți avea un deploy monolitic cu o structură modulară curată sau un deploy de microservices cu un model de domeniu încâlcit. Al doilea e mult mai rău.
  • Microservices fără disciplină sunt mai lente și mai predispuse la erori decât un monolit fără disciplină. Partea „fără disciplină” face multă muncă în propoziția aia. Majoritatea echipelor n-au disciplina pe care microservices o cer, pentru că microservices cer o maturitate organizațională (independența echipelor, ownership, rotații de on-call, observabilitate, infrastructură de deploy) pe care majoritatea echipelor n-au construit-o încă.
  • Momentul potrivit ca să extragi un serviciu e când ai măsurat o durere pe care un serviciu ar rezolva-o. Shopify a extras servicii. Au făcut-o din motive specifice: o componentă de checkout care trebuia să scaleze independent, un store de inventar în timp real care avea nevoie de garanții de consistență diferite, un flux de plăți care avea nevoie de izolare. N-au extras pentru modă.

Povestea Shopify e antidotul argumentului „suntem prea mari pentru un monolit”. Sunt mai mari decât tine, erau mai mari decât tine când au luat decizia, iar decizia a fost: păstrăm monolitul, investim în modularitate.

Cazul 3: Basecamp și monolitul majestuos

Dacă Stripe e cazul ingineresc și Shopify e cazul arhitectural, Basecamp e cazul filosofic. David Heinemeier Hansson, creatorul Ruby on Rails și cofondator al 37signals, a scris un eseu în 2016 numit „The Majestic Monolith” (https://m.signalvnoise.com/the-majestic-monolith/). E una dintre cele mai citate și cele mai înțelese greșit piese din canonul de arhitectură. Citește-l integral dacă n-ai făcut-o.

Argumentul nu e că monoliții sunt universal corecți. Argumentul e că pentru o echipă mică de produs (37signals avea cam o duzină de ingineri când a fost scrisă piesa), suprasolicitarea cognitivă a unui sistem distribuit șterge orice câștig de throughput pe care l-ai obține din scalare orizontală. O echipă de doisprezece ingineri care rulează un monolit Rails poate livra produs mai repede, debug-a mai repede, face onboarding mai repede și opera sistemul cu un cost mai mic decât aceiași doisprezece ingineri care rulează cincisprezece microservices. Matematica throughput-ului nu e despre mașini; e despre oameni.

Postul e citit uneori ca anti-microservices. Nu e. E anti-microservices-prematur. Distincția contează. Există un nivel de încărcare, o dimensiune de echipă, o complexitate organizațională la care microservices se justifică. Greșeala e să presupui că ești la acel nivel când nu ești.

Lecțiile:

  • Arhitectura e constrânsă de dimensiunea echipei la fel de mult ca de încărcare. O echipă two-pizza care rulează zece servicii petrece mai mult timp pe coordonare operațională decât pe produs. O echipă two-pizza care rulează un singur serviciu bine organizat livrează.
  • Cea mai scumpă arhitectură e cea care e mai mare decât problema ta. Lecția Stripe e că simplul ajunge departe. Lecția Shopify e că modularitatea în interiorul monolitului captează majoritatea beneficiului serviciilor. Lecția Basecamp e că pentru o echipă mică, arhitectura simplă e și cea rapidă.
  • Refactorizează spre servicii când durerea te forțează, nu când talk-urile de la conferințe îți spun.

Concluzia transversală a celor trei studii de caz e aceeași. Amână complexitatea arhitecturală. Plătește costul operațional al sistemelor distribuite doar când încărcarea o cere. În rest, investește în arhitectura simplă: query-uri mai bune, indexuri mai bune, teste mai bune, deploy-uri mai bune, observabilitate mai bună. Munca aia se compune. Complexitatea arhitecturală de dragul ei nu.

Trei cronologii pe o pagină

flowchart LR
    subgraph stripe ["Stripe (Postgres-first)"]
        direction LR
        s1["<b>2010</b><br/>Founded on Postgres"] --> s2["<b>2014</b><br/>Realtime fraud<br/>and ledger on PG"] --> s3["<b>2017</b><br/>Sharding layer<br/>on top of PG"] --> s4["<b>2020</b><br/>Online migration<br/>patterns shared"] --> s5["<b>2024</b><br/>Bulk of prod<br/>still on PG"]
    end

    subgraph shopify ["Shopify (Rails monolith)"]
        direction LR
        h1["<b>2004</b><br/>Founded as<br/>a Rails app"] --> h2["<b>2014</b><br/>Multi-billion GMV<br/>on the monolith"] --> h3["<b>2016</b><br/>Modular monolith<br/>strategy"] --> h4["<b>2019</b><br/>Deconstructing<br/>the Monolith"] --> h5["<b>2024</b><br/>Selective<br/>extractions"]
    end

    subgraph basecamp ["Basecamp and 37signals"]
        direction LR
        b1["<b>2004</b><br/>Basecamp<br/>on Rails"] --> b2["<b>2016</b><br/>Majestic Monolith<br/>essay"] --> b3["<b>2020</b><br/>HEY launched,<br/>also a monolith"] --> b4["<b>2024</b><br/>Small set of<br/>monoliths,<br/>dozen engineers"]
    end

    classDef yr fill:#1f2933,stroke:#0d9488,color:#e8edf1
    classDef boundary fill:transparent,stroke:#0d9488,stroke-dasharray: 5 5
    class s1,s2,s3,s4,s5,h1,h2,h3,h4,h5,b1,b2,b3,b4 yr
    class stripe,shopify,basecamp boundary

Punctul vizual al cronologiei e că arhitectura simplă n-a fost o fază scurtă înainte de rescrierea inevitabilă. În toate cele trei cazuri a durat și încă durează, mult dincolo de scara la care majoritatea echipelor presupun că trebuie să meargă mai departe. Rescrierea adesea nu vine. Când vine, e țintită, lentă și justificată de o durere specifică.

Ce nu spun studiile de caz

Merită să fim expliciți despre ce nu dovedesc aceste povești, pentru că discursul din jurul lor tinde să exagereze în ambele direcții.

Nu dovedesc că monoliții sunt mereu corecți. Nu sunt. Există workload-uri (real-time bidding, servire heavy pe citire distribuită geografic, inferență de machine learning la edge) unde un singur monolit cu adevărat nu poate face treaba și unde o arhitectură orientată pe servicii sau distribuită e punctul de pornire potrivit. Acele workload-uri sunt mai rare decât sugerează discursul, dar există.

Nu dovedesc că Postgres e mereu corect. Un workload care e fundamental analitic (terabytes de telemetrie, query-uri OLAP ad-hoc, agregări fan-out) îi e locul pe un store columnar, nu pe Postgres. Un workload care e fundamental key-value cu throughput mare la scriere și cerințe joase de consistență îi e locul pe un store key-value. Lecția Stripe e că workload-urile tranzacționale, chiar la scară foarte mare, pot rămâne pe Postgres. Nu e că toate workload-urile ar trebui.

Nu dovedesc că microservices sunt rele. Dovedesc că microservices sunt scumpe și că cheltuiala e rar justificată înainte de praguri specifice de scară și organizaționale. După acele praguri, microservices pot fi alegerea potrivită. Shopify folosește unele. Stripe folosește unele. Punctul e că „unele” e mult mai mic decât „toate”, iar selecția se face după nevoie, nu după default.

Ce dovedesc studiile de caz e un tipar repetabil: echipele care au rezistat complexității până când simptomele le-au forțat mâna au construit sisteme care au durat, au evoluat și au rămas ieftine de operat. Echipele care au adoptat complexitatea devreme, din modă sau din frică, au construit sisteme care erau scumpe de operat și lente de schimbat. Primul grup include Stripe, Shopify și Basecamp. Al doilea grup e nenumit pentru că post-mortem-urile sunt private. Există. S-ar putea să fi lucrat la una.

Unde merge cursul de aici

Ai ajuns la finalul Modulului 1. Concluzia acestor opt lecții e, într-o singură propoziție: începe simplu, recunoaște simptomele când apar și mută-te doar când simptomele te forțează. Arhitectura din lecțiile 1-6 e suficientă pentru majoritatea echipelor pe cea mai mare parte a vieții lor utile.

Dar „majoritatea” nu e „toate”. Unele echipe chiar depășesc singurul server, iar întrebarea devine cum să evoluezi arhitectura fără s-o înrăutățești. Despre asta e restul cursului, iar modulul următor începe cu fundamentele sistemelor distribuite: ce se schimbă în momentul în care nu mai ai o singură mașină, de ce sistemele distribuite sunt mai grele decât par și ce modele mentale ai nevoie înainte să scrii o singură linie de cod care vorbește cu altă mașină printr-o rețea.

Modulul 2 începe în lecția următoare cu una dintre ideile fundamentale ale domeniului: cele opt fallacies ale calculului distribuit. Lista are treizeci de ani și e încă dureros de relevantă. Vom trece prin ea o fallacy pe rând, cu exemple de sisteme construite de oameni care au eșuat pentru că au presupus că fiecare fallacy e adevărată.

Stripe, Shopify și Basecamp au amânat acea complexitate cât au putut. Vom petrece următoarele câteva lecții învățând ce amânau.

Citări și lecturi suplimentare

  • Stripe Engineering, „Online Migrations at Scale”, https://stripe.com/blog/online-migrations (accesat 2026-05-01).
  • Shopify Engineering, „Deconstructing the Monolith: Designing Software That Maximizes Developer Productivity”, https://shopify.engineering/deconstructing-monolith-designing-software-maximizes-developer-productivity (accesat 2026-05-01).
  • David Heinemeier Hansson, „The Majestic Monolith”, https://m.signalvnoise.com/the-majestic-monolith/ (accesat 2026-05-01).
  • Talk-uri Stripe despre sharding și operațiuni, disponibile prin canalul YouTube Stripe Engineering și arhivele de conferințe din 2018 până în 2020.
Caută