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

Prima arhitectură: o aplicație web single-server + bază de date

Cum arată un sistem funcțional la scală de startup: un VM, un Postgres, un proces și de ce e un punct de plecare perfect respectabil.

Lecția precedentă a fost despre trade-off-uri. Asta e despre arhitectura pe care ar trebui s-o alegi când n-ai câștigat încă niciunul dintre trade-off-urile mai grele. Majoritatea sistemelor ar trebui să înceapă aici. Un număr surprinzător de sisteme nu ar trebui să plece niciodată.

Forma: o mașină virtuală, o bază de date Postgres, un proces de aplicație, un pipeline de deploy. Opțional un frontend static pe un CDN, opțional un load balancer gestionat în față, opțional un cache mic atunci când ai cu adevărat nevoie de unul. Asta e toată diagrama. Încape pe spatele unui șervețel. Poți rula o afacere profitabilă pe ea. Oamenii o fac, în fiecare zi.

Lecția asta e despre de ce ăsta e un bun punct de plecare, ce poate și ce nu poate face, cât costă, care sunt piesele concrete și ce ar trebui să rezisti să adaugi pe deasupra cât de mult posibil.

Cum arată „arhitectura minimală viabilă”

Arhitectura minimală are patru piese în mișcare:

  1. Un framework web, în orice limbaj în care echipa ta e cea mai rapidă. Python (Django, FastAPI, Flask), Node (Express, Fastify, NestJS), Go (net/http sau chi sau Gin), Ruby (Rails), Elixir (Phoenix), .NET (ASP.NET Core), Java (Spring Boot, sau Quarkus dacă-ți place cold start-ul). Sunt toate ok. Alegerea abia contează la stadiul ăsta. Alege-l pe cel al cărui ecosistem îl poți naviga la 2 dimineața.
  2. O bază de date relațională. Postgres e alegerea sigură. MySQL/MariaDB e a doua alegere sigură. SQLite e cu adevărat viabil pentru multe workload-uri până la câteva mii de utilizatori; nu râde. Evită orice e mai trendy; nu ai câștigat încă complexitatea operațională.
  3. Un reverse proxy sau load balancer în față. Fie nginx pe același VM, fie un load balancer gestionat (AWS ALB, Cloudflare, Hetzner Load Balancer, DigitalOcean Load Balancer). Termină TLS, dă mai departe HTTP-ul către aplicația ta.
  4. O cale de deploy. Asta poate fi atât de fancy pe cât vrei sau atât de brut pe cât git pull && systemctl restart app. Ambele extreme au livrat afaceri reale.

Ăsta e tot stack-ul. Orice altceva (cache, coadă, frontend separat, CDN, search, warehouse de analiză) e ceva ce adaugi când poți numi o problemă specifică pe care o rezolvă pentru tine, nu pentru că diagramele de arhitectură din talk-urile de conferință au cutiile alea.

Iată diagrama de container C4 pentru asta:

flowchart LR
    user[End user browser] -->|HTTPS| lb[Load balancer or nginx]
    lb -->|HTTP| app[Web app process]
    app -->|TCP 5432| db[(Postgres database)]
    cdn[CDN] -.->|static assets| user

Atât. Un punct de intrare orientat spre utilizator, un proces de aplicație, o bază de date, opțional un CDN pentru fișiere statice. Tot lucrul încape în capul tău, poți face debug oricărei părți de pe un laptop și poți deploy-ui o versiune nouă în mai puțin de cinci minute.

Ce poate de fapt să facă

Instinctul, mai ales în 2026 după un deceniu de marketing pentru microservicii, e să crezi că forma asta e o jucărie. Nu este.

O aplicație web rezonabil tunată pe un singur VM de 4 vCPU și 8 GB, vorbind cu un Postgres pe aceeași mașină sau pe un vecin gestionat, poate gestiona confortabil:

  • Sute de request-uri pe secundă fără efort ingineresc special. Postgres gestionează point queries în milisecunde cu o singură cifră; overhead-ul framework-ului tău e de zeci de milisecunde în cel mai rău caz; rețeaua din față e bottleneck-ul mai des decât e aplicația.
  • Mii de request-uri pe secundă cu ceva grijă. Grija aia arată așa: connection pooling (PgBouncer sau pool-ul built-in al framework-ului tău, dimensionat corect), indexuri rezonabile pe coloanele după care filtrează query-urile tale, evitarea N+1 query-urilor folosind JOIN sau eager loading în loc de un query per rând, și un cache pentru căile cu adevărat fierbinți.
  • Zeci de milioane de rânduri în cele mai mari tabele ale tale înainte să trebuiască să te gândești serios la partiționare. Postgres e fericit la scara asta. Indexurile B-tree sunt fericite. Sequential scans pe rarul query prost scris sunt încă acceptabile.
  • Zeci de mii de utilizatori concurenți dacă aplicația ta se comportă bine și majoritatea traficului tău e citiri. Conexiunile WebSocket se difuzează per proces; dacă ai nevoie de un milion de socket-uri concurente ai o problemă diferită, dar foarte puține aplicații chiar au.

Un startup mic care procesează 10.000 de comenzi pe zi, un SaaS cu 5.000 de clienți plătitori, un site de conținut cu 500.000 de cititori lunari, un instrument B2B cu 200 de utilizatori enterprise care îl pisează în orele de lucru: toate astea rulează confortabil pe forma asta, cu loc de mișcare. Companiile care au nevoie de mai mult sunt reale, dar sunt o fracțiune mai mică a industriei decât sugerează circuitul de conferințe.

Adevărul nesentimental e că baza de date face aproape toată munca. Procesul de aplicație e în principal lipici: acceptă un request HTTP, validează input-ul, rulează niște query-uri, formatează output-ul. Dacă aplicația ta e lentă, e aproape întotdeauna pentru că un query e lent, nu pentru că framework-ul e lent. A ști asta te scapă de greșeala de a arunca mai multe servere de aplicație la o problemă care e de fapt un index lipsă.

Cât costă

Numere reale, în EUR, pentru 2026, pentru „am un side project / un SaaS mic care trebuie să ruleze undeva”:

  • Hetzner Cloud CPX21: 4 vCPU, 8 GB RAM, 80 GB SSD, 20 TB trafic. Aproximativ EUR 13/lună. Datacenter în Germania sau Finlanda, latency excelent pentru utilizatori europeni.
  • DigitalOcean Basic Droplet, 4 vCPU, 8 GB RAM, formă similară. Aproximativ EUR 45/lună. Mai multe regiuni globale, dashboard ușor mai drăguț.
  • AWS EC2 t4g.large (2 vCPU ARM, 8 GB) sau t3.medium (2 vCPU, 4 GB). Aproximativ EUR 25 până la 40/lună plus data transfer, plus snapshots, plus factura pe care nimeni n-o poate prezice.
  • Postgres gestionat, dacă vrei să sari peste rularea lui singur: Hetzner nu oferă încă unul; DigitalOcean managed Postgres începe în jur de EUR 15/lună pentru o instanță mică; AWS RDS pentru un db.t4g.small e în jur de EUR 30/lună, iar unul real production-grade cu backup-uri și o replică e mai aproape de EUR 80.
  • Cloudflare în față pentru DNS, TLS, caching, protecție DDoS: gratuit pentru majoritatea workload-urilor mici. Planul Pro e USD 25/lună dacă vrei WAF, analytics, optimizare de imagini. Majoritatea proiectelor mici sunt ok pe gratis.
  • Backups pe S3-compatible object storage (Backblaze B2 sau Hetzner Storage Box): câteva EUR/lună pentru volumele pe care le produce un SaaS mic.
  • Monitoring prin tier-ul gratuit al Grafana Cloud, BetterStack sau Prometheus self-hosted pe același VM: gratuit, sau o singură cifră în EUR pentru o alternativă gestionată.

Factura lunară totală pentru un deployment funcțional în producție: undeva între EUR 30 și EUR 100, în funcție de dacă self-host-uiești baza de date și cât valorezi să nu o rulezi singur. Asta e mai puțin decât te facturează din greșeală tier-ul de explorare AWS când uiți să oprești un NAT gateway. O afacere reală, scalată, cu clienți plătitori, poate rula pe asta o perioadă lungă.

Economia per unitate e extrem de prietenoasă cu echipele mici. Dacă ai 100 de utilizatori plătitori la EUR 20/lună, ai EUR 2.000/lună venit și EUR 50/lună infrastructură. Aproape orice altă formă arhitecturală costă mai mult, atât în compute, cât și în timp de inginerie. Cea mai ieftină cale de a opera e să nu ai multe lucruri de operat.

De ce ăsta e punctul de plecare corect

Trei motive, toate despre persoana care rulează sistemul, nu despre mașină.

Simplu de raționat despre el. Când ceva se strică, sunt patru locuri în care poate fi: load balancer-ul, aplicația, baza de date sau conexiunea dintre ele. SSH în VM, te uiți la log-uri, te uiți la top, te uiți la pg_stat_activity. Modelul mental e suficient de mic încât îl ții în memoria de lucru în timp ce faci debug. Compară asta cu un deployment de microservicii cu 12 servicii unde request-ul ar putea fi în oricare dintre șapte cozi async și nu poți spune care.

Rapid de deploy-uit. O versiune nouă a codului pe stack-ul ăsta e git pull, rulează migrări, restart la proces. Cinci minute de la „l-am reparat pe laptop” la „e live în producție.” Asta se compune. O echipă care livrează de zece ori pe zi învață de zece ori mai repede decât o echipă care livrează o dată pe săptămână, iar la stadiul ăsta de proiect, viteza de învățare e tot jocul.

Rapid de debug-uit. Stack trace-urile sunt locale. Log-urile sunt într-un singur loc. Timpul de pe ceasul de perete se potrivește cu timpul din baza de date care se potrivește cu timpul din aplicație. Nu există distributed traces de reconstruit, nu există clock skew între servicii, nu există cozi de mesaje care ascund ordinea operațiunilor. Când reproduci un bug, îl reproduci la fel de fiecare dată.

Fondatorii solo și echipele mici livrează ani de produs pe forma asta. Stripe a rulat pe Ruby on Rails și un Postgres mult timp. Basecamp încă o face, prin alegere. Nomad List al lui Pieter Levels rulează pe un singur VM cu o singură bază de date SQLite, o face de ani de zile, și tipărește bani. Plausible Analytics, înainte să crească, era o singură aplicație Phoenix și un Postgres. Pattern-ul e atât de comun încât e aproape invizibil.

Piesele în detaliu

Hai să trecem prin piesele concrete în mișcare ale unei versiuni reale a stack-ului ăsta, genul pe care l-ai pune în picioare mâine dimineață.

Framework-ul web

Alege-l pe cel în care echipa ta e cea mai rapidă. Nu există motiv arhitectural să alegi unul anume la scara asta. Framework-ul își va petrece timpul făcând același lucru în orice limbaj: acceptă request, validează, query la DB, randează răspuns. Indiferent ce framework alegi, învață-i convențiile profund în loc să te cerți cu ele.

Două alegeri opinionate dacă n-ai preferință și vrei ceva plictisitor și de încredere: Django dacă echipa ta e cu aromă de Python, Rails dacă echipa ta e cu aromă de Ruby. Ambele au 20 de ani, ambele sunt încă dezvoltate activ, ambele au o cantitate obscenă de biblioteci „chestia asta e deja rezolvată” și răspunsuri pe Stack Overflow, și ambele sunt proiectate exact pentru forma asta de o-singură-aplicație-și-bază-de-date. Phoenix (Elixir) e o alegere mai puțin comună, dar excelentă dacă echipa ta are ceva experiență cu el; scalează mult mai departe decât Django sau Rails pe o singură mașină pentru că BEAM VM e cu adevărat mai bună la concurență decât CPython sau MRI.

Postgres

Folosește Postgres. Nu te chinui cu decizia.

Postgres în 2026 e, conservator, cea mai general-utilă bază de date livrată vreodată. Face tabele relaționale, documente JSON (cu indexuri!), full-text search, query-uri geospațiale (PostGIS), time-series rezonabil (cu TimescaleDB sau partiționare), pub/sub (cu LISTEN/NOTIFY) și chiar cozi de joburi (cu SELECT ... FOR UPDATE SKIP LOCKED). Pentru majoritatea workload-urilor SaaS de mărime mică-spre-medie, nu îl vei depăși niciodată.

Rulează-l ca serviciu gestionat dacă-ți permiți primă mică. Self-host-uiește-l pe același VM cu aplicația dacă nu. Indiferent, activează backup-urile automate înainte să ai primul utilizator. Vom face o lecție întreagă despre backup-uri în modulul 9; deocamdată: pg_dump nocturn pe S3, cu retenție.

Connection pooling: Django și Rails gestionează asta in-process, ceea ce e ok până când ai nevoie de mai mult de câteva procese de aplicație. Odată ce ai mai multe procese (sau, eventual, mai multe mașini), pune PgBouncer în mod transaction-pooling în fața Postgres. E cea mai plictisitoare bucată posibilă de software și a lucrat fără reclamații cincisprezece ani.

Coada de joburi

Va trebui să faci muncă asincron: trimiterea de e-mail-uri, generarea de rapoarte, retry la apeluri externe de API, procesarea de imagini încărcate. Instinctul e să adaugi Redis sau RabbitMQ. Rezistă cât mai mult, pentru că Postgres e și o coadă de joburi perfect bună la scara asta, folosind SELECT ... FOR UPDATE SKIP LOCKED pentru a scoate joburi din coadă fără contenție.

Biblioteci care fac asta trivial: pg-boss (Node), solid_queue (default-ul în Rails 8, apropo), dramatiq cu broker Postgres, River (Go), Oban (Elixir), procrastinate (Python). Îți dau coada de joburi fără să-ți dea încă o piesă de infrastructură de operat. E un câștig semnificativ la mărime mică de echipă.

Dacă ai cu adevărat nevoie de muncă în timp real cu latency de milisecundă (chat, presence, stare live de joc), atunci da, Redis. Dar asta e o cerință specifică, nu un default.

Reverse proxy-ul și TLS

Două alegeri viabile.

Opțiunea A: nginx pe VM. Termină TLS cu Let’s Encrypt prin certbot, fă proxy către aplicația de pe localhost:8000, servește fișiere statice direct din /var/www. Trei fișiere de config, fiecare de 20 de linii, fiecare pe care îl copiezi dintr-un tutorial prima dată și nu-l atingi niciodată din nou. Gratuit, rapid, ok.

Opțiunea B: load balancer gestionat. Cloudflare în față pe gratis, sau AWS ALB/Hetzner LB pentru câțiva EUR pe lună. Gestionează TLS, WAF opțional, mitigare DDoS opțională. Ușor mai drăguț de operat; nu trebuie să-ți reînnoiești singur certificatele.

Oricare e ok. Majoritatea proiectelor încep cu opțiunea A și se mută la opțiunea B când vor un al doilea VM în spate.

Pipeline-ul de deploy

Există două forme acceptabile la scara asta.

Docker Compose pe VM. docker-compose.yml-ul tău definește aplicația, Postgres, opțional Redis. SSH înăuntru, git pull, docker compose up -d --build. Gata. Backup-urile sunt un cron job care face pg_dumpall și împinge pe S3.

Bare systemd pe VM. Aplicația ta rulează ca o unitate systemd. Postgres e serviciul împachetat de distribuție. Deploy-urile sunt: SSH înăuntru, git pull, bundle install sau pip install sau npm install, migrate, systemctl restart yourapp. Asta e ce făceau oamenii înainte de Docker și încă funcționează. E mai rapid, are mai puține piese în mișcare și modelul mental e cu o unitate mai mic decât versiunea Docker.

GitHub Actions pentru CI: pe push pe main, rulează teste, și dacă e verde, SSH în VM și rulează scriptul de deploy. Cinci linii de YAML. Tot setup-ul de CI/CD e ceva ce poți construi într-o oră și nu atingi niciodată din nou până când nu mai se potrivește, ceea ce pentru majoritatea proiectelor e niciodată.

Oricare formă e bună. Forma greșită e „o să rulăm Kubernetes pentru side project-ul nostru cu 50 MAU”, și cineva o va face oricum, și își va petrece mai mult timp pe YAML decât pe cod, și va scrie un blog post despre cât de frumos scalează, și nu ar trebui să-l asculți.

O diagramă mai bogată

Când proiectul începe să se simtă real (clienți plătitori, fondatorul nu mai e singurul on-call), poza crește cu monitoring, backup-uri și o poveste de deploy explicită. Iată cum arată forma de pasul următor:

Diagramă de creat: o diagramă de arhitectură „small SaaS architecture v1”, desenată în diagrams.net. Centru: un VM, cu trei cutii înăuntru etichetate „nginx”, „app process (gunicorn / puma / etc.)” și „Postgres”. La stânga: utilizatori finali conectându-se prin HTTPS printr-o cutie Cloudflare CDN. La dreapta: un cilindru separat „S3-compatible object storage” cu săgeți de la VM arătând backup-uri nocturne pg_dump (etichetă: „nightly, encrypted, 30-day retention”) și fișiere uploadate de utilizatori (etichetă: „uploads”). Sub VM: un cluster „Prometheus + Grafana” (acestea pot fi pe același VM sau pe un VM mic separat) cu săgeți în sus către VM arătând scraping de metrici. Sub asta: o cutie mică „Alertmanager / BetterStack” primind alerte și rutându-le către Slack/email. La dreapta-jos: un pipeline de deployment arătat ca trei cutii de la stânga la dreapta: „GitHub repo” -> „GitHub Actions (run tests)” -> „SSH deploy to VM (systemd restart)”. Folosește o schemă de culori blândă: VM în verde, storage în portocaliu, observability în albastru, deploy în gri. Grupează „production” într-o cutie albastru-deschis, „deployment” într-o cutie gri-deschis, „external services” în afara ambelor. Nu mai mult de 12 componente cu nume în total; diagrama ar trebui să încapă încă într-un singur ecran.

Diagrama aia reprezintă un SaaS mic cu adevărat production-grade. Multe companii cu venituri în intervalul EUR 1-10M rulează pe ceva nu foarte diferit. Nu are Kubernetes. Nu are service mesh. Nu are microservicii separate. Are lucrurile de care chiar are nevoie: TLS, backup-uri, monitoring, alerting și o cale de a duce cod din GitHub în producție de încredere.

Ce să NU adaugi încă

Lista asta e cea mai subapreciată lecție din lecția asta. Piața vrea să ți le vândă pe toate. Majoritatea nu aparțin într-un sistem care nu le-a câștigat încă.

Kubernetes. Până când chiar ai nevoie să rulezi mai multe servicii pe mai multe mașini cu auto-scaling, Kubernetes e o capabilitate plătită pe care n-o folosești, iar taxa operațională e reală. O echipă mică care rulează Kubernetes își petrece poate o treime din timpul ei de inginerie pe Kubernetes. Asta e o treime dintr-o echipă pe care n-o ai.

Microservicii. Un al doilea serviciu e ceva ce desprinzi când o graniță clară și durabilă a apărut în codebase și când costul împărțirii (deploy-uri separate, baze de date separate, comunicare async, distributed tracing) e justificat de beneficiu (scalare independentă, granițe de echipă, alegere de limbaj). La trei ingineri, beneficiul ăla aproape niciodată nu e acolo. Rămâi monolitic. Exercițiul de împărțire e teritoriul modulului 7 și o vom face cum trebuie.

Un cache layer separat (Redis ca cache). Postgres are un buffer pool. Majoritatea conversațiilor „am nevoie de un cache” la scară mică sunt de fapt conversații „am un index lipsă”. Adaugă mai întâi indexul, măsoară și adaugă Redis doar dacă poți numi query-ul specific care e suficient de fierbinte încât să justifice un layer separat.

O coadă separată (Redis, RabbitMQ, Kafka). Ca mai sus: Postgres-as-queue e excelent până la mii de joburi pe minut. Adoptă o coadă reală când ai un motiv real, nu înainte.

Un motor de search (Elasticsearch, OpenSearch, Meilisearch). Postgres full-text search e cu adevărat bun și gestionează probabil 80% din cazurile pentru care oamenii apelează la Elastic. Începe acolo; apelează la motorul dedicat doar când relevance scoring sau search multi-tenant la scală te forțează.

Read replicas. Până când primary-ul tău unic transpiră, o read replica e complexitate operațională fără beneficiu. Adaug-o când pg_stat_activity îți spune că citirile aglomerează scrierile, nu înainte.

O bază de date de analiză separată (Snowflake, BigQuery, ClickHouse). Până când query-urile de analiză interferează activ cu workload-ul de producție, rulează-le pe o read replica de Postgres. Ziua în care ai un warehouse de 4 TB și o echipă de date, ok. Ziua aia e mai târziu decât crezi.

Service mesh, sidecars, distributed tracing fabric. Toate utile la locul lor. Locul lor e departe în viitor pentru forma asta.

Principiul din spatele tuturor acestora e același: amână până când încărcarea o justifică. Arhitectura e managementul complexității, iar cea mai ieftină complexitate e cea pe care n-ai adăugat-o încă. Adăugarea unei piese de infrastructură e o ușă cu un singur sens în multe echipe; odată ce e în producție, scoaterea ei e un proiect.

Echipele pe care le-am admirat cel mai mult, lucrând pe sisteme cu utilizatori reali și venituri reale, au rulat toate ceva ce semăna foarte mult cu diagrama lecției ăsteia mult mai mult decât sugerează talk-urile de conferință că e posibil. Au scalat vertical, au tunat query-uri, au adăugat indexuri, au menținut suprafața operațională minusculă și au cheltuit timpul de inginerie economisit pe produs. Aia e forma plictisitoare și profitabilă.

Modulul următor începe călătoria de la arhitectura asta single-machine la sistemele care chiar o depășesc. Lecția 7 introduce modelul C4 în profunzime, ca să avem un vocabular comun pentru a desena aceste sisteme pe măsură ce devin mai complexe. Lecția 8 e primul pas de scalare: când o singură mașină nu mai e suficientă, ce vine după și cum să faci asta fără să pierzi simplitatea care a făcut prima versiune să funcționeze.

Pentru moment, dacă iei un singur lucru din lecția asta: cea mai mică arhitectură care îți rezolvă problema e aproape întotdeauna cea corectă. Poți întotdeauna adăuga complexitate mai târziu. Aproape niciodată n-o poți scoate.

Caută