Proper Mindset for Handling Data and Databases: Between Scaling and Failing
DevOps Engineering

Proper Mindset for Handling Data and Databases: Between Scaling and Failing

Apply database design principles that support scaling and prevent operational failures.

Arthur Azrieli

5 min read

Data is where scaling stops being abstract. The pressure usually arrives in an inconvenient form: an executive dashboard times out, checkout slows because a worker is holding locks, a migration blocks a release, a model-backed feature serves stale answers, or the cloud bill jumps and nobody can tie the spend to a tenant, query, index, queue, export, or batch job. That pressure creates a familiar reflex: pull the lever that is easiest to see. Resize the instance. Add a cache. Create a read replica. Move analytics away from the primary. Partition a table. Queue the work. Tune the connection pool. Upgrade the managed tier. Any of those moves can be right, but none is a strategy by itself. Without evidence, they can hide the real constraint, add inconsistent reads, shift toil to another team, and make the next incident harder to diagnose. Before the pager chooses the architecture, the team needs a shared operating model for data. Which dataset is authoritative? Who owns it? How is it restored? How much data can the business lose? How stale may a read be? Which data must be deleted, masked, retained, encrypted, excluded from analytics, or kept out of AI training, retrieval, and evaluation flows? What should the product do when the data layer is slow, overloaded, inconsistent, regionally unavailable, or returning partial results? The goal is not to pick the perfect database on day one or turn every schema change into a committee meeting. The goal is to make tradeoffs explicit, keep rollback and restore paths usable, and treat data scaling as normal engineering work instead of emergency improvisation. Strong teams still move quickly. They just stop pretending that performance, correctness, cost, privacy, compliance, and operability can be solved in separate conversations.

Catch Data Architecture Regrets While They Are Still Cheap

Data is not a passive payload that the application happens to store. It is operational state, customer trust, compliance exposure, audit evidence, product behavior, and future engineering leverage. Managed databases, caches, queues, warehouses, search indexes, feature stores, and vector stores reduce toil only when the hard decisions are explicit: who owns each dataset, who can access it, how long it is retained, how it is deleted, how it is restored, and which system wins when copies disagree. The serious failures usually begin before the outage. A table nobody owns becomes a migration nobody reviews. A queue without replay rules becomes a data-loss argument during an incident. A backup policy can look healthy in a console and still fail because the restore account lacks permissions, the encryption key rotated, the private route changed, the service identity was recreated, required extensions are missing, or the target environment no longer resembles production. Treat the data layer as product-critical infrastructure, not a storage detail. Before an incident bridge opens, teams should be able to answer:

  • Who owns the schema, access model, retention policy, deletion workflow, restore process, and downstream contracts?
  • Which changes can lock tables, rewrite large datasets, break consumers, invalidate assumptions, or expose sensitive data?
  • Which metrics prove that reads, writes, replication, sync jobs, queues, indexes, backups, restore drills, and deletion flows are healthy?
  • How are restores tested with realistic permissions, routes, encryption keys, service identities, feature flags, seed data, dependencies, and data volumes?
  • What happens when legal, customer success, security, finance, or product asks where data lives, who touched it, and how it is removed?
Keep the surrounding platform boring too. Staging does not need production data, and often should not have it, but it does need production-shaped permissions, secret patterns, network paths, service accounts, migration workflows, observability hooks, and data-adjacent dependencies. Teams that invest in production-shaped environments catch broken assumptions before a release or outage forces the issue. The practical rule is simple: every important dataset needs an accountable owner, every critical path needs an observable failure mode, every risky change needs a rollback or forward-fix path, and every recovery promise must be tested before someone depends on it.

The classic optimization playbook still works, but the order matters. Do not start with a cache, read replica, stream processor, materialized view, warehouse export, search index, vector store, or extra queue just because the system feels slow. First prove that the current path is understood. Does the schema match the queries the product actually runs? Can the team connect a slow user action to traces, database metrics, logs, query plans, lock waits, connection-pool behavior, retries, saturation signals, and downstream symptoms? Or is the plan based on an incident screenshot whose assumptions expired after a new tenant, workflow, report, billing rule, region, or AI feature shipped? Test against production-shaped behavior, not a clean staging dataset with three friendly users. Include real concurrency, background jobs, imports, tenant imbalance, long-tail queries, hot partitions, retry storms, data skew, backfills, schema migrations, and autoscaling side effects. Then check the limits that decide whether an incident stays boring: connection caps, IOPS ceilings, burst credits, memory pressure, replication lag, queue depth, DNS latency, storage throttling, maintenance windows, managed-service API rate limits, identity lookups, secrets retrieval, client-side timeouts, retry budgets, and cross-region dependencies. Restore backups on a schedule as well. A backup that has never been restored is not a recovery plan; it is a hopeful checkbox. Every new component needs an owner, a cost curve, a runbook, and a failure model. For each proposed optimization, ask:

  • What evidence proves this is the bottleneck rather than a symptom?
  • Which user-facing and system-level metrics should improve, and how much improvement is enough?
  • What correctness, freshness, privacy, ordering, idempotency, or replay risk does the change introduce?
  • How does rollback or forward-fix work if the change makes things worse?
  • How are stale, partial, duplicated, reordered, or missing results handled?
  • Who operates it during an incident, and which alert tells them what decision to make?
A cache without invalidation rules, a replica without lag alerts, a warehouse sync without ownership, or a vector index without rebuild and freshness expectations is not an optimization. It is incident debt with a cleaner architecture diagram. In platform-heavy environments, keep the investigation wider than the database. Kubernetes scheduling delays, noisy neighbors, brittle IAM changes, throttled storage, exhausted connection pools, DNS failures, certificate rotation, autoscaling lag, sidecar overhead, regional degradation, and drift between staging and production can all make the database look guilty when the constraint sits elsewhere. Start with a baseline, change one meaningful variable, measure user-facing and system-level effects, and only then add another moving part. Before choosing a lever, use these checks to keep the decision grounded:

  • Indexing — add indexes for access patterns that have proved they matter: frequent lookups, joins, filters, uniqueness checks, foreign-key enforcement, tenant scoping, pagination, and sorts on hot paths. An index is not free speed. It can add write latency, consume storage, change lock behavior, increase backup and restore time, worsen replication lag, and affect vacuuming, compaction, failover, or maintenance windows. Validate index changes with real query plans and production-like data before calling them safe. Small fixtures hide cardinality, skew, tenant imbalance, parameter-sensitive plans, and pagination behavior that appears only at scale. Confirm that the optimizer will use the index with realistic bind values, mixed workloads, current statistics, and the same query shape the application sends in production. Be skeptical of low-selectivity columns, duplicate indexes, wide composite indexes nobody fully uses, expression indexes tied to application quirks, and indexes created for rare admin reports outside the critical path. For large tables, ship an index like an application release. Confirm whether the engine can build it online, estimate duration, watch locks and replication lag, define rollback or abandonment criteria, and avoid heavy maintenance during peak traffic, scheduled batch windows, import periods, billing runs, or backfills. In multi-tenant systems, test the largest tenants and noisiest query shapes, not only the median case. In event-heavy or AI-adjacent systems, check whether new indexes slow ingestion, embedding refreshes, reconciliation jobs, downstream replication, or restore time. After deployment, do not stop at the faster query. Compare before-and-after plans, monitor write latency and storage growth, verify that the target path improved for real users, and confirm that the index is not harming adjacent workloads. Track whether the index remains useful after feature changes, query rewrites, retention changes, and tenant growth. Remove unused indexes deliberately, with the same care used to add them. A good index is tied to a measured workload, a known query plan, a safe rollout window, and an owner who will delete or reshape it when the workload changes.
  • Normalization — normalize when correctness, consistency, and clear ownership matter more than raw read speed. It is usually the right default when multiple services or jobs update related records, transactions need clean boundaries, or an incident responder must know which row represents the truth. Denormalization is not a design failure. It can be the right choice for low-latency reads, reporting, search, analytics, event-driven projections, offline exports, high-volume product views, feature generation, and AI retrieval workflows. The risk starts when duplicated data is added as a shortcut instead of managed as a contract. A derived copy is safe only when the team can name the source of truth, define acceptable freshness, rebuild the copy from scratch, and detect drift. Before adding a denormalized table, projection, cache, search document, feature store, or vector index, decide which copy wins, how updates propagate, how consumers detect stale data, how deletes and privacy rules apply, and how inconsistencies are repaired after a failed job, partial deploy, delayed event, schema migration, permission change, restore, or backfill. Also decide what the product does while derived data is rebuilding, lagging, or unavailable. It might serve the last known version, show partial results, degrade the experience, pause the workflow, or fail closed. Make that behavior visible to support, product, and incident responders instead of burying it in worker logs. If a derived view cannot be rebuilt safely, monitored for freshness, reconciled with its source, and deleted under the same rules as the original data, it is not an optimization. It is unowned operational state. A useful test is simple: normalize the write model when correctness and ownership are the hard parts; denormalize the read model when latency, fan-out, reporting shape, or product experience justifies the operating cost. Do not let either choice become ideology. The right model is the one the team can explain, restore, monitor, backfill, compare with its source, enforce privacy rules against, and retire without turning the next incident into a data archaeology project.
  • Query optimization — make the query boring before making the architecture clever. Select only the columns you need, filter early, paginate deliberately, and avoid sorting, joining, or aggregating far more data than the user will ever see. Look for N+1 patterns, unbounded date ranges, accidental cross-tenant scans, ORM-generated surprises, and reporting jobs that compete with customer traffic on the primary path. Use query plans, runtime metrics, and representative parameters before changing indexes or rewriting code. A production query is often slow because one large tenant, one skewed value, one bad plan, or one concurrency pattern never appeared in development. Fix the query shape before reaching for a replica or cache. Optimize in this order: correctness, latency, then cost. A faster query that returns stale, partial, or inconsistent data without the product expecting it is just a quieter failure mode.
  • Partitioning - divide large tables into smaller ones.

None of this is new, which is exactly why it gets skipped. Most teams already know the slogans: index carefully, avoid unnecessary duplication, measure before tuning, and test recovery. The gap is not awareness. The gap is execution when a launch is late, a customer is waiting, or the incident channel is filling up. In a startup or fast-moving product team, “we will clean this up later” often means the shortcut becomes the data model. The migration that was supposed to be temporary becomes too risky to touch. The cache added for one endpoint becomes a hidden dependency. The replica created for reporting becomes part of the product’s correctness story. The useful question is not whether an optimization technique exists. It is whether the team has the evidence, rollout plan, rollback path, ownership, and observability to use it safely the day it is introduced.

Plan Ahead for Your Data

Performance tuning starts with the shape and lifecycle of the data, not with the database brand. Before choosing an engine or adding infrastructure, define what the data represents, how often it changes, how it is queried, who owns it, and what correctness means for the product. A user profile, an append-only event stream, a financial transaction, and a derived analytics record do not share the same durability, latency, retention, privacy, or audit requirements. The database should fit those requirements and the application’s failure model. When it does not, the team usually pays the difference with fragile application logic, repair jobs, manual cleanup, and incident runbooks that grow longer every quarter.

Get to Know the Database

There are two main types of databases these days: relational (SQL) and document-based (NoSQL). 

NoSQL:

  • If your application needs to handle single yet flexible documents.
  • If you predict large amounts of data that might be distributed and sharded.
  • If you expect a lot of unstructured data.

SQL:

  • If your application requires rigid, well-defined schemas and relations.
  • Use strong consistency when the workflow truly depends on one current truth across the data landscape, and document where eventual consistency is acceptable so engineers do not guess during incidents.
  • If you intend to digest columnar data using big data tools.

Once you’ve chosen the database to work with, ask yourself again what your use case is. Inform yourself as to what others experienced working with MySQL, MariaDB, PostgreSQL, MongoDB, to name a few. Find the setbacks that others faced and see if at any point in the future you might face something similar. 

Get to Know the Data and its Characteristics

The way you design your data now will impact you in the future. It’s a hard task, but force yourself to think of what other functionality you have in store and plan to implement. See if the current data scheme and models allow easy integration of such functionality.

Every feature moves, changes, or depends on data. Before optimizing the storage engine or adding another service, look at how the data actually behaves under normal and abnormal use:

  • If the workload is write-heavy, optimize for sustained write throughput, predictable commit latency, batching behavior, and the cost of indexes or constraints on the hot path.
  • If the workload is read-heavy, start with I/O efficiency, query shape, cacheability, and read-path isolation. Add caching only when the team can define acceptable staleness, invalidation behavior, observability, and the fallback path for a cold, poisoned, or unavailable cache. A useful cache relieves known pressure on a known path; it does not compensate for a data model nobody understands. The cache should not become an undocumented second source of truth, a place where deletion rules are forgotten, or the hidden dependency that keeps the primary database alive during normal traffic. Check the failure mode explicitly: what happens on cache miss, cache flush, regional failover, bad serialization, oversized key growth, hot-key traffic, or delayed invalidation? If losing the cache changes correctness instead of only performance, the design needs another pass.

Load and Stress the Data

Database performance usually degrades before it fails outright. The early signals are easy to dismiss: a report takes a little longer, a migration needs a wider window, a queue drains more slowly after peak traffic, or one large tenant makes the dashboard look strange. Those symptoms often mean the original assumptions are expiring. The schema, indexes, batch jobs, connection limits, and backup strategy may have been built for small tables, even traffic, simple reports, and little operational contention. That is not spare capacity. It is an untested boundary. Create safe, production-shaped environments where the team can hit those boundaries before customers do. Load realistic data volumes, run migrations against large tables, replay representative read and write patterns where possible, and observe indexes, locks, queues, replicas, workers, connection pools, and backups under stress. The goal is not to build a perfect clone of production or copy sensitive data into every test system. The goal is to expose failure modes early enough to change the data model, query pattern, rollout plan, or operating limit without doing it during an incident. Repeatable infrastructure, realistic permissions, and consistent deployment paths make that testing less dependent on tribal memory.

This work is uncomfortable because it makes future scale visible while there is still time to act. A harmless table in the first release can become the object every deployment waits on. A background job that handled a few thousand rows can block writes once it scans millions. A cheap report can become the query that leaves replicas lagging all morning. A model or search feature that felt isolated can turn into another freshness contract the product depends on. Good judgment about data performance does not come from memorizing tuning tricks. It comes from testing assumptions, watching real behavior, and closing the gap between the data model and the workload before that gap becomes an incident. Use checks like these before adding the next component, widening a maintenance window, or approving a risky migration:

  • Test beyond the comfortable path. Run more reads, writes, migrations, retries, backfills, imports, and concurrent jobs than an ordinary day requires, then watch where latency, locks, queue depth, replica lag, error rates, saturation, and cost start to bend.
  • Only when it stops working do we look under the hood to find and fix the problem.

Everything else is tooling. Data is the application’s core state, so design and operate it with the same discipline you expect from production infrastructure: clear ownership, consistent schemas, observable access patterns, tested recovery paths, and explicit behavior when the data layer becomes slow, stale, inconsistent, or unavailable.

Protect your Data

After choosing a database, shaping the data model, and improving the main access paths, protect the data from your own systems as deliberately as you protect it from attackers. Security against malicious access is mandatory, but many serious outages start inside the boundary: a service with too much permission, a human running the wrong migration, a retry loop duplicating writes, a cleanup job deleting the wrong tenant, or a backfill corrupting derived state. Internal actors include people and services, and both fail in predictable ways when permissions, guardrails, and recovery paths are vague. Use the following controls to reduce accidental disruption, data loss, and data corruption:

  • Back up the data and plan for deploying from a snapshot.
  • Make every access path deliberate instead of inheriting old defaults, convenience choices, or the easiest connection string.
    • Send read traffic to replicas only when lag is measured, the application can tolerate stale reads, and critical flows still know when they must read from the primary.
    • Route analytics, exports, and ad hoc reporting away from customer-facing write paths unless the business has explicitly accepted the contention risk.
    • Limit service accounts and connection pools by workload so one noisy job cannot exhaust the database for the rest of the product.
    • Keep migrations, backfills, and maintenance jobs observable, throttled, and reversible enough that the team can stop them before they become an outage.
    • Document which paths are allowed to be stale, which must be strongly consistent, and which should fail closed when the data layer is degraded.
  • no human ever writes directly to the data.
  • If a service is the owner or main user of a table or database, other services request data through internal APIs.
  • Monitor the database CPU and memory and plan ahead in case you need to scale.
  • Look for, kill and find the source of long running queries to find misbehaving services.
  • Keep The Data Clean

    Protecting data is only half the job; keeping it usable is the other half. Product databases accumulate abandoned feature flags, duplicate events, expired sessions, old audit payloads, and records nobody wants to delete because nobody is sure who still depends on them. Cheap storage does not make this free. Unmanaged growth means larger indexes, slower backups, longer restores, heavier replication, noisier analytics, and more fragile migrations. A healthy data practice includes explicit housekeeping, not a panic cleanup after a disk alarm or a runaway bill. Use these checks to keep data clean without turning cleanup into a risky production event:

    • Don’t do soft deletes.
    • Scan and find least retrieved data and archive it.
    • Run the database’s native maintenance routines deliberately, not as folklore. In PostgreSQL, understand autovacuum and schedule manual VACUUM or VACUUM ANALYZE when the workload justifies it. In MySQL, use OPTIMIZE TABLE only where it is appropriate for the storage engine and table behavior. For any database, know what the maintenance command actually locks, rewrites, or recalculates before running it in production.
    • Be wary of making changes, don’t add tables that duplicate data.

    Keep your Data in Mind

    Out of all the aspects and methods we discussed there’s one conclusion to be drawn. Data is the most important, most overlooked aspect of software development. To keep your data in mind means to consider all the pros and cons of choosing a DB. To keep your data in mind means you always check how data retrieval affects performance. To keep your data in mind is to consider these principles:

    • Choose the right database for the workload.
    • Create indices and optimise your queries.
    • Optimize IO through hardware adjustments and caching.
    • Get rid of data that you don’t need, no soft deletes.
    • Check and check again that the high volume doesn’t create bottlenecks.
    • Back up your data and limit access.

    Apply these principles consistently because almost every product decision eventually changes how data is modeled, moved, queried, retained, recovered, or deleted. Scaling a database is not just buying a larger instance, adding replicas, or attaching another managed service. It is the discipline of removing ambiguity before ambiguity becomes downtime. When ownership, maintenance, observability, access control, and recovery are treated as normal engineering work, the database stops being a hidden liability. It becomes a dependable foundation for the product, the platform, and the teams that have to operate both under pressure.