PostgreSQL vs MongoDB. Kiedy SQL, kiedy NoSQL, i dlaczego prawie zawsze wybieram Postgresa
W 2018 założyłem, że MongoDB jest “nowoczesne”, a PostgreSQL “starocia”. W 2020 migrowałem trzy projekty z Mongo na Postgresa. W 2026 każdy nowy projekt klienta buduję na PostgreSQL, chyba że jest naprawdę dobry powód, żeby pójść inaczej.
Powód migracji? Każdy projekt po roku w Mongo miał te same problemy: niespójność danych, magiczne błędy w query, brak ACID, niemożność robienia sensownych joinów. Po przejściu na PostgreSQL te problemy znikały.
Ten post nie jest hejtem na MongoDB. Mongo ma swoje miejsce. Ale to miejsce jest węższe niż marketing sugeruje. Pokażę konkretnie, kiedy SQL ma sens, kiedy NoSQL, i jak wybrać dla swojego projektu.
Krótka odpowiedź
PostgreSQL dla 90% projektów. Aplikacje webowe, API, e-commerce, CRM, SaaS, prawie wszystko, co ma strukturę relacyjną (a ma, częściej niż się wydaje).
MongoDB dla specyficznych use case’ów: dane bez sztywnej struktury (logi, metadane z różnych źródeł), wymagana ekstremalna skalowość horizontalna z dnia 1, content management gdzie schema musi być fluid.
Inne bazy (Redis, DynamoDB, Elasticsearch) jako uzupełnienie, nie zamiast głównej bazy.
Jeśli zaczynasz nowy projekt i nie wiesz, weź PostgreSQL. Trudno żałować.
Skąd mit “NoSQL = nowoczesne”
W 2010-2015 NoSQL bazy były promowane jako “next generation”. Argumenty:
- “Skalowanie horyzontalne” (Mongo radzi sobie z dużymi danymi)
- “Brak sztywnej schemy” (elastyczność rozwoju)
- “Lepsze dla developera” (dokumenty JSON są intuicyjne)
- “JSON, nie tabele” (lepsze dla JavaScript)
Każdy z tych argumentów był częściowo prawdziwy w 2012. W 2026:
- PostgreSQL skaluje się świetnie do TB-ów. 90% projektów nie potrzebuje horizontalnej skalowości.
- Brak schemy okazał się problemem, nie zaletą (kolejny rozdział)
- ORM-y typu Prisma sprawiły, że pisanie SQL też jest “JS-friendly”
- PostgreSQL ma natywny JSON support równie dobry jak Mongo
NoSQL nie był pomyłką. Był rozwiązaniem konkretnych problemów (Google, Facebook scale), które zostało nadinterpretowane jako universal solution.
Strukturalność danych: NIEZBĘDNA, nie opcjonalna
Główny argument za Mongo: “Dane są elastyczne, schemy nie potrzeba”.
Po roku w produkcji:
- 30% kolekcji ma niespójne pola (część rekordów ma
userId, częśćuser_id, częśćuser.id) - Trafiasz na rekordy z lat 2020-2022, które miały inną strukturę, niepasującą do dzisiejszego kodu
- Frontend dostaje czasem string, czasem number, czasem null w tym samym polu
- Walidacja musi być wszędzie w kodzie aplikacji, bo baza nie sprawdza
W PostgreSQL z proper schema:
- Każdy rekord pasuje do typu
- Migrations to celowe zmiany, nie przypadkowe rozjazdy
- Baza wymusza spójność, kod jej nie utrzymuje
- Typy w bazie matchują typy w kodzie (TypeScript dostaje pewność)
Schema-less brzmi atrakcyjnie tylko teoretycznie. W praktyce po 12 miesiącach jest piekłem.
ACID: dlaczego ma znaczenie
ACID (Atomicity, Consistency, Isolation, Durability) to gwarancje, których PostgreSQL daje, a Mongo daje warunkowo (niektóre tylko w replica sets, niektóre w specyficznej konfiguracji).
W praktyce:
Atomicity: transakcja albo cała się udała, albo cała się cofnęła. Postgres tak. Mongo tak (od 4.0, ale wolniejsze).
Consistency: po commitcie wszystkie reads widzą nowe dane. Postgres tak. Mongo eventual consistency by default.
Isolation: transakcje nie widzą siebie nawzajem (poziomy isolation). Postgres pełen support. Mongo ograniczone.
Durability: po commitcie dane są na dysku, nie zgubią się. Postgres tak. Mongo wymaga writeConcern: majority.
Dla aplikacji finansowej (płatności, zamówienia, rachunki) ACID jest nie do negocjacji. Mongo dostarcza, ale z większym overhead i corner casami. Postgres dostarcza out of the box.
Pisałem o Strapi - tam też wybór padł na PostgreSQL, choć Strapi wspiera Mongo. Powód: spójność danych.
Joiny: SQL wygrywa miażdżąco
W każdej aplikacji prędzej czy później potrzebujesz danych z kilku tabel naraz. “Pokaż mi wszystkie zamówienia użytkownika z produktami i kategoriami”.
SQL:
SELECT u.name, o.total, p.name, c.title
FROM users u
JOIN orders o ON o.user_id = u.id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON oi.product_id = p.id
JOIN categories c ON c.id = p.category_id
WHERE u.id = 123
Jedno query, baza optymalizuje, dostajesz strukturyzowane dane.
MongoDB:
- Embed (denormalizuj) wszystko w dokumencie user. Problem: duplikacja, hard to update.
- Albo
$lookup(aggregation pipeline), który robi joiny, ale jest 5-10x wolniejszy niż SQL joins na podobnych danych. - Albo manualne joiny w kodzie (N+1 problem).
Mongo było projektowane z założeniem “denormalizuj wszystko”. W praktyce po 6 miesiącach to się rozjeżdża.
Indeksy: oba mają, Postgres lepsze
Indeksy decydują o performance.
PostgreSQL:
- B-tree (default)
- Hash (dla equality lookups)
- GIN (full-text, JSON, arrays)
- GiST (geometryczne, ranges)
- BRIN (dla bardzo dużych tabel z naturalnym sortowaniem)
MongoDB:
- B-tree (default)
- Hashed (dla sharding)
- Text (full-text basic)
- Geospatial
- Wildcard
Postgres ma bardziej wyspecjalizowane typy indeksów. Plus EXPLAIN ANALYZE jest dramatycznie bardziej czytelne niż MongoDB explain.
W praktyce: dla 90% projektów wystarczy B-tree i obie bazy są comparable. Dla skomplikowanych queries Postgres daje lepsze opcje.
JSON w PostgreSQL: best of both worlds
Mit: “Mongo dla JSON, Postgres dla relacji”.
Rzeczywistość: PostgreSQL ma natywne typy JSON i JSONB (binary, zindeksowane).
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
metadata JSONB
);
INSERT INTO products (name, metadata) VALUES
('Laptop', '{"brand": "Dell", "specs": {"ram": 16}}');
SELECT * FROM products
WHERE metadata->>'brand' = 'Dell'
AND (metadata->'specs'->>'ram')::int >= 16;
JSONB w Postgresie:
- Indeksowalny (GIN index na polach JSON)
- Querysowalny w SQL
- Strukturyzowany WHERE potrzeba
- Reszta tabeli ma sztywne typy
To eliminuje główny argument za Mongo. Część kolumn JSON, część typowane = best of both.
Mój standard: kolumny strukturalne (id, name, status, dates) jako sztywne typy. Pola elastyczne (metadata, config, settings) jako JSONB. 95% projektów to pokrywa.
Performance: oba szybkie, oba wolne
W realnych benchmarkach (nie tych z 2012):
Read performance: porównywalne, jeśli oba dobrze zaindeksowane.
Write performance: Mongo nieznacznie szybsze dla pojedynczych insertów. Postgres szybszy dla batch inserts.
Complex queries: Postgres wygrywa miażdżąco dzięki joinom i query planner’owi.
Aggregations: Mongo aggregation pipeline jest mocne, ale Postgres window functions plus CTEs są równie mocne, czytelniejsze.
Bottom line: oba pokryją performance need 99% projektów. Nie wybieraj bazy na podstawie hipotetycznych benchmarków.
Skalowanie: różne paradygmaty
Vertical scaling (mocniejszy serwer): oba dobrze. Postgres do TB, Mongo do TB.
Horizontal scaling (więcej serwerów):
- Postgres: read replicas łatwe, write sharding trudne (Citus, partitioning, Postgres-XL).
- Mongo: sharding built-in, projektowane do tego.
Jeśli Twój projekt potrzebuje horizontalnego write scalingu od dnia 1 (rzadko), Mongo daje to łatwiej.
Reality check: 99% startupów umiera, zanim dotrze do skali wymagającej horizontalnego shardingu. Optymalizacja pod tę skalę jest przedwczesna.
ORMs: które lepsze
PostgreSQL ORMs:
- Prisma: najlepszy w 2026, typesafe, świetna DX, dobra dokumentacja
- Drizzle: lżejszy niż Prisma, bardziej SQL-like
- TypeORM: starszy, więcej legacy bagażu
MongoDB ORMs:
- Mongoose: standard, masyw, mocno opinionated
- Prisma (też wspiera Mongo): trudniej, mniej feature’ów niż dla PG
W TypeScript-first projektach Prisma plus PostgreSQL to top-tier DX. Generuje typy z migrations, sub-second response time, świetne tooling.
Kiedy WYBRAĆ MongoDB (mimo wszystko)
Nie hejtuję, są use case’y:
Document-heavy CMS, gdzie schema się zmienia często. Headless CMS-y własnej roboty, content management dla różnych formatów.
Logi, telemetria, eventy. Wysokowolumenowe writes, ad-hoc queries, brak relacji. Może lepiej Elasticsearch albo ClickHouse, ale Mongo też pasuje.
Mobile-first apps z offline sync. MongoDB Realm jest świetny do mobile offline-first scenariuszy.
Dane geograficzne z mocnym geospatial querying. Mongo ma świetne geospatial features (Postgres też ma, ale Mongo jest bardziej out-of-the-box).
Granular permissions na poziomie dokumentu. MongoDB ma mocniejszy support dla field-level security.
W mojej praktyce: 1 na 20 projektów ma realny powód wziąć Mongo. Reszta to Postgres.
Inne bazy, które warto znać
Redis: in-memory key-value. Cache, session storage, rate limiting, queues. Nie zamiast głównej bazy, jako uzupełnienie.
Elasticsearch: search engine. Full-text search z fuzzy matching, faceted search. Indeksujesz dane z Postgres do Elastic dla search interfejsu.
ClickHouse: columnar OLAP, analytics. Bilionów rzędów dla dashboards i reports. Postgres dla aplikacji, ClickHouse dla analytics.
DuckDB: embedded analytics. SQLite dla analityka. Świetny do local data processing, ad-hoc queries.
SQLite: embedded relational. Świetny dla aplikacji desktop, mobile, prototypów. Niedoceniany dla małych SaaS.
W typowym stacku agencji mam: Postgres jako main + Redis dla cache/queues. Reszta zależy od projektu.
Migracja z Mongo na Postgres
Jeśli masz projekt na Mongo i myślisz o migracji:
Krok 1: zaprojektuj relational schema dla danych. Często zaskakująco prostsze niż się wydaje.
Krok 2: napisz migration script. Read z Mongo, write do Postgres. Batchami, z idempotencją.
Krok 3: napisz dual write w aplikacji. Każdy write idzie do obu baz. Po jakimś czasie sprawdzasz, że dane się zgadzają.
Krok 4: flip reads. Najpierw nowe endpointy z Postgres, potem stare. Mongo zostaje “backup”.
Krok 5: po miesiącu stabilności wyłącz writes do Mongo. Po kolejnym miesiącu wyłącz Mongo cluster.
Czas migracji: średni projekt 2-4 tygodnie pracy. Większy projekt 2-4 miesiące. Często warto, jeśli boli już regularnie.
Najczęstsze błędy
Wybór bazy na podstawie hype’u. “Wszyscy używają X”. Wszyscy są inni.
Brak indeksów. Po roku produkcji query, które kiedyś trwało 10ms, teraz trwa 5 sekund. Bo dane urosły, nie zaindeksowane.
Premature sharding. Stawiasz horizontal scaling, zanim faktycznie potrzeba. Komplikujesz architekturę bez korzyści.
N+1 queries. ORM ładuje 1 user, potem 1 query na każdą jego order. 100 userów = 101 queries. Użyj eager loading.
Brak migracji w version control. “Trzymamy schema w głowie”. Każdy update produkcji to mistrz, bo zmiany ad hoc. Migracje w gicie, deployable jak kod.
Trzymanie sekretów w bazie. API keys, passwords plain text. Hashing dla haseł, separate secrets store dla API keys.
Brak backupów. Twoja baza pada, masz 0 backup. Klasyk. Postgres ma pg_dump, automate, test restore co miesiąc.
Co czytać dalej
- “The Art of PostgreSQL” Dimitri Fontaine. Najlepsza książka o Postgres, jaką czytałem.
- “Designing Data-Intensive Applications” Martin Kleppmann. Klasyk o systemach danych, niezbędne dla architekta.
- PostgreSQL official docs. Najlepsze docs ze wszystkich baz.
Nie czytaj książek o NoSQL z 2014. Era się skończyła, większość zaleceń zdezaktualizowała się.
Od czego zacząć
Jeśli budujesz nowy projekt:
- Domyślnie PostgreSQL. Zmień zdanie tylko, jeśli masz konkretny powód (z listy wyżej).
- Użyj Prisma dla TypeScript (lub Drizzle dla bardziej SQL-like control).
- Hostuj na Supabase, Neon albo Vercel Postgres dla małych projektów (managed, free tier).
- Dodaj Redis jak potrzebujesz cache/queues.
- Pisz migracje od dnia 1. Każda zmiana schemy = migration w gicie.
Postgres nie jest sexy. Jest niezawodny. Po latach wybierania exotic tech, niezawodność wygrywa z koolskością.
Powiązane wpisy
-
Vibe coding bez bullshitu. Czy można naprawdę zbudować produkcyjną apkę gadając z AI
"Vibe coding" to termin, który Andrej Karpathy ukuł na początku 2025. Definicja: piszesz aplikacje, gadając z AI po a...