Choosing a Storage Engine

You have designed your schema, added the right indexes, and decided which data to normalize and which to denormalize. Now comes the question that ties all of that work together: where does this data actually live?

Choosing a storage engine means picking the specific database technology that will store each piece of your system's data. It is one of the hardest architectural decisions to reverse — migrating a production database from PostgreSQL to DynamoDB, or extracting a feature into a time-series store, requires a data migration, service rewrites, and often downtime. The right time to make this decision is before you write a single line of code.

The decision has three inputs — all of which come directly from your data modeling work:

  1. Data shape: Is the data structured with clear relationships, or hierarchical and schema-flexible?
  2. Access patterns: What queries does the system actually run? Are they varied and ad-hoc, or specific and predictable?
  3. Consistency needs: Must every read reflect the latest write, or can temporary staleness be tolerated?

The wrong way to make this decision is picking a technology because it sounds modern or because it appeared in a viral blog post. The right way is matching your data's characteristics to the system designed to handle them.

The Storage Engine Landscape#

The era of "just use SQL" or "NoSQL is the future" has given way to a more nuanced landscape. Modern production systems use several storage engines together — each chosen for a specific type of data or access pattern.

Engine CategoryPrimary PurposeExamplesKey Trade-off
Relational (SQL)Structured data with relationships, complex queries, and ACID transactionsPostgreSQL, MySQLFlexible queries + strong consistency vs. limited horizontal write scale
Wide-Column NoSQLPredictable, high-throughput reads and writes on known access patternsDynamoDB, CassandraHorizontal scale vs. no ad-hoc queries — access patterns must be known upfront
Object StorageBinary files: images, videos, backups, AI model weightsS3, GCSInfinitely cheap and durable vs. no query capability on file contents
In-Memory StoreCaching, sessions, rate limiting, pub/sub — speed over durabilityRedisSub-millisecond latency vs. all data must fit in RAM
Vector DatabaseSimilarity search over AI embeddingspgvector, Pinecone, QdrantSemantic search capability vs. requires an embedding pipeline upfront
Search EngineFull-text search, faceted filtering, and relevance rankingElasticsearch, OpenSearchRich search features vs. eventual consistency and complex operations
Time-Series DBHigh-frequency timestamped metrics and eventsInfluxDB, TimescaleDBEfficient compression and time-range aggregation vs. narrow, single-purpose scope

The Building Blocks section covers SQL, NoSQL, and Object Storage in depth. This page focuses on how to decide between them — and introduces the specialized engines (Redis, vector databases, search, time-series) that most beginner tutorials skip.

The Three Questions#

Before choosing a storage engine for any piece of data, answer three questions. The answers come directly from the data modeling work you have already done.

The Storage Engine Decision Tree

Start with data shape — it immediately rules out entire categories. Then examine your access patterns to choose between SQL and NoSQL for structured data. Finally, let your consistency requirement be the tiebreaker when the first two questions don't give a clear answer.

Rendering diagram...

Question 1: Data Shape#

Data shape describes the structural characteristics of your data — whether it has a fixed schema, how complex its relationships are, and how much the structure varies from record to record. Your ERD from schema design is the primary answer to this question.

Data ShapeSignals in Your ERD or Access Pattern ListBest Fit
Structured, relationalMultiple tables with FK relationships, fixed column types, constraints, JOINs between entitiesPostgreSQL — the relationships are the data; the database enforces them
Wide and flatSingle primary-key lookup, all needed data co-located per row, few or no JOINs ever neededDynamoDB or Cassandra — designed for fast single-partition reads and writes
Document-shapedDeeply nested objects, optional fields that vary per row, structure legitimately differs record to recordMongoDB or JSONB in PostgreSQL — schema varies per row by design
Binary / unstructuredFiles: images, videos, PDFs, model weights — the data is read as a whole object, never filtered by contentObject storage (S3/GCS) — databases store metadata; object storage stores the bytes
Vector embeddingsArrays of floats representing semantic meaning from an AI modelpgvector extension in PostgreSQL, or a dedicated vector database at high scale
Timestamped metricsEvery row has a timestamp; queries aggregate over time ranges, downsample, or set retention policiesTimescaleDB or InfluxDB — purpose-built compression and time-range queries

The most common mistake here is forcing data into the wrong shape — normalizing something that is naturally hierarchical, or flattening something that is naturally relational. If your ERD has one table with 40 nullable columns where only a handful are set on any given row, your data is probably better modeled as a document. If your ERD has 12 tightly-related tables joined by foreign keys, a relational database is the right tool — do not force it into a flat key-value structure to chase hypothetical scale.

Question 2: Access Patterns#

Access patterns are the specific queries your application will run: how you read data, how often, under what filters, and in what order. This is the most important question for distinguishing between SQL and NoSQL.

SQL can answer any query you give it — the query planner figures out how to execute it. NoSQL requires you to define every query upfront and design the schema around those specific queries. The reason is architectural: NoSQL distributes data across many servers using a partition key (the field that determines which server stores a given row). A sort key optionally determines the ordering of rows within a partition. Every query must target a known partition key — otherwise the database would have to scan every server, which defeats the purpose. If you discover a new access pattern after your NoSQL system is in production, you may need a new Global Secondary Index (a separate copy of the data indexed by a different key) or a full table redesign.

SQL vs. NoSQL: Access Pattern Flexibility

SQL lets you design the schema first and write queries later. NoSQL inverts this: define every query first, then design the schema to serve those exact patterns. For most teams building a new product, SQL's flexibility has more value than NoSQL's theoretical scale ceiling.

Rendering diagram...

Question 3: Consistency Needs#

Consistency needs determine what level of data correctness your application requires when multiple things happen at the same time — concurrent writes, replica lag, or partial failures.

This question has two canonical answers:

  • Strong consistency (ACID — Atomicity, Consistency, Isolation, Durability): Every read reflects the most recent committed write. If you update a record, the next read returns the updated value — guaranteed. Required when stale data causes real harm.
  • Eventual consistency (BASE — Basically Available, Soft state, Eventually consistent): Replicas may lag behind the primary. Reads may return slightly stale data. Eventually, all replicas converge. Acceptable when the data is informational and temporary staleness is harmless.
Data TypeConsistency RequiredWhy
Bank balances, payment recordsStrong (ACID)A stale balance read can authorize a transaction that should be rejected — double-spend or overdraft
Inventory countsStrong (ACID)Two users simultaneously seeing 'stock: 1' can both complete checkout — the deduction must be atomic
User accounts, authentication tokensStrong (ACID)A deleted account must not remain accessible; a new password must immediately invalidate old sessions
Social media like/view countsEventual (BASE)Showing 1,423 likes instead of 1,424 for a few seconds causes no real harm
Shopping cart contentsEventual (BASE)A cart appearing empty for 100ms before catching up is acceptable in most applications
News feed, activity feedEventual (BASE)A post appearing 1–2 seconds later for some users is barely noticeable
Real-time metrics dashboardsEventual (BASE)A dashboard showing data from 30 seconds ago is fine — nobody requires millisecond-perfect numbers on a chart

The practical rule: If a user could lose money, be locked out of their account, or receive a duplicate order because of stale data, you need strong consistency. If the worst outcome is a number that is slightly behind for a few seconds, eventual consistency is acceptable.

SQL as the Correct Default#

Given the above framework, PostgreSQL is the correct default for the structured relational data in the vast majority of new applications. It is not a fallback or a "beginner" choice — it is the production-proven choice used by Shopify, Instagram, Notion, and Reddit at massive scale.

The question is not whether PostgreSQL can scale — it can handle millions of reads per day on a modern instance, and horizontal read scaling via read replicas extends this further. The question is whether your specific workload has characteristics that push beyond what a relational database can offer.

CharacteristicStay in PostgreSQLConsider Moving Out
Write throughputHundreds to low thousands of writes/second on a single primaryMillions of concurrent writes/second across many nodes with no natural foreign keys
Query complexityArbitrary JOINs, GROUP BY, subqueries, ad-hoc analyticsOnly single-partition key lookups are ever needed — no JOINs, ever
Schema stabilitySchema evolves with migrations over timeStructure varies legitimately per record — truly schemaless data
Data sizeTerabytes — well within PostgreSQL's range with proper vacuuming and archivingPetabytes of append-only time-series or event data where per-record storage cost matters
Geographic distributionSingle region, or primary + read replicas across a regionLow-latency writes required simultaneously across 3+ geographic regions

A well-designed PostgreSQL schema with proper indexes, connection pooling (PgBouncer), and read replicas will outlast most early-stage systems' scaling requirements. Don't introduce NoSQL complexity before you have the scale problem it solves.

Specialized Engines: Beyond SQL and NoSQL#

Once your core relational data is in PostgreSQL and any high-throughput key-value patterns are in DynamoDB or Cassandra, there are four specialized categories that solve problems neither SQL nor wide-column NoSQL are designed for.

Redis: The In-Memory Speed Layer#

Redis is an in-memory data store — all data lives in RAM, which gives it sub-millisecond read and write latency. It is not a primary database. It is a speed layer placed in front of your database for specific, high-frequency operations.

Redis: Cache-Aside Pattern

Redis sits between your application and your primary database. It absorbs repeated reads that would otherwise hit the database on every request. The cache-aside pattern is the most common: check the cache first; on a miss, fetch from the database and populate the cache for the next request.

Rendering diagram...

Vector Databases: The AI Data Layer#

Vector databases are purpose-built for a new kind of query that became central to AI applications: similarity search. Instead of asking "give me rows where title = 'Password Reset'" (exact match), you ask "give me the 10 records most semantically similar to this sentence."

AI models represent meaning as vectors — arrays of hundreds or thousands of floating-point numbers. Two semantically similar sentences produce vectors that are numerically close in this high-dimensional space. A vector database indexes these vectors and efficiently answers nearest-neighbor queries.

Ingestion (done once, offline)
Rendering diagram...
Query search (real-time)
Rendering diagram...

The central question in 2026 for vector search is whether to use pgvector (a PostgreSQL extension) or a dedicated vector database (Pinecone, Qdrant, Weaviate).

Factorpgvector (PostgreSQL extension)Dedicated Vector DB (Pinecone, Qdrant)
Best forVector search is one feature alongside relational data in the same applicationVector search is the core product; billions of vectors or thousands of queries per second
ScaleExcellent up to tens of millions of vectors with HNSW indexing (an approximate nearest-neighbor algorithm that trades a small accuracy loss for dramatically faster search), sub-100ms at 99% recallDesigned for billions of vectors with consistent sub-100ms latency at very high QPS
Combined queriesYes — JOIN vectors with relational data in a single SQL query (e.g., filter by user_id + similarity rank)Limited — metadata filtering is supported but complex relational joins require two separate queries
Operational complexityZero — lives inside your existing PostgreSQL instanceAdditional service to deploy, monitor, scale, and pay for
CostIncluded in existing PostgreSQL costsAdditional cost — Pinecone serverless starts at $0.096 per 1M query units
RecommendationDefault choice in 2026 — start here for all RAG and semantic search workloadsGraduate to this only when pgvector benchmarks show it cannot meet your latency or throughput requirements

Recent benchmarks (2025–2026) show PostgreSQL with pgvector HNSW indexing achieves sub-100ms maximum query latency at 99% recall for tens of millions of vectors — competitive with specialized vector databases for moderate workloads. QPS = queries per second.

Start with pgvector. You avoid introducing a separate system, and you can combine vector similarity search with relational filters in a single SQL query:

-- Find the 10 documents most similar to a query, for a specific user
SELECT id, title, content
FROM documents
WHERE user_id = 42
ORDER BY embedding <=> $query_vector   -- pgvector cosine distance operator
LIMIT 10;

Time-Series Databases#

A time-series database is optimized for a specific data shape: records with a mandatory timestamp where queries aggregate or filter over time ranges. This pattern is everywhere: infrastructure metrics (CPU, memory every 10 seconds), IoT sensor readings, application performance data (request latency per minute), and financial tick data.

PostgreSQL handles time-series data reasonably well with BRIN indexes (optimized for sequential timestamps), but two options go further:

  • TimescaleDB is a PostgreSQL extension that adds automatic time-based partitioning (hypertables), column-level compression (often 90%+ size reduction on old data), and continuous aggregates (pre-computed downsampled views that update automatically). It runs inside PostgreSQL — same connection string, same SQL, same tooling.
  • InfluxDB is a standalone time-series database built for extreme write volume — millions of data points per second. Used at the scale of infrastructure monitoring platforms and large IoT deployments.
Scale / Use CaseRecommendation
Application metrics for a small/medium system (<10,000 data points/second)TimescaleDB — PostgreSQL extension, zero new infrastructure
Infrastructure monitoring, IoT, financial tick data (>100,000 points/second)InfluxDB or a managed service (Datadog, AWS Timestream)
Time-series is a minor feature in an otherwise relational systemPlain PostgreSQL with a BRIN index on the timestamp column — sufficient for low-frequency data

Search Engines: Elasticsearch#

When users type into a search box and expect relevant results ranked by how well they match — not just exact string matches — a standard database index is not enough. Elasticsearch (and its open-source managed variant OpenSearch) provides:

  • Full-text relevance scoring: ranks results by how well they match a query, accounting for term frequency, document length, and fuzzy matching ("pasword" → "password")
  • Faceted filtering: "show all products where category = 'Electronics' AND price < $100, ordered by relevance" across millions of records

The cost: Elasticsearch is a separate, operationally complex system with eventual consistency. You typically write data to PostgreSQL (your source of truth) and sync a copy to Elasticsearch for search. Two systems, two writes, and a sync pipeline to maintain and monitor.

When the trade-off is worth it: Users expect search results ranked by relevance over large amounts of text — product catalogs, documentation, content libraries. When it is not: Your "search" is filtering by category or exact field match — PostgreSQL's ILIKE or the pg_trgm extension handles that without an additional system.

Polyglot Persistence: Using Multiple Engines Together#

Real production systems use multiple storage engines together — each chosen for a specific data type and access pattern. This is called polyglot persistence.

Polyglot Persistence: An E-Commerce Platform with AI Features

A realistic e-commerce platform uses five storage technologies. Each serves a specific type of data. PostgreSQL is the source of truth for structured relational data. Every other system either caches from it, holds synced copies for specific query patterns, or handles data that is genuinely non-relational.

Rendering diagram...

The right order to add each system:

  1. Start: PostgreSQL only. This handles 95% of applications correctly.
  2. When you add auth: Redis for session storage with TTL-based expiry.
  3. When users upload files: S3 for object storage from day one — retrofitting it later is painful.
  4. When you build AI features: pgvector for embeddings — already inside PostgreSQL, zero new system.
  5. When search quality is inadequate: Elasticsearch, with PostgreSQL as the sync source.
  6. When metrics volume overloads PostgreSQL: TimescaleDB extension first, then InfluxDB if extension is insufficient.

How AI Agents Get This Wrong#

AI agents make predictable storage selection mistakes. They have no visibility into your data shape, access patterns, or consistency requirements unless you explicitly provide them — so they fall back to defaults.

What AI Agents Do By DefaultThe ProblemWhat to Specify Instead
Use PostgreSQL for every type of data, including binary files and sessionsFile bytes end up as BLOB columns in the database; session data bloats the primary store under load"Store file uploads in S3. Save only the object key in PostgreSQL. Store sessions in Redis with a 24-hour TTL."
Never recommend Redis, Elasticsearch, or specialized engines unless askedNo caching layer; the database serves identical queries thousands of times per second, with no relief valve"Cache the 20 most recent posts per user in Redis (TTL: 60s). Use Elasticsearch for the product search endpoint."
Suggest a dedicated vector database (Pinecone, Qdrant) for any AI featureIntroduces an additional paid service and sync pipeline when pgvector handles the workload inside PostgreSQL"Use pgvector. Only consider Pinecone if pgvector HNSW benchmarks fall below 95% recall at our target QPS."
Recommend NoSQL 'for scale' on any project that mentions growthNoSQL operational complexity added with no concrete scale problem — queries become inflexible and access patterns must be frozen upfront"Start with PostgreSQL. Define the specific write throughput benchmark that would justify introducing DynamoDB."
Over-engineer from day one: Kafka, Cassandra, Elasticsearch, Redis, Pinecone simultaneouslyEnormous infrastructure overhead for a system that has 100 users and unknown access patterns"Start with PostgreSQL only. List the specific performance metrics that would trigger adding each additional system."

The most useful constraint to add to your architectural spec is the graduation trigger — the concrete, measurable condition that justifies moving data out of PostgreSQL into a specialized engine. Without it, AI agents either never recommend specialized systems or add them all immediately.

Example graduation triggers to put in your spec:

  • "If p99 read latency for the product catalog exceeds 50ms after indexes are optimized, evaluate Elasticsearch."
  • "If write throughput on the events table exceeds 5,000 writes/second sustained, evaluate TimescaleDB."
  • "If pgvector recall benchmarks fall below 95% at our target queries per second, evaluate Qdrant."

Summary#

DecisionFrameworkCommon AI Mistake
Default choicePostgreSQL for all structured relational data — handles most applications at production scaleRecommending NoSQL or complex architectures before any scale problem is proven
Data shapeStructured → PostgreSQL; Binary → S3; Vectors → pgvector; Metrics → TimescaleDBStoring binary files as BLOB columns in PostgreSQL; storing filterable fields in a document DB
SQL vs. NoSQLSQL if access patterns vary or are not yet fully known; NoSQL only for specific, stable, high-volume single-key patternsChoosing NoSQL for scale before measuring whether SQL is actually the bottleneck
ConsistencyStrong (ACID) for financial, auth, inventory data; Eventual (BASE) for feeds, counters, and preferencesApplying eventual consistency to data where a stale read causes real user harm
RedisSpeed layer for hot reads, sessions, rate limiting — never as a primary databaseOmitting a caching layer entirely, or using Redis as the primary data store
Vector DBpgvector first; graduate to a dedicated vector DB only when benchmarks show pgvector cannot meet requirementsImmediately adding Pinecone or Qdrant before trying pgvector
Polyglot persistenceAdd each additional engine only when a specific, measured bottleneck requires itPre-emptively adding all possible storage systems from day one
Graduation triggersDefine the concrete metric (latency, throughput, scale) that justifies moving data to a specialized engineAdding systems based on perceived need rather than measured limits

The storage decision is not a one-time choice — it evolves as your system grows. But the sequence matters: start with the simplest system that works, measure its actual limits, and move only the specific data type that needs a different engine. A five-database architecture is not evidence of good engineering — it is evidence that each component was added because the system genuinely outgrew the previous one.

Sources: