Scale baseline  /  v1  /  3 August 2026

A scale baseline: making insights more visible in RL experiment tracking

A single 21-hour synthetic run exercising Metrana's ingestion and storage path at 50,000 environments and 1 million concurrent series. This page reports what we measured, the configuration and hardware it ran on, how completeness was verified, and the limits we reached. What's stable at step 1,000 can be the source of a collapse at 100,000.

Fig. 1 — One cell per parallel environment. Brightness marks a metric write landing. 50,000 envs  ·  20 series each  ·  1,000,000 concurrent series
Single run  /  synthetic load  /  fully reconciled

One continuous 21-hour run logged 384 billion data points across 50,000 environments and 1 million concurrent series, sustaining around 5 million points per second — and every point was reconciled afterwards, with none lost. It is a synthetic load test: it exercises the ingestion and storage path, not a live training loop. The limit we reached was in the network, not the engine. Scope, caveats and known limits are set out in §5.

§1

Results

A single continuous run. No stitching, no sampling, no downsampled retention: every point below remains individually queryable after the fact.

Single-run scale baseline
Measure Result Status
Parallel environmentsConcurrently logging, single run 50,000 Verified
Concurrent time series20 metrics per environment 1,000,000 Verified
Environment steps loggedTotal across the run 19.2 B Verified
Data points loggedEach = one env, one metric, one env step (a scalar) 384 B Verified
Write throughput, sustainedSingle logger, single connection, vectorized ~5 M/speak > 6M/s · one connection, same-region Verified
Query latency, server-sideP99 during heaviest phase, under concurrent background merges 250–500 ms+~100ms client round-trip Verified
CompletenessEvery point reconciled against series headers and counters 100%no points lost, plots or stats Verified
Verified  measured on a single continuous synthetic run
§2

Test configuration

One run, one configuration, a first pass. We have not yet swept the tuning parameters that govern batching, compression or partitioning, so these figures are a floor rather than a ceiling.

Parallel environments
50,000
Metrics per environment
20Scalar series, logged per step
RL steps
6,000
Env steps per RL step
64
Run duration
21 h 20 m
Disk written
2.2 / 7.5 TiBUsed of available, full-fidelity
ClickHouse
3 × i7ie.3xlargeSingle shard, 3 replicas · 12 vCPU / 96 GiB / NVMe each
Ingestion
4 workers1 vCPU / 2 GiB each, + 1 service pod
Kafka
3 nodes1 vCPU / 4 GiB each
Logger
1 process, 1 connectionVectorized · same region, separate AZ
6,000 RL steps × 64 env steps  =  384,000 steps per environment
384,000 × 50,000 envs  =  19.2 B environment steps
19.2 B × 20 metrics  =  384 B data points
384 B ÷ 21 h 20 m  =  5.0 M data points/s average  ·  > 6.0 M/s sustained peak
§3

Where the ceiling actually was

Throughput first held at 4.4M points per second, and ClickHouse was not the reason: CPU and disk both had headroom. The limit was the network. Too much of the workload landed on one Kubernetes node and saturated its link, and AWS throttled it. Enabling compression on the ingestion workers cleared the constraint, and throughput rose past 6M per second and held there for hours, at which point ClickHouse CPU became the next bound. The run settled at 5M only because of a self-limiter left in the logger configuration, not because of a ceiling.

This matters for how the headline number should be read: every limit we reached was an infrastructure parameter, not the engine. More shards, more Kafka partitions and more ClickHouse CPU each open the next band. We quote the run conservatively, since it is synthetic, and would stand behind around 4M per second as a comfortable sustained figure on this hardware — with 5M measured, and 6M and above reachable when the headroom is spent on ingestion rather than reserved for queries and merges.

6M 4M 2M 0 ~4M comfortable 4.4M initial 1.8M — node reshuffle 6M+ — compression on 5M settle
Fig. 2 — Write throughput across the run. Illustrative reconstruction of the phases described below; not a raw telemetry export. y: data points/second  ·  x: run time (~21 h)
§4

Context

Most frontier R&D compute is spent on experiments, not on the final training run.
Epoch AI estimates OpenAI's 2024 R&D compute at about $5B, with roughly $500M going to the final training runs behind released models. MiniMax and Z.ai reported the same shape in their IPO disclosures.  →  epoch.ai

Experiments dominate the compute budget, and in RL they are also the least observable part of the stack. A policy degrades, an environment stops resetting, a reward gets gamed — and a tracker that records only top-line curves reports that something went wrong long after it did, without the environment-level detail that would say what. Retaining per-environment and per-episode data lets a change in the aggregate be traced to the specific environments, episodes and transitions that produced it. A pathology identified at step 200 rather than step 200,000 is compute not spent, which is the reason the ingestion rate has to hold under sustained load rather than in a burst.

The constraint is cardinality within a single run, not raw rate. Conventional trackers take the run as the unit — a dashboard of runs, each with a modest number of series. Environment-level RL logging inverts that: one run holds a million concurrent series, one per environment per metric, all live and individually queryable. That is a different problem from tracking many runs, and holding a million live series in one run without sampling any away is what this baseline was built to test.

§5

Honest notes

A result is only interpretable alongside its limits, so we report them in the same place, at the same level of detail.

  • Scope

    This is a synthetic load test

    The load was generated data from a single logging process, not a live RL training loop. It exercises the ingestion and storage path, which is the part that has to survive scale. It does not measure the training loop itself, or the overhead placed on it.

    One consequence worth stating plainly: generating random points is faster than real training, where the GPU takes time to produce each step. In a real run, throughput is bounded by how fast the training loop emits data, not by the logger. This test shows what the logger and ingestion path can absorb — the ceiling, not the typical case. A run against a live workload is the next measurement.

  • Held up

    Queries stayed responsive under load

    Server-side P99 of 250–500ms during the heaviest phase, while ClickHouse was also running background merges; add roughly 100ms for the client round-trip. Rare outliers approached a second under peak merge pressure. Measured on NVMe-backed self-hosted ClickHouse.

  • Known limit

    Storage is the real constraint over long runs

    The run filled 2.2 of 7.5 TiB in a day at full fidelity — comfortable here, but sustained around the clock, storage becomes the binding limit before throughput does. Continuous operation at this rate needs larger disks and a tiering policy for older runs. A genuine limit, not a tuning gap.

  • Headroom

    5M per second was a single connection

    The whole run used one logger over one TCP connection, and that connection — not the backend — was the limit. Throughput scales with how the network load is spread, so distributed logging across several processes should go further. The figure was set by a single connection, not the backend.

  • Reproducibility

    Throughput depends on the network between logger and cluster

    5M per second was reached with the logger in the same AWS region as the cluster, one availability zone away — close, low-latency, near-ideal. Two limits sit between a logger and the backend: our AWS L7 layer caps TCP window sizes (an infrastructure detail we can bypass, and one that matters less the closer you log to the cluster), and, from outside the cloud, the client's own upload bandwidth — which nobody can control. From a home connection in Poland the same single logger reached around 1M per second.

    Three things raise it: logging close to the cluster; distributed logging across more TCP sessions (with the caveat that sessions sharing one machine can still hit a shared upload cap); and a fast enough producer — if the training loop only emits 0.5M/s, the logger sends 0.5M/s. The high-throughput logging approach is documented.

  • Unswept

    The configuration is unoptimised

    A first pass. Batching, partitioning and compression are largely untuned; enabling compression alone took the run from 4.4M to past 6M. We expect to improve on these figures and will republish when we do, including if anything moves the wrong way.

§6

Completeness

A throughput number is worth only as much as its completeness. Under sustained backpressure a pipeline can shed load silently, and the gaps surface only later, as missing spans in a plot. So the run was reconciled in full, in layers.

The statistics and headers — roughly 4 billion episode headers and 6 billion per-step stat rows — were reconciled first, since they are the fragile layer: they demand exactly-once accounting, where a single retry that double-counts would corrupt the aggregate. Every count matched. The individual data points were then verified against the per-series counters recorded in those headers, down to the level of each series: every metric held exactly its expected 384,000 points, and each series segment its expected 64 or 128.

No data point was lost, in the plots or the statistics, and every point, header and stat was queried and accounted for. The 384 billion is a reconciled count, not an ingestion count.

Running it against a real workload.

These are synthetic-load figures. The measurement that matters is whether the same holds against a live training run at your scale — we can set that up on your infrastructure.

team@recurvia.ai
Metrana — RL-native experiment tracking  ·  Recurvia Baseline v1  ·  3 August 2026