---
plan: 02-02
title: "Ring buffer capacity analysis"
phase: 2
wave: 2
depends_on:
  - 02-01
files_modified:
  - .planning/phases/02-junction-telemetry-audit-items/RING-BUFFER-ANALYSIS.md
requirements:
  - AUDT-01
autonomous: true
estimated_lines: 0 (documentation only)
---

# Plan 02-02: Ring Buffer Capacity Analysis

## Goal

Document whether the current ring buffer capacity (10K, from `OPENDEVIATIONBAR_MAX_PENDING_BARS`) is sufficient for 18 symbols x 4 thresholds during a full-day fill, or if it needs tuning before Phase 4 (parallel fill).

## Must-Haves (Goal-Backward)

1. Written analysis of ring buffer capacity vs worst-case bar production rate
2. Clear recommendation: keep 10K, increase, or make dynamic

## Tasks

### Task 1: Compute worst-case bar production and document analysis (AUDT-01)

<read_first>

- `crates/opendeviationbar-streaming/src/live_engine.rs` lines 112-148 (LiveBarEngineConfig defaults, bar_channel_capacity)
- `crates/opendeviationbar-streaming/src/live_engine.rs` lines 1030-1040 (bar_buffer.len() backpressure logging)
- `crates/opendeviationbar-streaming/src/live_engine.rs` lines 236 (ConcurrentRingBuffer::new)
  </read_first>

<action>
Create `.planning/phases/02-junction-telemetry-audit-items/RING-BUFFER-ANALYSIS.md` with:

1. **Current configuration**: Ring buffer = 10,000 (env: `OPENDEVIATIONBAR_MAX_PENDING_BARS`). `ConcurrentRingBuffer` is a lock-free ring — when full, `push()` returns false and the bar is DROPPED (not blocked).

2. **Worst-case production rate during fill_from_rest**:
   - 18 symbols x 4 thresholds = 72 processor instances
   - Each symbol processes ~50K trades per REST chunk
   - Lowest threshold (250 dbps = 0.25%) on BTCUSDT produces ~1 bar per 50-200 trades
   - Worst case: 50K trades / 50 trades_per_bar = 1,000 bars per chunk per threshold
   - Per symbol per chunk: 4 thresholds x 1,000 = 4,000 bars
   - Sequential fill (current): only 1 symbol fills at a time → 4,000 bars max in flight
   - Parallel fill (Phase 4, 4 workers): 4 symbols x 4,000 = 16,000 bars max in flight

3. **Consumer drain rate**: Python `sidecar.py` polls `collect_bars()` in a loop. Each poll drains the entire ring buffer. Poll interval is ~100ms under normal load. During fill_from_rest, Python is waiting (not polling) — bars accumulate until `start()` returns and the main loop begins.

4. **Key insight**: During `fill_from_rest`, the Python sidecar is NOT consuming bars (it called `fill_from_rest()` synchronously via PyO3). The ring buffer must hold ALL bars produced during the entire fill. For sequential fill of 18 symbols: worst case ~72,000 bars (18 x 4,000). For parallel fill: same total, produced faster.

5. **Recommendation**: 10K is INSUFFICIENT for full-day fill with 18 symbols. Current 2-symbol setup works because ~8,000 bars fit. For Phase 4 (18 symbols), increase to 100K (`OPENDEVIATIONBAR_MAX_PENDING_BARS=100000`) or implement a bounded-channel with backpressure instead of drop-on-full. Flag this as a Phase 4 prerequisite.

6. **Validation approach**: The TELE-05 telemetry from Plan 02-01 (`bars_produced` per symbol) will provide real production numbers to validate these estimates with actual data.
   </action>

<acceptance_criteria>

- File `.planning/phases/02-junction-telemetry-audit-items/RING-BUFFER-ANALYSIS.md` exists
- grep: `10,000` or `10K` appears in the analysis (current capacity)
- grep: `OPENDEVIATIONBAR_MAX_PENDING_BARS` appears in the analysis
- grep: `Phase 4` appears in the analysis (forward reference to parallel fill)
- grep: `Recommendation` or `recommendation` appears in the analysis
  </acceptance_criteria>
