Data persistence is how software survives a restart: storing, retrieving, and protecting state across processes and machines. The choice between SQL, NoSQL, and caching layers shapes every system’s consistency guarantees, latency profile, and operational cost. Example: picking the wrong isolation level can silently corrupt data under concurrency, while an unnecessary cache adds a stale-read failure mode that did not exist before.

Storage Options at a Glance

Different stores optimize for different access patterns. Default to relational storage because constraints, transactions, joins, and ad-hoc queries preserve options while a service is still changing. Add or replace it only when a measured access pattern earns a narrower contract:

Store typeAccess pattern that earns itGuarantee or cost to verifyExamples
Relational (SQL)Default for changing business data, joins, constraints, and multi-row transactionsIndex and query-plan discipline; a single writer eventually becomes a scaling boundaryPostgreSQL, SQL Server
DocumentOne aggregate is normally read and replaced as a whole and its fields evolve independentlyCross-document constraints and joins move into application code or explicit transactionsMongoDB, Cosmos DB
Key-valueNearly every request knows one key and needs predictable low latencySecondary access paths require another index or duplicated record; hot keys can concentrate loadRedis, DynamoDB
Wide-columnWrites and range reads follow a stable partition plus clustering key at very large scaleQuery-first denormalization, repair, compaction, and partition balance become design workCassandra, Bigtable
GraphThe result comes from multi-hop traversal through relationshipsBulk aggregates and high-volume property scans are not its strengthNeo4j, Neptune
Time-seriesAppends and time-window aggregates dominate, with explicit retention/downsamplingLate data, cardinality, and corrections need product-specific handlingPrometheus, InfluxDB

Most systems combine a relational system of record with a cache, search index, or specialized read store on one proven hot path. That polyglot design adds synchronization, backup, security, and operational work for every extra store, so the specialized store should have a named workload and success metric.

Block, File, and Object Storage by Access Contract

These storage types differ by the unit an application controls. The provider name matters less than the access contract exposed to the workload:

ConcernBlock storageFile storageObject Storage
Access unitAddressed blocks presented as a volumeFiles and directories through a filesystem protocolWhole objects addressed by bucket/container and key
Namespace ownerAttached host or storage-aware application formats and manages itFilesystem manages hierarchical paths, permissions, and locksService manages a flat keyspace; clients emulate folders with prefixes
SharingCommonly attached to one writer; multi-attach needs filesystem coordinationDesigned for concurrent clients through NFS/SMB or a managed equivalentMany clients use HTTP APIs; no shared POSIX edit/lock contract
Update shapeLow-latency random reads and overwritesFile and byte-range operationsPut/replace an object; multipart upload handles large values
Consistency boundaryVolume and filesystem determine ordering and crash behaviorProtocol and service define visibility and lockingProvider defines single-key and listing behavior; multi-object transactions are application work
Application responsibilityFilesystem, snapshots, replication, and recoveryPath/permission design, lock behavior, and shared throughputKeys, metadata, checksums, lifecycle, versioning, and multi-object publication protocol
Typical fitDatabase pages, VM disks, transactional logsShared home directories, content tooling, lift-and-shift applicationsMedia, backups, artifacts, data lakes, immutable large values

A database volume needs low-latency random I/O and crash ordering, so block storage normally wins. A render farm that opens and locks shared project files needs file semantics. A service that writes immutable 500 MiB videos and serves them through a CDN needs object scale and lifecycle controls. Calling all cloud storage “object storage” hides the failure boundary the application actually depends on.

Database Performance Diagnosis before Scaling

Start with the slow request and follow its time, not a list of fashionable remedies. Suppose GET /orders/42 regresses from 80 ms to 600 ms at p95:

  1. Split request time into pool wait, query execution, lock wait, network, serialization, and downstream calls. Correlate the same interval with CPU, memory, IOPS, connection count, cache hit rate, rows scanned, and replication lag.
  2. Capture the actual SQL and representative parameters. Use the engine’s plan tooling, such as PostgreSQL EXPLAIN (ANALYZE, BUFFERS), to compare estimated rows, actual rows, scans, joins, spills, and I/O.
  3. Fix the smallest demonstrated cause: return fewer columns/rows, remove an N+1 path, add or correct an index, shorten a transaction, or change a schema/query shape. Re-run the same trace and load.
  4. If requests wait for connections while the database is healthy, correct leaks and size the pool against the whole fleet. A larger pool does not repair saturated storage or lock contention.
  5. Add the persistence-layer cache or a materialized read model only when repeated reads tolerate a declared freshness window. Add read replicas when reads dominate and replica lag is acceptable.
  6. Scale the node after the query path is sound. Partition or shard only when one node’s measured capacity, data size, or failure/recovery boundary remains the limiter.

This order preserves evidence. Jumping directly to cache, replicas, or shards can reduce one graph while adding stale reads, duplicated writes, and failure modes that obscure the original defect.

Database Scaling Escalation Ladder

Move down the ladder only when the previous step no longer meets a concrete load, latency, or recovery target:

StepDiagnostic triggerWhat it buysCost introduced
Query and access-path repairHigh rows scanned, bad estimates, N+1 calls, lock waits, or unnecessary payloadMore capacity from the existing system without changing its consistency modelIndexes add write/storage cost; query/schema changes need regression tests
Materialized view or denormalized read modelStable expensive join/aggregate dominates readsPrecomputed reads with predictable shapeRefresh logic, duplicate data, and a freshness boundary
Vertical scalingCPU, RAM, or storage throughput is saturated after query repairSame data model and transaction boundary on a larger nodeHigher failure concentration, finite ceiling, and larger restart/recovery events
CachingRepeated reads tolerate staleness and origin load is the bottleneckLower read latency and fewer origin requestsInvalidation, stampedes, eviction, and stale answers
Read replicasReads dominate; primary writes are healthyMore read capacity and additional failover optionsReplication lag, read-your-writes routing, promotion, and replica cost
ShardingOne writer or data set exceeds the largest acceptable nodeHorizontal write/storage distributionShard-key constraints, resharding, hot shards, and cross-shard transaction/query work

Combining steps is normal, but their guarantees do not compose for free. A cached read from a lagging replica now has two freshness delays; a denormalized view across shards needs an explicit delivery and replay protocol.

Data Management Pattern Map

NeedPatternMechanismCost to accept
Lower latency for repeated readsCache-asideRead cache first; load from the source on miss; invalidate after source writesStaleness window, miss storms, eviction, and another failure mode
Precompute expensive derived readsMaterialized viewStore a query result or projection and refresh it on a schedule or changeRefresh lag, extra storage, and failed/duplicate update handling
Separate read and write modelsCQRSCommands update a write model; queries use an independently shaped read modelSynchronization, messaging, and consistency boundaries
Keep historical source of truthEvent SourcingAppend events and rebuild current state by replay or snapshotsSchema evolution, replay cost, idempotency, and irreversible event history
Support an alternate lookupIndex table or secondary indexMaintain another key-to-record path for a known queryEvery write must update it; rebuilds and uniqueness need a protocol
Distribute data and write loadShardingRoute each partition key to one shardCross-shard work, resharding, skew, and hot keys

The categories overlap: CQRS can use materialized views, event sourcing can feed them, and shards can each maintain local indexes. Name the problem first. Event sourcing is not a cache strategy, and a secondary index is not a substitute for partitioning a write bottleneck.

Questions

References

11 items under this folder.