← All posts

Why We Chose PostgreSQL Over DynamoDB for a Multi-Cloud SaaS Platform

Every SaaS platform needs a database. For smplkit, the choice was more constrained than it first appeared — not because there aren’t enough options, but because multi-cloud portability eliminated most of them.

smplkit is built on AWS today, but the platform is designed to run on Azure and eventually Google Cloud. Customers care about where their data lives, and tying the data layer to a single cloud provider’s proprietary database service would make multi-cloud deployment a rewrite instead of a reconfiguration.

The Candidates

We seriously evaluated four options:

DynamoDB — AWS’s fully managed NoSQL database. Excellent for high-throughput, single-table-design workloads. The initial favorite because it’s truly zero-ops: no servers, no connection pools, no vacuuming.

MongoDB (via DocumentDB or CosmosDB) — a document database with a wire protocol that AWS DocumentDB and Azure CosmosDB claim to support. The appeal was “write to the MongoDB API, deploy on any cloud.”

CosmosDB (Mongo API) — Azure’s globally distributed database with MongoDB API compatibility. Evaluated specifically for the cross-cloud angle.

PostgreSQL — the open-source relational database, available as a managed service everywhere.

Why DynamoDB Was Eliminated

DynamoDB is excellent, but it’s AWS-only. There is no Azure DynamoDB, no Google DynamoDB. If smplkit needs to run on Azure, the entire data access layer — queries, indexes, capacity planning — would need to be rewritten for a different database.

For a product that’s committed to a single cloud, DynamoDB is a strong choice. For smplkit, it was a non-starter.

The MongoDB API Trap

The MongoDB wire protocol compatibility story across clouds sounds great on paper: write your application against the MongoDB API, deploy on DocumentDB on AWS, deploy on CosmosDB (Mongo API) on Azure. Same code, different cloud.

In practice, the compatibility gaps are significant enough to matter:

Aggregation pipeline. DocumentDB and CosmosDB both support a subset of MongoDB’s aggregation stages. Complex pipelines that work on MongoDB Atlas may silently return wrong results or fail on the compatible services. You won’t discover this in unit tests — it surfaces in production when a rarely-used code path hits an unsupported operator.

Transactions. Multi-document transactions work differently across implementations. MongoDB’s transaction model, DocumentDB’s subset of it, and CosmosDB’s multi-record transactions have different semantics, different isolation levels, and different failure modes.

Change streams. MongoDB’s change streams are used for real-time event-driven architectures. DocumentDB supports them (with caveats). CosmosDB has its own change feed mechanism with a completely different API.

Indexes. Index types and behaviors vary across implementations. Some index types available in MongoDB aren’t supported in DocumentDB or CosmosDB.

The promise of “write once, deploy anywhere” breaks down at the edges — and the edges are exactly where production bugs live. We’d be coding against a common subset of three different databases, none of which is the actual MongoDB we’d be testing against in development.

Why PostgreSQL Won

PostgreSQL solved the multi-cloud problem simply: every major cloud offers a first-party managed PostgreSQL service.

  • AWS: RDS PostgreSQL and Aurora PostgreSQL
  • Azure: Azure Database for PostgreSQL
  • Google Cloud: Cloud SQL for PostgreSQL

These are real PostgreSQL, not wire-protocol-compatible approximations. The same queries, the same transaction semantics, the same index types, the same everything. An application running against RDS PostgreSQL moves to Azure Database for PostgreSQL with a connection string change.

Beyond portability, PostgreSQL brought advantages that influenced the decision:

JSONB as an escape hatch. One of the initial draws of document databases was schema flexibility — the ability to store evolving data shapes without running migrations for every change. PostgreSQL’s JSONB type provides this inside a relational database. You get JSON storage with indexing (GIN indexes on JSONB fields), querying (->, ->>, @> operators), and the ability to promote frequently-queried JSON fields to proper columns when they stabilize.

Every table in smplkit’s schema has a data JSONB column that starts as an empty object. Fields that are experimental or evolving live in data. Fields that prove their importance get promoted to dedicated columns with types and constraints. This hybrid approach gives us document-style flexibility without sacrificing relational integrity.

Transactional guarantees. smplkit handles billing, subscriptions, API key management, and multi-step provisioning. These operations require transactions that are correct, not eventually consistent. PostgreSQL’s ACID transactions are battle-tested across decades of production use. We don’t worry about whether a transaction will behave differently on a different cloud.

Ecosystem depth. SQLAlchemy (Python ORM), Alembic (migrations), psycopg (driver), pgAdmin, DBeaver — the PostgreSQL tooling ecosystem is mature and comprehensive. Developer onboarding is faster because everyone has used PostgreSQL before.

The Schema Management Tradeoff

The one genuine advantage of document databases is not needing schema migrations. With PostgreSQL, schema changes require Alembic migration scripts: create the migration, review it, deploy it, run it against the database.

For smplkit, this tradeoff is acceptable. Schema migrations are a solved problem — Alembic autogenerates migration drafts by diffing SQLAlchemy models against the live database, and the migration runs automatically on service startup. The operational cost is low, and the benefit is a schema that’s explicit, versioned, and enforceable.

The JSONB data column provides an escape hatch for the cases where migration overhead isn’t worth it. New fields that might change shape can live in data until they stabilize, then get promoted. This gives us the best of both worlds.

The Decision

PostgreSQL is the primary data store for all smplkit services. JSONB provides document-style flexibility. Schema migrations are managed by Alembic. The database is deployed as RDS PostgreSQL on AWS, with a clear path to managed PostgreSQL on any cloud.

The decision has held up well. We haven’t encountered a single case where we wished we’d chosen a document database, and the JSONB columns have been used exactly as intended — as flexible storage for evolving data that doesn’t warrant dedicated columns yet.

smplkit is built on PostgreSQL for every service. Learn more about our architecture in the docs.