Data Pipeline & Streaming Services Upgrade Roadmap
Executive Summary
This document outlines a technical roadmap to upgrade the data pipelines and streaming services of the CLI Proxy API. The current implementation suffers from significant buffering bottlenecks, excessive I/O operations (double-writes), and scalability limits in log retrieval. The proposed upgrades focus on zero-copy streaming, asynchronous processing, and optimized data persistence.
Current Architecture Assessment
1. Proxy & Streaming Service
Current State:
- Buffering: The proxy buffers the entire response body in memory for non-streaming responses (and gzip streams) to perform content rewriting.
- Gzip Handling:
internal/api/modules/amp/proxy.goreads the full body into memory to decompress it ifContent-Encodingis present, defeating the purpose of streaming for compressed upstream responses. - Response Rewriting:
ResponseRewriterbuffers non-streaming bodies entirely. For SSE, it attempts to parse chunks individually, which is brittle if JSON tokens span across network chunk boundaries.
Bottlenecks:
- High memory pressure during large response payloads.
- Increased Time-To-First-Byte (TTFB) due to buffering.
- Potential data corruption in SSE if network chunks split
data:lines or JSON fields.
2. Data Pipeline (Logging)
Current State:
- Write Path:
FileStreamingLogWriterwrites chunks to a temporary file asynchronously. However,Close()triggers a synchronous "assembly" phase that reads the temp file back and writes it to the final log file. This results in 2x Disk I/O (Write Temp -> Read Temp -> Write Final). - Read Path:
LogRepositoryscans all files in the log directory to build a list or find logs. Reading a specific log involves iterating through lines in memory (logAccumulator).
Bottlenecks:
- Double I/O penalty for every logged request.
- Log retrieval performance degrades linearly (O(N)) with the number of log files.
- Synchronous blocking on file system operations during request finalization.
Technical Roadmap
Phase 1: Zero-Buffer Streaming Proxy
Goal: Eliminate memory buffering in the proxy layer to minimize latency and memory footprint.
1.1 Streaming Decompression
- Task: Refactor
proxy.goto use a streaminggzip.Reader(orbrotli/zstdwrappers) that wraps thehttp.Response.Body. - Implementation: Create a
DecompressingReadCloserthat transparently decompresses asRead()is called, rather than pre-reading the whole body. - Benefit: Constant memory usage regardless of response size.
1.2 Streaming Response Rewriter
- Task: Rewrite
ResponseRewriterto use a streaming JSON parser (e.g.,json.Decoderor a token-based replacer) instead ofgjson/sjsonon full buffers. - Implementation:
- Create a
TokenReplacingReaderthat scans the stream for specific keys (model,modelVersion) and replaces values on the fly. - Ensure it maintains state across
Read()calls to handle tokens split across buffer boundaries.
- Create a
- Benefit: Zero-latency overhead for model name rewriting; safe for large JSON bodies.
Phase 2: Robust SSE Handling
Goal: Ensure 100% reliability for streaming AI responses (Server-Sent Events).
2.1 Stateful SSE Parser
- Task: Replace the naive line-splitting logic in
response_rewriter.go. - Implementation:
- Implement a state machine that buffers only incomplete lines.
- Process full
data: {...}lines as they become available. - Handle multi-line JSON data correctly.
- Benefit: Prevents corruption when network packets fragment SSE messages.
Phase 3: High-Performance Logging Pipeline
Goal: Decouple logging from request latency and reduce I/O.
3.1 Eliminate Double-Writes
- Task: Redesign the log storage format to allow append-only writing without post-request assembly.
- Implementation:
- Change log format to a structured line-based JSON (NDJSON) or a format that doesn't require a specific "header-first, body-second" physical layout if possible.
- Alternatively, keep the temp file approach but use
sendfile(viaio.Copyoptimizations) to merge files efficiently, or just move/rename the temp file to the final location if the order can be adjusted.
- Recommendation: Switch to a directory-per-request or a pure append-only log file where request metadata and body chunks are interleaved but tagged with a Request ID. This allows writing directly to the final destination.
3.2 Async Log Persister
- Task: Move file I/O entirely out of the request context.
- Implementation:
- A background worker pool receives
LogEntryobjects (metadata, body chunks) via a buffered channel. - Workers handle file opening/writing/closing independently of the HTTP handler.
- A background worker pool receives
- Benefit: Zero impact of disk latency on API response times.
Phase 4: Scalable Data Access
Goal: Make log retrieval instant regardless of history size.
4.1 Indexing Strategy
- Task: Stop scanning all files for listing/searching.
- Implementation:
- Maintain a lightweight
index.jsonor SQLite DB that tracks:RequestID,Timestamp,Path,StatusCode,Filename. - Update the index asynchronously when logs are finalized.
- Maintain a lightweight
- Benefit: O(1) lookup by Request ID; O(log N) lookup by time range.
4.2 Optimized Reader
- Task: Read logs efficiently.
- Implementation:
- When tailing logs (
latest), read the file backwards from the end (usingSeek) rather than scanning from the start. - Implement pagination for log listing based on the index.
- When tailing logs (
Execution Plan
Step 1 (Critical): Fix the Proxy buffering. This is the biggest risk for production stability.
- Refactor
proxy.gogzip handling. - Refactor
ResponseRewriterfor streaming JSON.
- Refactor
Step 2 (Reliability): Fix SSE parsing in
ResponseRewriter.- Implement stateful line buffering.
Step 3 (Performance): Optimize Log Writing.
- Refactor
RequestLoggerto avoid double-write.
- Refactor
Step 4 (Scalability): Implement Log Indexing.
- Add
LogIndexServiceand updateLogRepositoryto use it.
- Add