Temporal Patterns#

Use global_iter() with math functions to make generated data change shape over the life of a workload. This lets you model real-world patterns like shifting popularity, price inflation, seasonal traffic, periodic spikes, and sensor calibration drift; all without external scripts.

Every pattern on this page uses the same building block: an expression that takes global_iter() as input and produces a value whose distribution or magnitude changes as the iteration counter grows.

The full config is at _examples/temporal_patterns/crdb.yaml.

Globals Are User-Defined#

Names in the following globals: block are arbitrary and become variables available in any expression. The patterns below use names like total_iters and initial_skew, but you can name them whatever you like.

Patterns#

Zipf Skew Drift#

Product popularity starts nearly uniform and concentrates over time. The zipf() skew parameter is linearly interpolated from initial_skew to final_skew using global_iter().

Globals

globals:
  total_iters: 10000
  products: 100
  initial_skew: 1.2   # nearly uniform
  final_skew: 3.0     # heavily concentrated on the first product

Expression

zipf(initial_skew + (final_skew - initial_skew) * global_iter() / total_iters, 1, products - 1)

Config

- name: insert_view
  type: exec
  args:
    - zipf(initial_skew + (final_skew - initial_skew) * global_iter() / total_iters, 1, products - 1)
    - initial_skew + (final_skew - initial_skew) * global_iter() / total_iters
    - global_iter()
  query: |-
    INSERT INTO product_views (product_id, skew_at_time, iteration)
    VALUES ($1::INT, $2::FLOAT8, $3::INT8)

Verification query

SELECT
  bucket,
  count(*) AS total,
  count(*) FILTER (WHERE product_id = 0) AS top_product,
  round((count(*) FILTER (WHERE product_id = 0))::FLOAT8 / count(*)::FLOAT8 * 100, 1) AS top_pct,
  repeat('▒', (round((count(*) FILTER (WHERE product_id = 0))::FLOAT8 / count(*)::FLOAT8 * 40))::INT) AS histogram
FROM (
  SELECT *, ntile(10) OVER (ORDER BY iteration) AS bucket
  FROM product_views
)
GROUP BY bucket
ORDER BY bucket;
  bucket | total | top_product | top_pct |                histogram
---------+-------+-------------+---------+-------------------------------------------
       1 |  2644 |        1225 |    46.3 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       2 |  2643 |        1959 |    74.1 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       3 |  2643 |        2440 |    92.3 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       4 |  2643 |        2593 |    98.1 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       5 |  2643 |        2632 |    99.6 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       6 |  2643 |        2637 |    99.8 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       7 |  2643 |        2641 |    99.9 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       8 |  2643 |        2642 |     100 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       9 |  2643 |        2643 |     100 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      10 |  2643 |        2643 |     100 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

Tuning

  • initial_skew / final_skew - controls how much the distribution shifts. A smaller gap produces a subtler drift; a larger gap makes it dramatic. Values near 1.0 are nearly uniform, values above 3.0 concentrate almost all traffic on the lowest-ranked items.
  • total_iters - the iteration count at which final_skew is reached. After this point the skew parameter stays at final_skew. Set this to roughly match your expected total iterations (see Estimating total iterations).

Logarithmic Growth#

Prices rise steeply at first then plateau. Models inflation, adoption curves, or any quantity with diminishing returns.

Globals

globals:
  base_price: 50.0
  products: 100

Expression

floor(base_price * (1.0 + log(1.0 + global_iter() / 1000.0)) * 100.0) / 100.0

Config

- name: insert_price
  type: exec
  args:
    - uniform(1, products)
    - floor(base_price * (1.0 + log(1.0 + global_iter() / 1000.0)) * 100.0) / 100.0
    - global_iter()
  query: |-
    INSERT INTO price_history (product_id, price, iteration)
    VALUES ($1::INT, $2::FLOAT8, $3::INT8)

Verification query

SELECT
  bucket,
  round(avg(price)::NUMERIC, 2) AS avg_price,
  round(min(price)::NUMERIC, 2) AS min_price,
  round(max(price)::NUMERIC, 2) AS max_price,
  repeat('▒', (round(avg(price) / max(avg(price)) OVER () * 40))::INT) AS histogram
FROM (
  SELECT *, ntile(10) OVER (ORDER BY iteration) AS bucket
  FROM price_history
)
GROUP BY bucket
ORDER BY bucket;
  bucket | avg_price | min_price | max_price |                histogram
---------+-----------+-----------+-----------+-------------------------------------------
       1 |    105.75 |     50.09 |    139.66 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       2 |    156.93 |    139.67 |    170.72 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       3 |    185.96 |    170.73 |    200.89 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       4 |    211.99 |    200.90 |    221.88 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       5 |    229.27 |    221.89 |    236.09 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       6 |    242.19 |    236.10 |    247.77 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       7 |    252.52 |    247.78 |    256.94 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       8 |    260.88 |    256.95 |    264.72 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       9 |    268.11 |    264.72 |    271.35 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      10 |    274.40 |    271.35 |    277.37 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

Tuning

  • base_price - starting price before any growth.
  • The divisor inside log(1.0 + global_iter() / 1000.0) controls how quickly the curve rises. A smaller divisor (e.g. 100.0) makes it steeper; a larger divisor (e.g. 10000.0) flattens it.
  • floor(... * 100.0) / 100.0 rounds to cents. Remove the floor/division to keep full precision.

Sine Wave Seasonality#

Request counts oscillate around a slowly growing baseline. Models daily/weekly traffic cycles, seasonal patterns, or any periodic signal riding on a trend.

Globals

globals:
  traffic_period: 50000
  traffic_amplitude: 100.0
  base_traffic: 100.0

Expression

floor(abs(base_traffic + 0.5 * sqrt(global_iter()) + amplitude * sin(2.0 * pi * global_iter() / period)))

Config

- name: insert_traffic
  type: exec
  args:
    - set_rand(['GET /products', 'GET /orders', 'POST /checkout', 'GET /search'], [])
    - floor(abs(base_traffic + 0.5 * sqrt(global_iter()) + traffic_amplitude * sin(2.0 * pi * global_iter() / traffic_period)))
    - global_iter()
  query: |-
    INSERT INTO traffic_log (endpoint, request_count, iteration)
    VALUES ($1, $2::INT, $3::INT8)

Verification query

SELECT
  bucket,
  round(avg(request_count)::NUMERIC, 1) AS avg_requests,
  min(request_count) AS min_requests,
  max(request_count) AS max_requests,
  repeat('▒', (round(avg(request_count) / max(avg(request_count)) OVER () * 40))::INT) AS histogram
FROM (
  SELECT *, ntile(20) OVER (ORDER BY iteration) AS bucket
  FROM traffic_log
)
GROUP BY bucket
ORDER BY bucket;
  bucket | avg_requests | min_requests | max_requests |                histogram
---------+--------------+--------------+--------------+-------------------------------------------
       1 |        149.5 |          101 |          186 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       2 |        212.8 |          186 |          236 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       3 |        248.8 |          236 |          257 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       4 |        253.9 |          247 |          257 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       5 |        232.8 |          215 |          247 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       6 |        192.7 |          170 |          215 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       7 |        147.7 |          127 |          170 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       8 |        111.6 |          100 |          127 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       9 |         96.9 |           96 |          100 | ▒▒▒▒▒▒▒▒▒▒▒▒
      10 |        107.2 |           98 |          121 | ▒▒▒▒▒▒▒▒▒▒▒▒▒
      11 |        142.9 |          121 |          167 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      12 |        199.3 |          167 |          229 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      13 |        256.6 |          229 |          282 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      14 |        301.4 |          283 |          316 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      15 |        322.7 |          316 |          325 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      16 |        315.4 |          303 |          324 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      17 |        282.0 |          258 |          302 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      18 |        233.1 |          209 |          258 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      19 |        186.0 |          167 |          209 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      20 |        154.7 |          148 |          167 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

Tuning

  • traffic_period - iterations per full sine cycle. This is the most important parameter. If each bucket in your verification query averages over a full cycle or more, the sine wave averages to zero and you only see the baseline trend. See Matching period to your run.
  • traffic_amplitude - peak deviation from the baseline. Must be large enough relative to the baseline to be visible. If base_traffic is 100 and amplitude is 10, the wave is only a 10% wobble.
  • 0.5 * sqrt(global_iter()) - the growth coefficient on the baseline trend. A larger coefficient makes the upward trend dominate the wave; a smaller one lets the oscillation stand out.
  • abs(...) - prevents negative request counts when the trough of the sine wave dips below zero.

Periodic Spikes#

Severity surges at regular intervals then drops to near zero. Models periodic maintenance windows, batch job impacts, or recurring failure modes.

Expression

pow(cos(pi * mod(global_iter(), interval) / interval), 2.0) * 10.0

cos² produces a smooth pulse: maximum (10.0) when mod(iter, interval) = 0, minimum (0.0) at the midpoint of each interval.

Globals

globals:
  spike_interval: 25000

Config

- name: insert_error
  type: exec
  args:
    - uniform(500, 599)
    - pow(cos(pi * mod(global_iter(), spike_interval) / spike_interval), 2.0) * 10.0
    - global_iter()
  query: |-
    INSERT INTO error_events (error_code, severity, iteration)
    VALUES ($1::INT, $2::FLOAT8, $3::INT8)

Verification query

SELECT
  bucket,
  round(avg(severity)::NUMERIC, 2) AS avg_severity,
  round(max(severity)::NUMERIC, 2) AS max_severity,
  repeat('▒', (round(avg(severity) / max(avg(severity)) OVER () * 40))::INT) AS histogram
FROM (
  SELECT *, ntile(20) OVER (ORDER BY iteration) AS bucket
  FROM error_events
)
GROUP BY bucket
ORDER BY bucket;
  bucket | avg_severity | max_severity |                histogram
---------+--------------+--------------+-------------------------------------------
       1 |         8.06 |        10.00 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       2 |         1.43 |         4.67 | ▒▒▒▒▒▒
       3 |         2.85 |         6.56 | ▒▒▒▒▒▒▒▒▒▒▒▒
       4 |         9.05 |        10.00 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       5 |         6.34 |         9.58 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       6 |         0.59 |         2.20 | ▒▒▒
       7 |         4.52 |         8.16 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       8 |         9.38 |        10.00 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       9 |         4.34 |         8.12 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      10 |         0.52 |         1.97 | ▒▒
      11 |         5.91 |         9.36 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      12 |         9.21 |        10.00 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      13 |         3.37 |         7.09 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      14 |         1.09 |         3.91 | ▒▒▒▒▒
      15 |         7.41 |         9.92 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      16 |         8.50 |        10.00 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      17 |         2.03 |         5.37 | ▒▒▒▒▒▒▒▒▒
      18 |         1.92 |         5.11 | ▒▒▒▒▒▒▒▒
      19 |         8.37 |        10.00 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      20 |         7.42 |         9.96 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

Tuning

  • spike_interval - iterations between spike peaks. Same rule as traffic_period: if the interval is shorter than the iteration width of a single bucket, each bucket averages over multiple full cycles and flattens to ~5.0. See Matching period to your run.
  • The * 10.0 multiplier at the end sets the peak severity. Change it to scale the spike magnitude.

Bounded Drift (Arctangent Saturation)#

Sensor readings drift from a true value and asymptotically approach a maximum offset. Models calibration drift, battery degradation, or any quantity that grows quickly then saturates.

Expression (upward drift)

100.0 + (2.0 * atan(sqrt(global_iter()) / 100.0) / pi) * 15.0 + noise

Expression (downward drift)

100.0 - (2.0 * atan(sqrt(global_iter()) / 100.0) / pi) * 15.0 + noise

2 * atan(x) / pi maps any positive input to the range (0, 1). Multiplying by 15 bounds the drift to 0–15 units. The sqrt inside atan gives fast initial movement that slows as it approaches the asymptote. Flip + to - to model decay (e.g. battery degradation) instead of growth.

Config

- name: insert_sensor
  type: exec
  args:
    - uniform(1, 50)
    - 100.0 + (2.0 * atan(sqrt(global_iter()) / 100.0) / pi) * 15.0 + norm_f(0, 0.5, -2, 2, 2)
    - 2.0 * atan(sqrt(global_iter()) / 100.0) / pi * 15.0
    - global_iter()
  query: |-
    INSERT INTO sensor_calibration (sensor_id, reading, drift_offset, iteration)
    VALUES ($1::INT, $2::FLOAT8, $3::FLOAT8, $4::INT8)

Verification query

SELECT
  bucket,
  round(avg(drift_offset)::NUMERIC, 3) AS avg_drift,
  round(avg(reading)::NUMERIC, 2) AS avg_reading,
  repeat('▒', (round(avg(drift_offset) / max(avg(drift_offset)) OVER () * 40))::INT) AS histogram
FROM (
  SELECT *, ntile(10) OVER (ORDER BY iteration) AS bucket
  FROM sensor_calibration
)
GROUP BY bucket
ORDER BY bucket;

Upward drift - readings climb from 100 toward ~115:

  bucket | avg_drift | avg_reading |                histogram
---------+-----------+-------------+-------------------------------------------
       1 |     6.068 |      106.06 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       2 |     9.114 |      109.12 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       3 |    10.209 |      110.22 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       4 |    10.853 |      110.86 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       5 |    11.292 |      111.30 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       6 |    11.621 |      111.62 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       7 |    11.866 |      111.87 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       8 |    12.068 |      112.05 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       9 |    12.235 |      112.21 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      10 |    12.376 |      112.37 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

Downward drift - readings decay from 100 toward ~85:

  bucket | avg_drift | avg_reading |                histogram
---------+-----------+-------------+-------------------------------------------
       1 |   -12.376 |       87.63 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       2 |   -12.235 |       87.79 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       3 |   -12.068 |       87.95 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       4 |   -11.866 |       88.13 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       5 |   -11.621 |       88.38 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       6 |   -11.292 |       88.70 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       7 |   -10.853 |       89.14 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       8 |   -10.209 |       89.78 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
       9 |    -9.114 |       90.88 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
      10 |    -6.068 |       93.94 | ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒

Tuning

  • The * 15.0 multiplier sets the maximum drift (asymptote). Change it to control how far the reading can wander.
  • The / 100.0 inside atan(sqrt(iter) / 100.0) controls how quickly saturation is reached. A smaller divisor reaches the asymptote sooner.
  • norm_f(0, 0.5, -2, 2, 2) adds Gaussian noise to individual readings. Increase the stddev (second arg) for noisier data.

Estimating Total Iterations#

The actual number of global_iter() increments depends on workers, duration, query complexity, and hardware. This matters because periodic parameters (traffic_period, spike_interval) must be tuned relative to the actual iteration count - not the configured total_iters global.

Estimate total iterations from three values:

total_iterations = workers * duration_seconds / avg_latency_seconds
VariableMeaning
workersNumber of concurrent workers (-w flag)
duration_secondsRun duration in seconds (-d flag)
avg_latency_secondsAverage time for one iteration (query execution + overhead)

Example: 10 workers, 60s duration, 5ms average latency:

10 * 60 / 0.005 = 120,000 iterations

This assumes each worker spends all its time executing queries. In practice, connection overhead and GC pauses reduce throughput slightly, so treat the result as an upper bound.

Matching Period to Your Run#

The verification queries use ntile(N) to divide rows into N equal-sized buckets by iteration order. For a periodic pattern to be visible in the histogram, each bucket must cover less than one full cycle. If a bucket spans one or more complete cycles, the periodic component averages out and disappears.

The rule: each bucket’s iteration width should be less than the period.

bucket_width = total_iterations / number_of_buckets

For the pattern to be clearly visible:

period > bucket_width

For 2 visible cycles across all buckets (a good default):

period = total_iterations / 2

Example: with ~100,000 actual iterations and 20 buckets:

  • Each bucket spans ~5,000 iterations
  • traffic_period: 50000 -> 2 full cycles, 10 buckets per cycle - clear wave
  • traffic_period: 5000 -> 20 full cycles, 1 cycle per bucket - wave averages out, histogram looks monotonic
  • traffic_period: 500 -> 200 cycles per bucket - completely flat

The same logic applies to spike_interval. If your histogram looks flat when you expect oscillation, increase the period.

Combining Patterns#

All five patterns run concurrently using run_weights to control the mix:

run_weights:
  insert_view: 25
  insert_price: 20
  insert_traffic: 25
  insert_error: 15
  insert_sensor: 15

Adjust weights to emphasise specific patterns or to control relative table sizes. The weights don’t affect the drift expressions themselves - global_iter() increments globally regardless of which statement executes.