# 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](/docs/synthevents/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](/docs/synthevents/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:

```text
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`](/docs/synthevents/errors/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`](/docs/synthevents/errors/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](/products/synthevents)** 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](/docs/eventsourcingdb/sources)** 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:

```text
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](/docs/eventsourcingdb/limits)** 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**.
