Data persistence is the part of a system that must survive process and machine restarts. A storage choice fixes more than where bytes live: it sets the available consistency guarantees, the shape of efficient queries, and much of the operating cost. A weak isolation choice can corrupt business state under concurrency. An unnecessary cache creates a stale-read path that the original system never had.

Storage Options at a Glance

Each store makes a different access pattern cheap. Relational storage is the practical default because constraints and transactions keep invariants close to the data, while joins and ad-hoc queries leave room for the service to change. A narrower store earns its place when a measured workload needs its 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

Many systems keep a relational source of truth and add one specialized read path, such as a cache or search index. Every extra store needs synchronization and recovery work. Its workload and success metric should be named before it enters the design.

Block, File, and Object Storage by Access Contract

Block, file, and object storage differ in the unit the application controls. The workload depends on that contract, not the provider label.

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

data persistence data persistence

A database volume normally needs block storage for low-latency random I/O and crash ordering. A render farm needs file semantics because workers open and lock shared project files. Immutable 500 MiB videos served through a CDN fit object storage and its lifecycle controls. Calling all cloud storage “object storage” hides the failure boundary the application relies on.

Database Performance Diagnosis before Scaling

Start with the slow request and account for its time. 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.

data persistence data persistence

This sequence keeps the diagnosis tied to evidence. Jumping to cache, replicas, or shards may improve one graph while adding stale reads or duplicated writes that hide the original defect.

Database Scaling Escalation Ladder

Move down this ladder only when the previous step still misses 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

Several steps may be necessary, but their guarantees accumulate. A cached read from a lagging replica 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

data persistence data persistence

The categories overlap. CQRS may use materialized views, event sourcing may feed them, and each shard may maintain local indexes. The design still starts with the problem. Event sourcing does not replace a cache, and a secondary index does not partition a write bottleneck.

Questions

References

8 items under this folder.