Why we chose PostgreSQL over DynamoDB for a multi-cloud SaaS platform
Every SaaS platform needs a database, and for smplkit one requirement eliminated most of the options up front: multi-cloud portability. We’re on AWS today and designed to run on Azure and eventually Google Cloud, because customers care where their data lives. Tie the data layer to one provider’s proprietary database and multi-cloud stops being a reconfiguration and becomes a rewrite.
The candidates
Three made the shortlist. DynamoDB, the initial favorite, because it’s genuinely zero-ops — no servers, no connection pools, no vacuuming. MongoDB via the wire-protocol-compatible services (AWS DocumentDB, Azure CosmosDB’s Mongo API), for the “write to the MongoDB API, deploy on any cloud” promise. And PostgreSQL, available as a first-party managed service everywhere.
Why DynamoDB was eliminated
DynamoDB is excellent and AWS-only. There is no Azure DynamoDB. If smplkit needs to run on Azure, the entire data access layer — queries, indexes, capacity model — gets rewritten against a different database. For a product committed to one cloud, DynamoDB is a strong choice; for us it was a non-starter, which was disappointing, because “zero-ops” is a beautiful phrase to give up.
The MongoDB API trap
The compatibility story sounds great on paper: write against the MongoDB API, run DocumentDB on AWS, CosmosDB on Azure. Same code, different cloud.
In practice the gaps are big enough to matter. The aggregation pipeline is a subset on both DocumentDB and CosmosDB — a complex pipeline that works on real MongoDB can fail or silently return wrong results on the compatible services, and you won’t find out in unit tests; you’ll find out when a rarely used code path hits an unsupported operator in production. Multi-document transactions have different semantics, isolation levels, and failure modes across the three implementations. Change streams work on MongoDB, work with caveats on DocumentDB, and are a completely different API on CosmosDB. Index types vary too.
So the “write once” promise means coding against the common subset of three databases, none of which is the actual MongoDB you’d develop against. The promise breaks down at the edges — and the edges are exactly where production bugs live.
Why PostgreSQL won
PostgreSQL solves multi-cloud by brute fact: RDS and Aurora on AWS, Azure Database for PostgreSQL, Cloud SQL on GCP — all real PostgreSQL, not wire-protocol approximations. Same queries, same transaction semantics, same index types. An application on RDS moves to Azure with a connection string change.
The rest of the case:
JSONB is the document database we would have wanted anyway. The genuine appeal of document stores is schema flexibility, and PostgreSQL provides it inside the relational model: JSON storage with GIN indexing, query operators (->, ->>, @>), and a promotion path. Every table in smplkit’s schema has a data JSONB column that starts as an empty object. Experimental and evolving fields live in data; fields that prove their importance get promoted to dedicated columns with types and constraints. Document-style flexibility, relational integrity, no second database.
Transactions that don’t need an asterisk. smplkit handles billing, subscriptions, API keys, and multi-step provisioning — operations that need to be correct, not eventually consistent. PostgreSQL’s ACID transactions have decades of production miles on them and behave identically on every cloud.
Ecosystem depth. SQLAlchemy, Alembic, psycopg, and every GUI client ever written. Onboarding is faster when the answer to “have you used our database?” is always yes.
The schema management tradeoff
The one genuine advantage document databases keep is not needing migrations. With PostgreSQL, schema changes mean Alembic scripts: generate, review, deploy. We accepted that trade without much agonizing, because migrations are a solved problem — Alembic autogenerates drafts by diffing the SQLAlchemy models against the database, and migrations run automatically on service startup. The cost is low and the payoff is a schema that’s explicit, versioned, and enforceable, with the JSONB data column as the escape hatch for fields that aren’t stable yet.
The decision has held up. We haven’t hit a case where we wished we’d chosen a document database, and the JSONB columns get used exactly as intended — as a waiting room for fields that haven’t earned a column.