first commit
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
# `batching`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Buffers outbound log records and flushes them in batches over a single
|
||||
MessagePort frame. Reducing one-frame-per-record to one-frame-per-batch
|
||||
materially lowers the wire cost for workers under load, where a sustained
|
||||
plugin can emit several hundred records per second. The batcher is
|
||||
sink-agnostic — it invokes an `onFlush` callback supplied at construction
|
||||
with a frozen snapshot of buffered records, so it composes equally with
|
||||
the worker-side client's `postMessage` call and with test sinks.
|
||||
|
||||
A second concern handled here is backpressure. Logging is best-effort: a
|
||||
slow main-side sink must never break the application by surfacing as an
|
||||
exception in `log.info(...)`. The batcher caps the number of
|
||||
outstanding-but-not-yet-released batches at `maxOutstandingBatches`; once
|
||||
the cap is reached new records are dropped silently and an `onDrop`
|
||||
callback fires so the consumer can increment the
|
||||
`LOGGER_PROXY_BACKPRESSURE_DROP` counter.
|
||||
|
||||
## Features
|
||||
|
||||
- Flush triggers: max batch size (default 100), idle interval (default
|
||||
50 ms), `error`/`fatal` level (immediate, but records buffered before
|
||||
the error still ship in the same batch — order is preserved).
|
||||
- Bounded outstanding-batch budget (default 8). Dropped records increment
|
||||
a counter; the batcher never throws on `enqueue`.
|
||||
- Injectable `TimeoutScheduler` for deterministic timer tests.
|
||||
- `dispose()` clears the timer and drops anything still buffered.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```ts
|
||||
import { LogBatcher } from "@suaveplan/api-logger-worker-proxy/batching";
|
||||
|
||||
const batcher = new LogBatcher((records) => {
|
||||
port.postMessage({
|
||||
v: "1.0.0",
|
||||
kind: "log-batch",
|
||||
plugin: { name: "demo", version: "0.1.0" },
|
||||
records,
|
||||
});
|
||||
batcher.releaseBatch();
|
||||
});
|
||||
|
||||
batcher.enqueue({
|
||||
level: "info",
|
||||
msg: "hello",
|
||||
ts: new Date().toISOString(),
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
Wiring the drop counter to an OTel meter and using a deterministic
|
||||
scheduler in tests:
|
||||
|
||||
```ts
|
||||
import {
|
||||
LogBatcher,
|
||||
type TimeoutScheduler,
|
||||
} from "@suaveplan/api-logger-worker-proxy/batching";
|
||||
|
||||
let dropped = 0;
|
||||
const fakeScheduler: TimeoutScheduler = {
|
||||
set: (h) => {
|
||||
queueMicrotask(h);
|
||||
return null;
|
||||
},
|
||||
clear: () => {},
|
||||
};
|
||||
|
||||
const batcher = new LogBatcher((records) => sink.write(records), {
|
||||
maxBatchSize: 10,
|
||||
flushIntervalMs: 5,
|
||||
maxOutstandingBatches: 4,
|
||||
onDrop: () => {
|
||||
dropped++;
|
||||
},
|
||||
scheduler: fakeScheduler,
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `new LogBatcher(onFlush, options?)` — constructor.
|
||||
- `enqueue(record): boolean` — `true` accepted, `false` dropped.
|
||||
- `flush()` — flush whatever is buffered; idempotent.
|
||||
- `releaseBatch()` — return a batch slot to the budget.
|
||||
- `dispose()` — stop accepting; drop everything in the buffer.
|
||||
- `droppedCount` / `flushedBatchCount` / `bufferedCount` /
|
||||
`outstandingBatches` — read-only counters for tests + telemetry.
|
||||
- `DEFAULT_BATCHER_OPTIONS` — the defaults exposed for re-use.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`error` and `fatal` flush immediately AFTER being appended to the buffer
|
||||
so any records queued before them ship in the same batch and the
|
||||
ordering is preserved. The `outstanding` counter is decremented by
|
||||
`releaseBatch()`; the worker-side client invokes that synchronously
|
||||
after `postMessage` returns since `MessagePort` has no ack frame for
|
||||
log batches and there is no transport-level confirmation to wait for.
|
||||
Best-effort posture: every code path that could otherwise throw on a
|
||||
log call (full buffer, disposed batcher) routes through `recordDrop`
|
||||
instead.
|
||||
Reference in New Issue
Block a user