Performance testing needs data that matches production in both scale and shape: enough volume to show how the system behaves under load, and the same distributions, cardinality, and referential integrity that make query planners and indexes behave as they will in production. Copying raw production data into a test environment is fast but unsafe, and production rarely holds the volume you need to test against future growth or peak events. The realistic approach is to derive a safe, referentially intact dataset from production — de-identifying and subsetting it — then scale that dataset synthetically to the volumes performance testing demands.

What performance testing actually needs from test data

A performance test is only as trustworthy as the data underneath it, and what makes data trustworthy is not raw size — it's how closely the data reproduces the shape of production. Two datasets with identical row counts can produce completely different results under load. A table filled with a million near-identical rows compresses well, indexes cleanly, and caches almost entirely, so the system sails through a test that real traffic would stall. Production data is lumpy in ways that matter, and test data that flattens that lumpiness gives you false confidence: green dashboards that say nothing about release day.

Getting a trustworthy result means reproducing four properties of production data at once:

  • Volume — enough rows, across the right tables, to exercise the code paths that only appear at scale: pagination, batch jobs, cache eviction, and connection-pool contention.
  • Distribution and cardinality — the spread of values in a column and the number of distinct values it holds. Cardinality drives which indexes the query planner chooses and how selective they are; uniform or low-cardinality test data sends the planner down paths it would never take against real data.
  • Referential integrity — the guarantee that every foreign key resolves to a real parent row, so joins traverse the same relationships they will in production instead of hitting orphaned or empty references.
  • Edge cases and large payloads — the oversized documents, deep hierarchies, and outlier accounts that stress memory, serialization, and query planning far more than the median row does.

Miss any one of these and the test measures a system that doesn't exist.

Why production data alone doesn't get you there

Pointing a load test at a copy of production fails for two independent reasons, both of which shape how to build a realistic performance dataset.

The first is safety. A production copy carries every sensitive value it contained — customer names, payment details, health records, credentials — and lower environments are, almost by definition, the least-controlled places in your infrastructure: broader access, lighter monitoring, and more copies scattered across laptops and CI runners. Moving raw production data into them spreads your most sensitive records across your weakest controls, which is why masking production data for lower environments is a baseline expectation for regulated teams rather than an optional hardening step.

The second reason is that production is only ever today's size and today's shape. Performance testing usually cares about tomorrow's: the traffic you'll see after a year of growth, the spike from a launch or a seasonal peak, the volume that tips a query from an index scan into a full table scan. Production doesn't hold that data because those events haven't happened yet. You can't subset your way to a dataset larger than its source, and duplicating existing rows only multiplies the same values — inflating counts without adding the distribution or cardinality that make a larger dataset behave like a real one. That splits the work into two separate jobs: deriving a faithful, safe dataset from production, and reaching the volumes production never contained — the fidelity of testing and QA data on one side, raw scale on the other.

Building a realistic performance dataset from production

The safe route to production-realistic data is to derive it from production without copying it wholesale: de-identify the sensitive values, then reduce the result to a slice small enough to work with but faithful enough to behave like the source. Done well, that yields a representative dataset carrying production's structure, relationships, and value distributions while containing no real personal data.

Subsetting is the harder half to get right. Subsetting means selecting a portion of a database — say, ten percent of customers and everything related to them — rather than the whole thing. The naive approach of picking rows table by table at random shatters referential integrity: you keep an order whose customer was left behind, and its foreign key now points at nothing. A relationship-aware subsetter avoids this by traversing foreign keys to pull every related row along with its parent, inferring virtual foreign keys for relationships the schema never formally declared, and resolving circular dependencies between tables so the slice stays internally consistent.

Tonic Structural is built around this exact problem. Structural de-identifies production data through configurable masking that preserves each column's format and statistical character, and its patented data subsetting engine produces a smaller, representative slice with referential integrity intact across the whole schema. Because the masking is consistent, a customer ID transformed one way in one table is transformed the same way everywhere it appears, so joins still resolve after the data is masked.

The pattern holds at the top end of the size range. eBay scales a multi-petabyte data ecosystem down to manageable, referentially intact subsets that shorten its development cycles and feed its automated testing suite — the same subset-from-production move, run against one of the largest data estates there is.

Scaling volume beyond production, synthetically

When the volume you need runs past what production holds, subsetting reaches its limit — you can't slice out more data than exists — and the job shifts from deriving data to generating it. Load and stress tests are usually sized against a target production can't supply: a multiple of current data, a next-year growth projection, or the peak of a launch or seasonal event. The goal is net-new records that match the real schema, distributions, and relationships, not copies of existing rows. Duplicating rows to hit a row count is the trap here: it inflates the total while collapsing cardinality and skew, so the query planner sees a workload that gets easier as it grows instead of harder. Generated records avoid that by following the source's patterns, so a dataset several times production size still exercises indexes and query plans the way real data would.

This is where two products do two distinct jobs in sequence. Tonic Structural produces the safe, de-identified base from production, as above. Tonic Fabricate then takes that de-identified dataset as a model and generates additional records that follow its patterns, expanding record counts beyond what production provides while keeping referential integrity across tables and databases. Because Fabricate performs synthetic data generation modeled on the de-identified data rather than reusing production values, the larger dataset reaches performance-testing volumes without reintroducing any sensitive information.

The division of labor is the point. Structural makes production data safe and representative; Fabricate multiplies it into the volumes that load and stress tests require. They are complementary steps rather than competing options — one solves for fidelity and safety, the other for scale — and used together they cover a range neither reaches alone.

The Tonic Advantage: safe data at production scale and beyond. Performance and load testing need volumes production can't safely provide, and neither de-identification nor generation closes that gap on its own. Pairing the two does. Tonic Structural de-identifies and subsets real production data into a safe, referentially intact base, and Tonic Fabricate models that base to generate net-new records at a multiple of production size. The result is a dataset that behaves like production under load — the same distributions, the same relationships — at volumes production never contained, with no sensitive data carried into the test environment.

Getting the data shape right: distribution, skew, and edge cases

Volume with the wrong shape is worse than a smaller dataset with the right shape, because it produces confident but misleading numbers. Matching production's statistical shape — and deliberately including the outliers that break systems — is what separates a load test that predicts production from one that flatters it.

Four dimensions of shape are worth reproducing on purpose:

  • Distribution — the frequency curve of values in a column. If ninety percent of orders in production sit in three regions, uniformly random regions in test data spread load evenly across partitions that production hammers unevenly.
  • Cardinality — the count of distinct values, which sets index selectivity and join cost. Get it wrong and the query planner optimizes for a workload you will never actually run.
  • Skew and hotspots — the handful of heavy accounts, the user with millions of followers, the tenant with ten times everyone else's data. These concentrations create lock contention and cache pressure that an even spread never surfaces.
  • Edge cases and large payloads — the maximum-length fields, deeply nested documents, and null-heavy rows that stress serialization and memory.

Reproducing these is exactly what generating data that matches production's distributions is for. Rather than flattening every column to a uniform value, you model the real frequency curves, preserve the concentrations, and inject the outliers deliberately. A dataset that captures the tails, not just the averages, is the one that tells you where the system will actually strain under load.

Automating performance test data in your pipeline

Performance data earns its keep only when it's repeatable. A number from a one-off dataset tells you little; a number you can reproduce on every release, against the same data, tells you whether performance is improving or regressing. That calls for test data that refreshes on a schedule, regenerates deterministically from a fixed seed so a rerun gets the same dataset every time, and can be provisioned on demand rather than requested through a ticket.

Wiring this into continuous integration turns performance from a pre-release scramble into a routine check. When a pipeline can stand up a known dataset at a known size on every run, a performance regression surfaces the same way a failing unit test does — tied to the commit that caused it, while the change is still fresh. Tonic Structural supports the derive-from-production half of this with provisioning and refreshing on demand, so environments are rebuilt with current, safe data automatically rather than by hand. For the synthetic scale-up, Tonic Fabricate's reusable generators capture a generation workflow once and re-run it at any size, so one definition produces a small dataset for a smoke test and a large one for a load test.

Ephemeral environments keep the cost in check: spin up a disposable, correctly sized dataset for a run, then tear it down, rather than paying to keep a permanent full-scale copy alive. Fitting fresh, safe test data inside pipelines this way lets performance testing run continuously without becoming a standing storage line item.