Unique Test Data Generation: Sequences and Counters, Not Random IDs
Random or seeded fake data can still repeat a value it's already
produced, so unique-constraint violations and flaky duplicate-key failures
creep back in across repeated runs or parallel workers. The fix is to stop
generating uniqueness randomly and instead pull values from a server-side
counter — a named sequence that persists between calls and hands out a new,
strictly increasing value every time. JsonFabrica exposes this as a stateful
createSeq/getSeq function pair (and a matching /v1/sequences API) built
for exactly this.
This is a different problem from the one covered in Random Isn't Realistic — that post is about relationships between fields (an order pointing to a real customer). This one is about uniqueness of a single field across calls, runs, and workers — order numbers, invoice numbers, emails, external IDs — so your test suite stops throwing unique-constraint errors.
Why does random test data still produce duplicates?
Random and seeded generation both draw from a fixed space of possible values, and every call is independent of every other call — the generator has no memory of what it already handed out. A random 6-digit order number has a 1-in-a-million chance of colliding on any single call, but run your suite hundreds of times a day across CI and local machines, or run tests in parallel workers that each start their own generator, and repeats become a practical certainty. Seeding makes results reproducible within one run, but the same seed produces the same sequence of "random" values every time, which is the opposite of what you want when two runs, or two parallel workers, need to land on different IDs at the same moment.
How do sequences guarantee uniqueness across runs?
A sequence is a named counter that lives on the server, not in your test
process. Instead of generating a value and hoping it's new, you ask the
counter for the next one, and it advances before handing it back — so no two
calls, no matter which run or which worker made them, can ever receive the
same value. In a JsonFabrica template you declare the counter once with
createSeq(name, type?, start?, step?), then draw from it anywhere with
getSeq(name), which returns the next value and advances the counter each
time it's called (both documented in the Function Reference).
Because the counter's state is persisted server-side per tenant rather than
held in a local process, it survives across runs, across CI jobs, and across
parallel workers hitting the same account — which is what makes the
uniqueness guarantee hold outside of a single script's lifetime, not just
within it.
{ "orderNo": "<createSeq('orderNo', 'number', 1000, 1)><getSeq('orderNo')>" }
createSeq is a setup call and produces no value by itself — the two
functions sit directly adjacent, with no separator between them, so it's
getSeq alone that emits the number rendered into the field.
The same counter can also be managed directly through the REST API. Create a named sequence with a starting value and step:
curl -s -X POST https://api.jsonfabrica.com/v1/sequences \
-H 'Authorization: Bearer sk_...' \
-H 'content-type: application/json' \
-d '{
"name": "orderNo",
"type": "number",
"start": 1000,
"step": 1
}'
which returns a SequenceDto with currentValue: 1000. Advance it with a
dedicated bump call:
curl -s -X POST https://api.jsonfabrica.com/v1/sequences/orderNo/bump \
-H 'Authorization: Bearer sk_...'
which returns currentValue: 1001. Every subsequent bump — from any test
run, any worker, any machine — returns a strictly greater value, because the
counter itself is the single source of truth. The full CRUD surface
(createSequence, getSequence, listSequences, patchSequence,
deleteSequence, bumpSequence) is documented on the
Sequences API reference.
How do I get parallel test data isolation across workers?
The failure mode unique to parallel test runners is two workers asking for
"the next order number" at the same moment and both getting the same
answer — which is exactly what happens with in-memory counters that live
inside each worker process. A server-side sequence avoids this because
getSeq (or a bump call) is a single atomic operation against the shared
counter: worker A's call and worker B's call are each processed and
incremented in turn, so they can't observe the same currentValue twice.
That's parallel test data isolation without any coordination logic in your
test code — the counter does the serializing for you.
If you want fully separate counters per suite or per branch instead of one
shared counter, pass a sequenceNamespace on a batch generation request
(e.g. "sequenceNamespace": "batch-2026-07-23"); each namespace gets its own
independent copy of the same named sequence, which is useful for keeping a
nightly CI run's IDs from ever overlapping a feature branch's run.
Sequences vs UUIDs vs timestamps — which should I use?
Each guarantees uniqueness a different way, and they aren't interchangeable for test data:
- UUIDs (v4) are unique with overwhelming probability without needing any shared state, but they're not ordered, not human-readable, and don't look like the auto-increment integers or formatted order/invoice numbers most real schemas actually use — so they're a poor stand-in when a test is specifically exercising uniqueness-constraint or ordering behavior.
- Timestamps (or timestamp-seeded values) are ordered and cheap, but two calls in the same millisecond, or two parallel workers, can still collide, and they don't produce the small, dense, sequential numbers (1000, 1001, 1002...) that order numbers, invoice numbers, or row IDs typically look like.
- Sequences are ordered, dense, and collision-free by construction, because they're backed by durable server-side state rather than a probability argument — the tradeoff is a network round trip per call, which JsonFabrica prices as one of its "stateful" functions rather than a free in-memory one.
For fields that are actually declared unique in your schema and that a real
system generates as small sequential integers or formatted counters,
sequences are the closer match. Reach for createSeq('id', 'uuid') instead
when the field genuinely is a UUID in production.
What does a sequence call cost?
createSeq/getSeq (and the equivalent relational-context function
getContext) are billed as JsonFabrica's "stateful" function tier because
each call is a durable, cross-call database read-and-write rather than an
in-memory computation. Per the pricing page, stateful functions
currently cost 1,000 weight-units per call against a 1,000,000-weight-unit
credit — about 1,000 sequence calls per credit — versus 10 weight-units for
most lightweight functions like random names or formatted numbers, which put
roughly 100,000 calls in the same credit. In practice a template only needs
one getSeq call per unique field, so a batch of thousands of documents
still spends most of its weight on the cheap, non-stateful fields around it.
FAQ
Does createSeq need to run before every getSeq call?
No — createSeq is a one-time setup step that declares the counter (and is
safe to call again with the same name; it won't reset an existing counter's
current value). getSeq(name) is what you call repeatedly to draw the next
value, and it's the call that actually advances the counter each time.
Can two test runs share the same sequence?
Yes, by default a named sequence is shared across everything running under
the same tenant, which is exactly what gives you uniqueness across runs. Use
a sequenceNamespace on a batch request when you want isolated counters
instead — for example, keeping a load-test run's IDs from overlapping your
regular CI run.
What happens if I delete a sequence?
deleteSequence removes the counter entirely; a later createSeq with the
same name starts fresh from its start value, so any uniqueness guarantee
against previously generated data is gone. Don't delete a sequence backing
data you still rely on being unique.
Can a sequence produce strings or UUIDs, not just numbers?
Yes — type on createSeq accepts number, string, or uuid, so you can
use the same counter mechanism for formatted invoice numbers (INV-000123)
or unique string IDs, not just plain integers.
Is a sequence the same thing as an auto-increment database column? Conceptually yes — it's a monotonically increasing counter — except it lives in JsonFabrica's generation service rather than your database, so you get guaranteed-unique values for test fixtures before they're ever inserted anywhere.
Collision-free, cross-run uniqueness is one call away when the counter lives
on the server instead of in your test process. Sequences are available now
through the Sequences API and as
createSeq/getSeq in any JsonFabrica template.
Generate realistic test data with JsonFabrica
Describe the shape of your data once, then generate as many fresh, realistic JSON documents as you need via a simple API call.