Databases

Choosing a database is choosing which problems you want to be easy. Every engine optimises for some access patterns and consistency guarantees at the expense of others. This page maps the territory and indexes the detailed notes.

Start with a default

For a new application whose requirements are not yet clear, start with PostgreSQL. It is relational, transactional, mature, and stretches surprisingly far: JSON documents, full-text search, geospatial data, time series and vector search all work in one engine. You can run it anywhere.

Move to something specialised when you can name the specific limit you have hit — not in anticipation of scale you do not have. Operating two data stores well is more than twice the work of operating one.

The families

FamilyData modelGood atWeak atExamples
RelationalTables, rows, joins, SQLTransactions, integrity, ad hoc queriesHorizontal write scalingPostgreSQL, MySQL, SQL Server
Distributed SQLRelational, sharded and replicatedSQL with horizontal scale and multi-region consistencyLatency per transaction, cost, complexitySpanner, CockroachDB, YugabyteDB, Aurora DSQL
Key-valueKey → opaque valueVery fast lookups by key, simple scalingQueries by anything other than the keyRedis, DynamoDB, etcd
DocumentJSON-like documentsFlexible schemas, aggregates read and written wholeJoins, multi-document invariantsMongoDB, Firestore, DocumentDB
Wide-columnRows with sparse, dynamic columnsHuge write volumes, known query patternsAd hoc queries; data must be modelled per queryCassandra, Bigtable, ScyllaDB
Columnar / OLAPData stored by columnAggregations over billions of rowsSingle-row lookups and updatesClickHouse, BigQuery, Redshift, Snowflake
SearchInverted indexesFull-text search, relevance, facetingBeing the system of recordOpenSearch, Elasticsearch
Time seriesTimestamped measurementsAppend-heavy ingest, downsampling, retentionGeneral-purpose queriesPrometheus, InfluxDB, TimescaleDB
GraphNodes and edgesTraversing relationships many hops deepBulk analytics, simple CRUDNeo4j, Neptune
VectorHigh-dimensional embeddingsSimilarity searchEverything elsepgvector, Pinecone, Qdrant, Milvus
Log / streamAn append-only, replayable logDecoupling producers and consumers, event historyRandom access and queriesKafka, Kinesis, Pulsar

The trade-offs underneath

OLTP or OLAP. Transactional workloads read and write a few rows at a time and need low latency. Analytical workloads scan millions of rows and aggregate. Row stores serve the first, column stores the second — see columnar databases. Running heavy analytics on the transactional database is a classic cause of outages; replicate to an analytical store instead, for example with change data capture.

Consistency. A single-node relational database gives ACID transactions. Once data is replicated across machines, the CAP theorem applies: during a network partition a system either refuses some requests or serves possibly stale data. Know which your database does, and what its default settings actually guarantee.

Scaling. Scale up first; it is simple and modern machines are very large. Then add read replicas for read-heavy workloads. Sharding — splitting data across independent nodes — is the last resort, because it pushes complexity into the application: cross-shard queries, transactions and rebalancing. Background in scalability and hashing.

Schema. “Schemaless” databases still have a schema; it just lives in application code instead of the database. Enforcing it in the database catches bad data at write time; enforcing it in code makes changes faster and failures later.

A selection method

  1. Write down the access patterns: the ten most important reads and writes, with expected rates and sizes.
  2. State the consistency needs per pattern. Money and inventory need transactions; a view counter does not.
  3. Estimate volume and growth honestly — capacity planning.
  4. Check whether the default handles it. It usually does.
  5. Consider operations: who runs it, backs it up, upgrades it and is paged for it. Prefer a managed service unless there is a reason not to.
  6. Plan the exit: how would data be migrated out? See data migration.

Common combinations that work well: PostgreSQL as the system of record, Redis for caching and ephemeral state, Kafka to move data between systems, and a columnar store for analytics.

Notes in this section

Relational fundamentals

Engines

Around the database

Managed services

ProviderRelationalKey-value / cacheDocument / wide-column / other
AWSRDS, AuroraDynamoDB, ElastiCacheDocumentDB, Neptune, Redshift
AzureAzure SQLAzure Cache for RedisCosmos DB
GCPCloud SQL, SpannerMemorystoreBigQuery, Firestore, Bigtable

Running databases on Kubernetes is possible with operators and persistent volumes — see StatefulSets and EBS on EKS — but a managed service is the better default unless you have the operational depth.

Further reading