Skip to content

Durability

Volatile vs durable mode, the pre-ready buffer, and when fire() is not enough.

Updated View as Markdown

LogbunConfig.mode is 'volatile' (default) or 'durable'.

Volatile

The root constructs an internal MemoryReliabilityAdapter when reliability is omitted. persistent is always false. Unflushed queues, the optional in-process journal, and the memory DLQ are lost on process exit.

Volatile mode emits limit / unsafe_default_volatile once at construction (including when you set mode: 'volatile' explicitly).

On request-scoped hosts (Workers, serverless), detached fire() is not a delivery guarantee. Use:

await audit.fireAsync('user.created', { actorId: 'u1' });
await audit.flush();

Durable

Durable mode requires a reliability adapter with persistent: true. Missing reliability or MemoryReliabilityAdapter throws synchronously in the constructor:

durable mode requires LogbunConfig.reliability with a persistent adapter
  (e.g. FileReliabilityAdapter from "logbun/durability/filesystem" or
  CloudflareReliabilityAdapter from "logbun/durability/cloudflare")
durable mode requires a persistent ReliabilityAdapter
  (MemoryReliabilityAdapter is not durable)

ENTERPRISE_DEFAULTS is only { mode: 'durable', requireTenantId: true }. Spreading it without a persistent reliability still throws.

Persistent backends:

Adapter Import Survives
FileReliabilityAdapter logbun/durability/filesystem Process death, if the disk is intact
CloudflareReliabilityAdapter logbun/durability/cloudflare Isolate exit, inside a SQLite Durable Object

Pre-ready buffer

audit.ready is a bootstrap promise. It never rejects. Failure sets degraded and emits bootstrap_fail / degraded.

Logs accepted before ready sit in a volatile buffer (maxPreReadyBuffer, default 10_000) even when mode: 'durable'. Excess emits drop / pre_ready_buffer_full. Durable-mode pre-ready accepts also emit limit / pre_ready_volatile.

Await ready before fire / fireAsync in durable mode. Bootstrap drains the buffer through the real enqueue path (including journal) before ready resolves.

fire vs fireAsync

fire() fireAsync()
Throws never yes
Returns void Promise<void>
Durable admission background; pass waitUntil on Workers awaited
Missing tenant when required drop / require_tenant_id rejects
Oversize tenantId drop / max_tenant_id_bytes rejects
Degraded drop / degraded rejects AuditLogger is degraded — fireAsync unavailable

If a Cloudflare journal/DLQ row commits but getAlarm / setAlarm fails, fireAsync rejects with DurableAdmissionSchedulingError and durableAdmissionCommitted === true. Do not resubmit that event; call requestMaintenance() after the scheduler recovers. fire() rethrows that error into the waitUntil task (and emits flush_fail / durable_admission_scheduling) so Workers keep the isolate awake; fire() itself still does not throw.

import { isDurableAdmissionSchedulingError } from 'logbun';

try {
  await audit.fireAsync('audit.event', input);
} catch (error) {
  if (isDurableAdmissionSchedulingError(error)) {
    await reliability.requestMaintenance();
  } else {
    throw error;
  }
}

isDurableAdmissionSchedulingError accepts the class instance or a duck type with name === 'DurableAdmissionSchedulingError' and durableAdmissionCommitted === true. A lone { durableAdmissionCommitted: true } is not this error.

Degraded

After bootstrap failure, audit.degraded is true. fire drops; fireAsync, query, DLQ ops, flush, and maintenance throw AuditLogger is not initialized (or the fireAsync degraded message). Open a new logger to recover.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close