Use this file to discover all available pages before exploring further.
Idempotent producers provide exactly-once write semantics for Durable Streams, eliminating duplicates from client retries while enabling fire-and-forget writes with automatic batching and pipelining.
Unlike regular appends, producer.append() returns immediately without waiting for the network:
// Regular append - waits for server responseawait stream.append(data) // Blocks until server ACKs// Idempotent producer - returns immediatelyproducer.append(data) // Returns instantly, batches in backgroundproducer.append(data) // Returns instantlyproducer.append(data) // Returns instantly// Wait for all pending messagesawait producer.flush() // Now waits for server ACKs
Fire-and-forget means you can write thousands of messages without blocking. The producer automatically batches and pipelines requests for maximum throughput.
The producer automatically batches multiple append() calls into single HTTP requests:
const producer = new IdempotentProducer(stream, 'my-producer', { epoch: 0, maxBatchBytes: 1024 * 1024, // 1MB batch size lingerMs: 5 // Wait up to 5ms for more messages})// These 3 appends are batched into 1 HTTP requestproducer.append('{"id": 1}')producer.append('{"id": 2}')producer.append('{"id": 3}')// Batch is sent when:// 1. maxBatchBytes is reached, OR// 2. lingerMs elapses, OR// 3. flush() is called
Batching parameters:
maxBatchBytes: Maximum batch size (default: 1MB)
lingerMs: Maximum time to wait for more messages (default: 5ms)
Tune these based on your workload:
High-frequency writes: Use larger batches (1-10MB) and longer linger (10-50ms)
Low-latency requirements: Use smaller batches (64-256KB) and shorter linger (1-5ms)
Idempotent producers support pipelining - multiple batches in flight simultaneously:
const producer = new IdempotentProducer(stream, 'my-producer', { epoch: 0, maxInFlight: 5, // Up to 5 concurrent HTTP requests maxBatchBytes: 256 * 1024, // 256KB batches lingerMs: 10})// Producer can have up to 5 batches in flight at oncefor (let i = 0; i < 1000; i++) { producer.append(JSON.stringify({ id: i }))}await producer.flush()
Pipelining dramatically improves throughput over high-latency connections. With maxInFlight=5 and 100ms latency, you can achieve 5x the throughput compared to serial requests.
For ephemeral producers (serverless, testing), use autoClaim to automatically claim epochs:
const producer = new IdempotentProducer(stream, 'my-producer', { epoch: 0, autoClaim: true // Auto-retry with epoch+1 on stale epoch errors})// If another producer already claimed epoch=0,// this producer automatically retries with epoch=1
Auto-claim should be used cautiously. In production, prefer explicit epoch management with persistent storage to avoid accidental zombie fencing.
Idempotent producer close is fully idempotent, even with a final message:
// Close with final message (idempotent)await producer.close(JSON.stringify({ status: 'complete' }))// Safe to retry - deduplication worksawait producer.close(JSON.stringify({ status: 'complete' }))// Returns same finalOffset, no duplicate// Or close without final messageawait producer.close()
Unlike DurableStream.close({ body }) which is not idempotent with a body, IdempotentProducer.close() uses producer headers for deduplication, making it safe to retry.
// Stop this producer but keep stream openawait producer.detach()// Another producer can continue writingconst producer2 = new IdempotentProducer(stream, 'producer-2', { epoch: 0 })producer2.append('more data')