Docs / SynthEvents / Fundamentals / Generating Data Sets

Generating Data Sets

This guide explains how a data set comes about: which parameters shape it, how SynthEvents arrives at the number of events you ask for, what the seed does, and how the source of every event tells you how to generate the data set again.

A Simulation, Not a Random Sample#

SynthEvents does not make up events one by one. It simulates actors over the time range you ask for, such as the readers and the librarian of a library, or the users of a to-do list. Every actor follows a story: it waits, does something, learns whether that worked, and decides what to do next. Whatever an actor does is a command, and the rules of the domain decide whether it is accepted and which events it results in.

A command that the rules refuse leaves no event, but it still steers what the actor does next. A reader who finds every copy of a book lent, for example, tries again on a later visit. This is why the events of a data set always follow the rules of their domain, and why conflicts arise from actors acting on what they believe rather than on what is true, as in a real system.

All events of one command share the same time, as all events of one write do in EventSourcingDB. Event IDs start at 0, and time never runs backward.

The Parameters of a Request#

A request names six parameters, all of them mandatory:

  • domain is the domain to simulate, such as library or todo.
  • scenario is one of the domain's scenarios, such as everyday. A scenario sets the whole composition of a run: who acts, how often, and in which time zone the rules reckon their days. There are no further parameters: to get a different kind of data, you pick a different scenario.
  • range is the time range, with from and to as instants in RFC 3339 with an offset, such as 2025-01-01T00:00:00Z. It is half-open: it includes from, but not to. Every event of the data set lies within it.
  • eventCount is the approximate number of events, from 1 to 20,000.
  • seed makes the data set reproducible.
  • format is either cloudevents or eventsourcingdb. It only chooses the envelope, not the events (see Formats).

A bound given with a different offset is still the same instant, so a range from 2025-01-01T01:00:00+01:00 gives the same data set as one from 2025-01-01T00:00:00Z that ends at the same instant.

A request does not name an event type prefix. Every event type is io.synthevents.<domain>.<event>, such as io.synthevents.library.book-borrowed, so that a data set says where it comes from. For the details of the request, see API.

The Number of Events#

You do not choose how many readers, users, or customers there are; you choose how many events you would like, because that is what most people think in. SynthEvents then sizes the population of the scenario, such as the number of readers of a library, so that the run comes out at about that number:

  1. A pilot run with a small population grows until it comes out at a tenth of the number you asked for.
  2. From the pilot, SynthEvents sizes the population and runs it.
  3. If that run misses the number by more than the tolerance, SynthEvents sizes the population once more from what the run came out at.
  4. If that misses as well, SynthEvents searches between the nearest runs below and above the number, with up to six more runs, and delivers the first that comes out within the tolerance, or else the one that came closest.

The tolerance for a number of N events is:

max(0.1 * N, 4 * sqrt(N))

Events come in clumps, such as the several events of one booking, so chance varies a number of events by about twice its square root. For a small number, the tolerance is what chance does. From 1,600 events on, it is a tenth:

You ask for Tolerance Within the tolerance
10 12.6 1 to 22 events
100 40 60 to 140 events
500 89.4 411 to 589 events
5,000 500 4,500 to 5,500 events
20,000 2,000 18,000 to 22,000 events

A data set comes out within the tolerance, unless the population cannot be sized to reach it.

When the Range and the Number Do Not Fit#

A population has at least one member and at most as many members as events you ask for. Some combinations of range and number cannot be reached within these bounds:

  • If even a single member comes out at too many events, the range is too long for the number, and the request is refused with range-too-long. A single reader of the library, for example, borrows dozens of books over a long range.
  • If even the largest population comes out at too few events, the range is too short for the number, and the request is refused with range-too-short. In a very short range, hardly anybody does anything.

A run that comes out at no events at all is refused with range-too-short as well, since an empty data set is never what anybody asks for.

The Seed#

The seed is mandatory and has no default. It consists of 1 to 64 characters of a to z, 0 to 9, and -, and is used exactly as given. Choose one that says what the data set is for, such as ddd-workshop-2026. The standard data sets on the product page use the seed standard.

The same request to the same version of SynthEvents gives the same data set, byte for byte. Every actor draws from a random stream of its own, derived from the seed and the actor's identity, and every UUID in a data set, such as the one of a copy of a book or of a reader, is drawn from such a stream. Nothing that happens in a run depends on the wall clock or on chance outside of these streams.

A different seed gives a different data set of the same kind: the same domain, scenario, range, and approximate number of events, but different actors with different stories.

The Source as a Recipe#

The source of every event is a tag URI that names the version of SynthEvents and every parameter of the request except the format. For the standard data set of the library with about 5,000 events, generated by version 0.3.0, it reads:

tag:synthevents.io,2026:synthevents;version=0.3.0;domain=library;scenario=everyday;from=2025-01-01T00:00:00Z;to=2026-01-01T00:00:00Z;eventCount=5000;seed=standard

It consists of these parts:

  • tag:synthevents.io,2026:synthevents names SynthEvents. The year 2026 is part of that name, not the year of the events.
  • version is the version of SynthEvents that generated the data set.
  • domain, scenario, eventCount, and seed are the parameters as the request gave them.
  • from and to are the bounds of the range, written in UTC.

The parameters are pairs separated by semicolons, so nothing needs escaping in JSON. The format is left out, since both formats carry the same events.

So a data set says how to generate it again: even after it has been restored into EventSourcingDB, the source of any of its events is the recipe. And since the source differs whenever a parameter does, two different data sets never share both a source and an id.

Versions#

Within a version of SynthEvents, the same request gives the same bytes. This is a promise. Across versions, it is not: a newer version may change what a scenario does, and so the events it generates for the same request. The version in the source tells you which version generated a data set.

If you depend on exactly these events, such as for a workshop or a test, keep the file rather than generating it again later.

Limits#

  • A request asks for 1 to 20,000 events.
  • A data set never has more than 25,000 events, the limit of an evaluation license of EventSourcingDB, so every data set can be restored into an instance that runs with such a license. Asking for at most 20,000 leaves room for a data set that comes out above the number asked for.
  • A range starts on or after 1970-01-01T00:00:00Z, since a restore into EventSourcingDB refuses events before it, and ends on or before the moment of the request.