Table capacity and performance

Choose an access path by the work it does: local mapped records, HTTP queries or persistent table synchronization. Compare latency, payload rates, working-set size and contention together.

The evidence below covers local synthetic benchmarks and passive production HTTP observations. Maximum capacity has not been established for any mode. WebSocket and TCP table synchronization have no published capacity measurement here. The implementations describe capabilities, not measured speed or a ranking between transports.

Different access paths expose different costs. Their existing results use different workloads.
Access modeStrength Limits and evidence
Local memmap Processes on one host share mapped record pages. Resident data can be accessed without an HTTP round trip or JSON serialization. Warm-cache table reads and writes are benchmarked below. The working set, page faults, storage and the per-table index mutex affect capacity. Remote clients cannot share these local pages.
HTTP Data API Authenticated queries resolve entities, select data and return JSON with provenance to remote clients. Observed means and payload sizes are available. Query work, serialization and network delivery add costs; the server timer excludes delivery. Concurrent-client capacity is unmeasured.
WebSocket table sync A persistent connection transfers LZ4-compressed binary record batches to another table, avoiding JSON conversion of each row. Throughput and end-to-end update lag are unmeasured. Lookback scanning, polling, compression, pacing, client fan-out and receiver apply work affect capacity.
TCP socket table sync A persistent TCP connection transfers length-prefixed, LZ4-compressed binary record batches to another table. Throughput and end-to-end update lag are unmeasured. It shares the synchronization and pacing logic with WebSocket table sync; lower framing overhead alone does not establish higher capacity.

For both table-sync transports, the client APIs default to a pacing target of 1,000,000 compressed body bytes per second per connection, about 0.954 MiB/s. This is a configurable target, not a benchmark or a hardware limit; framing and network overhead are additional. Raw record throughput can exceed the compressed byte rate when data compresses well. Table synchronization tracks record state within its lookback settings; it does not establish delivery of every intermediate update.

Local memmap: measured read and write costs

Two synthetic runs on 31 October 2026 used SharedData 6.86.0, one process and warm page-cache data. Creation, JIT compilation and warm-up preceded timing. The dataset contained 5,000 keyed records and 10,000 history records; the range selection returned 125 rows. These measure the Python table API, including indexed selection, rather than a raw memory load.

Microseconds per operation. Paired values are run 1 / run 2; p50 and p95 are nearest-rank percentiles.
OperationRows per call p50 µs, runs 1 / 2p95 µs, runs 1 / 2
Primary-key read1 46.89 / 47.7277.28 / 72.61
Date range for one symbol125 195.56 / 203.08314.58 / 261.61
Upsert one record1 79.87 / 82.22122.17 / 121.41
Batch upsert, mmap-visible100 225.38 / 191.18296.95 / 250.55
Batch upsert plus explicit write()100 6,132.67 / 6,060.996,328.46 / 7,277.40

Small warm reads and batched updates are strengths in this workload. The explicit table write() step costs substantially more than making an update visible through mmap. Neither variant establishes fsync-level durability. These single-process latency samples do not establish maximum operations per second, RAM bandwidth, cold-cache performance or capacity under concurrent readers and writers.

Read the benchmark method and sample counts, or download the sanitized reports for run 1 and run 2.

HTTP API: observed production latency and payload work

Observed server timings and response volume from normal production traffic, collected without issuing data queries or a load test. A separate observation measures payload volume over elapsed wall time.

Measured runtime: 6.86.0. Counter snapshots were collected on 4 November 2026, 14:08:06–14:08:53 UTC. Results cover seven of eight workers alive during collection; the bounded observation ended before the remaining worker was reached. These are partial observations of one deployment and its workload, with no service-level guarantee.

Each worker's counters cover completed requests since that worker started. The oldest observed history starts at 13:20:57 UTC that day; other workers started later. The collection interval above describes when the counters were gathered.

Existing GET requests in the covered workers. Means include successful and failed responses.
OperationRequests Mean server msMean response KiB HTTP errors
Source discovery/api/data/sources 2474.20 9.30
Symbol listing/api/data/symbols 3 (small sample)98.65 457.00
Symbol identity/api/data/symbol/{symbol} 244.09 5.90
Market-data fetch/api/data/marketdata/{symbol} 1,706584.38 1,930.12

KiB = 1,024 bytes. HTTP errors count responses with status 400 or higher. The market-data observations contain 1,704 HTTP 200s and two HTTP 500s. The three-request symbol-listing history is too small for a stable estimate.

Request sizes and query shapes differ between operations. A market-data response averaged about 1.9 MiB, so its mean should not be used as the latency of a small record lookup. The counters contain no date-range, row-count, field-count or cache-state breakdown. No measurements are claimed for the other Data API routes.

Payload size puts that latency in context: the exact market-data means give 3.23 MiB per server-timed second, from 1,976,438.26 response bytes divided by 0.58437594 seconds and 1,048,576 bytes per MiB. This equals total counted response bytes divided by total recorded server duration for those requests. It is a payload/server-time ratio, not a mean of individual request transfer rates. It excludes network delivery and does not establish maximum throughput or the number of clients that can be served concurrently.

What the numbers measure

The running server's /api/traffic_stats counters provide a count and mean for each operation in each worker. The observer identified responding workers through local socket ownership, retained one latest snapshot per worker, and combined the means using request counts as weights:

mean_ms = 1000 × sum(worker_requests × worker_mean_seconds)
                 / sum(worker_requests)

The timer runs inside Flask, from its request timing hook to its response hook. Network round trips, proxies, body transfer to the client and work before the timing hook are outside this measurement. Initial authentication checks can precede that hook. Response sizes are the bytes counted by the application before any downstream proxy compression.

p50, p95, p99 and maxima are unavailable. The live operation counters do not retain individual latencies or histograms, and no matching per-request Data API timing records were available. Percentiles cannot be reconstructed from counts and means.

Unfinished requests, including requests still blocked on a lock, are absent from completed-request counters. Retired workers' histories and the unobserved worker are also absent. These omissions can understate the cost of contention. The values mix the covered workers' warm-up and later work; cold-cache latency, maximum throughput and saturation were not isolated.

Bandwidth: observed response payload throughput

A separate passive observation on 4 November 2026, 14:36:36–14:38:10 UTC covered 94.02 seconds of normal traffic on runtime 6.86.0. Matched counter changes account for 40,089,055 response bytes across 105 completed Data API responses, all HTTP 200. Dividing those bytes by the full observation interval gives 3.41 Mbit/s (0.407 MiB/s) of observed application payload generation. This is a conservative partial-coverage rate.

Counted changes during the bandwidth observation. Omitted traffic is not estimated.
OperationCompleted responses Response bytes
Market-data fetch/api/data/marketdata/{symbol} 9738,938,952
Source discovery/api/data/sources 328,446
Symbol identity/api/data/symbol/{symbol} 318,076
Symbol listing/api/data/symbols 21,103,581
Counted total10540,089,055

Mbit/s = 1,000,000 bits per second; MiB/s = 1,048,576 bytes per second. Rates and elapsed time are rounded. This window is separate from the lifetime latency counters above.

Six of eight live workers were reached; five had at least two snapshots and contributed counter deltas. Each contributing worker's first and last snapshots span a different subinterval, ranging from 10.02 to 88.01 seconds. Traffic before its first or after its last snapshot, the worker seen only once and the two unobserved workers are omitted. The denominator remains the entire 94.02-second window, rather than adding rates from unequal windows or extrapolating missing traffic.

counted_bytes = sum(last_response_bytes - first_response_bytes
                    for each matched worker)
observed_Mbit_per_second = 8 × counted_bytes / wall_seconds / 1000000

This does not measure maximum bandwidth or client download speed. The server counts response Content-Length at its response hook, before delivery. These bytes describe generated application bodies; they do not confirm that clients received them. Headers, TCP/TLS overhead, retransmissions, downstream compression and client connection speed are outside these counters. The observed request-body byte change was zero; that does not establish zero incoming network traffic or upload capacity.

The observer made 48 sequential metadata reads, at least two seconds apart, with the same 250 ms stop threshold and no retries. Observed metadata round trips ranged from 14.4 to 27.7 ms. No data queries, concurrent downloads or saturation traffic were injected. Worker identities remained stable, and health, runtime version and build matched before and after collection. Maximum transfer capacity and sustained throughput under a controlled workload remain unmeasured.

What a capacity decision still needs

Storage size, response rate and concurrent-client capacity are different measurements. A useful sizing result fixes the workload and reports what happens as demand increases. The measurements on this page provide starting points; none is a saturation test.

A controlled comparison needs an isolated table and bounded client workload. Production observations here added no table queries or load tests. WebSocket/TCP capacity, cold-cache behavior, contention tails and maximum sustainable rates remain open measurements.

How production load was kept bounded

Both production observations issued zero Data API or table-query requests. For the latency observation, the observer read existing counters sequentially: two validation reads, then a run capped at 48 metadata probes, spaced at least one second apart. Each probe had a one-second connection timeout and a two-second read timeout; the run would stop after an error or a metadata round trip exceeding 250 ms. There were no automatic retries.

Only route-level counts, timings, sizes and status totals were used for this page. Instruments, sources, dataset addresses, users, tokens, machine identities and production record values are omitted. No services, configuration, indexes or datasets were changed for the measurement.

A client timeout alone does not cancel work already running on the server. That is why this observation used existing counters instead of injecting table reads or lock contention.

Storage and concurrency limits

Read the Data API contract and table interfaces. The separate socket and WebSocket connection guide describes setup, authentication, synchronization and transport limits. The synthetic local-container benchmark reports p50/p95 for a different, isolated Python workload.