Parameterized Test Data Without Cache Bias or Duplicate-User Failures
Choose shared, finite, or unique load-test data deliberately and track exhaustion, cache effects, collisions, and cleanup.
Test data is part of the workload. Reusing one record can give you a fast cache hit or a serialized lock. Making every record unique can turn a business test into a storage-growth test. Pick shared, finite, or unique data on purpose, and make exhaustion and collisions visible.
Start with the state a journey needs
List the identities, carts, products, documents, permissions, tokens, and asynchronous jobs involved. Mark which values are read-only and which change. A browse flow can usually share a catalog record. Checkout usually needs its own cart and inventory state.
Define a data boundary and an owner. Use synthetic records with no personal information. Record the environment, fixture version, cardinality, relationships, and cleanup policy with the test artifact.
Choose a reuse strategy
Shared data is compact and can model popular or hot records. It also raises the cache hit rate and contention. Finite partitioned data gives each actor a slice and can show realistic collisions when a slice runs out. Unique data cuts interference, but it can underweight popular reads and add storage work.
If the product has a mixed population, use a mixed scenario. In a hypothetical catalog, 80 percent of reads might come from a small popular set and 20 percent from a long tail. Those figures are an example. Use your actual distribution, or label it as a planning assumption.
Make parameter allocation deterministic
Allocate records by worker and actor. Do not pick at random without collision checks. A deterministic allocator makes a failed journey reproducible. Put the worker and partition in diagnostics, and never in a label that customers can see or that carries secrets.
Decide what happens when a pool runs out: fail the scenario, stop adding users, recycle records and accept the cache consequence, or provision more fixtures. Silent wraparound makes a long run look healthy while many actors share one mutable record.
Detect cache bias
Run warm, cold, and mixed cache states. Compare hit rate, backend calls, response distributions, and target saturation. A dataset that reads one key over and over can make the service look faster than a production-shaped distribution would. A dataset that never repeats can make it look slower.
Warming a cache is work too. Count the preparation time, and check whether prewarming competes with the test. If each replica has its own local cache, check whether routing warms them unevenly. Put the cache state in the result labels.
Test duplicate-user failures
Authentication and writes depend on identity. A shared login can invalidate another session, serialize a row, or trip device protection. Unique accounts can expose limits and cleanup volume. Read authentication and sessions for expiry and refresh scenarios.
Assert identity and ownership in responses. A request that succeeds using another actor’s record is a correctness failure. It is not a performance win. Test duplicate creation, concurrent updates, stale versions, and cleanup failure with safe fixtures.
Observe data health
Track allocation success, exhaustion, collisions, reused records, cache hits, cleanup completion, and remaining capacity. Split latency and errors by data mode. If the result does not show that the pool ran out halfway through, you cannot trust its throughput and error rate.
Check the generator as well. Data parsing, file reads, serialization, and random selection all use client resources. Preloading a large dataset can help scheduling but changes memory pressure. Measure that choice instead of assuming it costs nothing.
Validate in stages
Run one actor, then a small cohort, then the intended load shape. Inspect state before and after. In a safe test, exhaust a fixture on purpose to check that the failure is loud. Run again with warm and cold caches. Keep the first valid result instead of overwriting it with a more convenient fixture.
Keep data partitions and scenarios together with comparable runs. No platform can decide for you whether a hot record or a unique record is realistic for your product. Make the policy explicit, measure its side effects, and never let a silent duplicate pass as capacity.
A fixture acceptance checklist
Before you scale, check cardinality, relationships, allocation latency, uniqueness, cache state, and cleanup. Run a warm-cache read, a cold-cache read, a concurrent mutation, and an exhausted-pool case. Assert ownership and tenant boundaries in every response. Record which cases should pass and which should invalidate the run.
After a rehearsal, compare the remaining fixture count and storage growth with the plan. A test that leaves thousands of records behind can produce valid latency and still leave an unsafe environment. Cleanup is part of the data contract. If it fails, that belongs in the result, not in a manual note.
Keep one result per data mode and compare only equivalent modes. A warm shared dataset, a cold unique dataset, and a finite partition are all valid experiments with different costs. Naming them in the scenario stops a later reviewer from calling the fastest mode “capacity” without seeing its cache or collision tradeoff.
Add a fixture budget: records available, records consumed per iteration, expected cleanup rate, and exhaustion threshold. Reconcile it after the run. If consumption differs from the budget, look for retries, duplicate journeys, or cleanup failure before you trust the result. Accounting for data turns hidden state into a performance variable you can see.
Make data variation intentional
A parameter only helps when its distribution matches the question you are asking. In a search test, a few repeated terms can make caches look healthier than they are. On a detail page, one popular ID can hide row-size or authorization costs. Define the key space, selection rule, cardinality, and mutation rate before the run. Keep a stable seed for comparison, then add a second seed or a broad distribution when you need to test sensitivity.
Keep data preparation apart from measured work. Pre-create records when setup is not part of the user journey, and label setup time outside the request window. If creation is part of the risk, give it its own scenario and threshold. A hidden factory inside every iteration can saturate the database and make a read test look slow for the wrong reason. A fully warmed fixture, on the other hand, can hide the write amplification the release changes.
Watch contention and cleanup. Parallel workers should reserve separate mutable records or use an idempotent operation built for sharing. If two actors update the same row, the lock waits may be the finding, or they may be the test getting in its own way. Record conflicts and retries instead of treating them as ordinary latency. After the run, reconcile created, updated, expired, and deleted counts. A mismatch is a correctness issue before it is a capacity result.
Test the edges of the distribution. Include a small record, a large record, a missing or expired record, and a cold key, when those states are valid. Keep expected outcomes explicit so nobody confuses a legitimate 404 with a broken fixture. If an edge case is rare in production but high-impact, run it as a separate bounded profile instead of skewing the main mix.
When cache behavior matters, report hit and miss paths separately. Compare the same parameter set after a reset and after warm-up, and record eviction or TTL conditions. A faster second run may just be cache warming, not a code improvement. The final artifact should keep the generator seed, fixture version, allocation rule, and cleanup evidence, so another run can explain any change.
Keep the allocation manifest versioned with the scenario.
Review that manifest whenever a journey changes its mutation or cleanup behavior. Stale allocations can cause collisions that look like service regressions.