Description:
Currently, the Profiler._record_span method executes in the main application thread. It uses a threading.Lock to protect the buffer and, when the buffer is full, performs a synchronous flush to BeaverDB (file I/O) within the same thread.
In high-concurrency asyncio applications or CPU-intensive workloads, this design introduces two major performance bottlenecks:
- Thread Contention: The lock forces serialization of all span completions. If 1,000 tasks finish simultaneously, they must wait in line to record their data.
- I/O Latency: Flushing to disk (even if buffered) blocks the application's execution flow, causing unpredictable latency spikes in endpoint responses.
Proposed Solution:
Decouple the "recording" of a span from the "persistence" of that span. Use a lock-free, thread-safe queue to accept spans immediately, and move all file I/O operations to a dedicated daemon thread.
Tasks:
Acceptance Criteria:
_record_span returns immediately without acquiring locks or performing I/O.
- Heavy loads (e.g., 10,000 spans/sec) do not block the main execution thread.
- All queued spans are successfully flushed to disk upon
profiler.close().
Description:
Currently, the
Profiler._record_spanmethod executes in the main application thread. It uses athreading.Lockto protect the buffer and, when the buffer is full, performs a synchronous flush toBeaverDB(file I/O) within the same thread.In high-concurrency
asyncioapplications or CPU-intensive workloads, this design introduces two major performance bottlenecks:Proposed Solution:
Decouple the "recording" of a span from the "persistence" of that span. Use a lock-free, thread-safe queue to accept spans immediately, and move all file I/O operations to a dedicated daemon thread.
Tasks:
ferret/core.py):self._bufferlist andself._buffer_lockwith aqueue.SimpleQueue(unbounded) orqueue.Queue(bounded, to prevent OOM)._record_spanto simplyput()the span model into the queue. This operation is atomic and requires no explicit locking._background_writermethod that runs in a loop, consuming items from the queue.buffer_sizeitems or a timeout (e.g., 0.5s) before writing toBeaverDB.daemonthread inProfiler.__init__.Profiler.close()to send a "poison pill" or set athreading.Eventto stop the worker.close()waits (thread.join()) for the worker to finish flushing the remaining queue items before returning.Acceptance Criteria:
_record_spanreturns immediately without acquiring locks or performing I/O.profiler.close().