Skip to content

Add bounded typed RequestBatcher with one outcome per accepted request #71

Description

@MasterOfBinary

Current outcome

Give independent callers a plain request/result API while combining compatible calls to a bulk endpoint. ShitQuant's multi-token outcome sampling is the reference workload; the API remains domain-neutral.

Replanned on 2026-09-06 against GoBatch master 63ef757 and ShitQuant's recorder, enrichment, paper/replay, and flow workloads. This is an implementation target, not a claim that the behavior already exists.

Required behavior

  • Use separate request and response types and a unique internal identity for every accepted submission. Repeated lookup keys remain distinct operations unless explicit later coalescing permits equivalence.
  • Bound accepted/queued requests, active handler calls, batch size and waiting resources; reject or cancel admission explicitly under saturation. Do not spawn one goroutine per internally queued request.
  • Use max batch size and max linger; do not inherit MinItems/MinTime configurations that can leave a single caller waiting indefinitely. Queue time counts against the caller deadline.
  • Validate correlation before delivery, including unordered, missing, extra and duplicate responses and partial failure. Never turn a missing result into zero-value success. Adapter-side grouping may map multiple pool rows to one token result.
  • Distinguish batch transport/protocol failure from per-request failure, preserving trustworthy successes only when attribution is safe.
  • A caller's cancellation ends its own wait and cannot cancel unrelated callers in the same handler invocation. Handler execution has a lifecycle context and finite timeout.
  • Make Submit/Close and completion/cancellation races safe; every accepted request gets one terminal outcome. Use a finite graceful-shutdown budget; on expiry terminally settle unresolved waiters with shutdown errors and request execution cancellation. Report actual handler termination separately; do not release active-handler capacity or claim handlers finished until they return.
  • Provide documented construction, close/abort and error contracts without inheriting stream filtering, stage continuation, or the historical Reader/Writer prototype's channel-close races.

Verification and completion

Use a fixture-backed multi-token market adapter and a generic bulk lookup example. Prove result identity under cancellation and malformed/partial responses, bounded saturation, a lone request, concurrent close, and no lost/duplicate replies. Compare upstream calls and latency with straightforward bounded calls/direct grouping.

Follow the repository's formatting, race-test, vet/lint, package documentation, example and changelog requirements for the changed surface. Report the actual supported behavior and migration; do not treat a passing coverage percentage as proof of these outcomes.

Scope and relationships

Independent of flow and of stream filtering/error-channel redesign. Reuse admission/lifecycle primitives only where their contracts fit; settle the relevant #73/lifecycle decisions within this work if not reused. No generic retries, coalescing, Redis integration or provider credentials in this first API. A published version and clean consumer pin remain separate adoption evidence.

Design history

The earlier report/prototype remains below for provenance; the requirements above supersede conflicting prescriptions. Existing discussion is preserved.

Original issue: Synchronous (blocking) batch Reader/Writer API

Summary

Add a sync subpackage providing synchronous, blocking batch APIs on top of the async batch engine, so callers get the efficiency of batching with a simple call-and-wait interface.

Motivation

The core engine is asynchronous (channels in, errors out). Many real workloads — caching layers, DB access, bulk-capable external APIs — want a plain blocking call (value, err := r.Get(ctx, key)) while still coalescing requests into batches behind the scenes.

Proposed API

From feature/sync-package @ 0fd80a9 (~1,180 LOC, generic BatchReader/BatchWriter):

// Reads
readFunc := func(ctx context.Context, keys []string) (map[string]string, error) {
    return db.BatchGet(ctx, keys)
}
cfg := batch.NewConstantConfig(&batch.ConfigValues{MaxItems: 10, MaxTime: 50 * time.Millisecond})
reader := sync.NewBatchReader(cfg, readFunc)
value, err := reader.Get(ctx, "key1")   // blocks; batched behind the scenes

// Writes
writeFunc := func(ctx context.Context, data map[string]string) error {
    return db.BatchSet(ctx, data)
}
writer := sync.NewBatchWriter(cfg, writeFunc)
err := writer.Set(ctx, "key1", "value1")

The prototype handled: per-request context cancellation, request queuing/batching, error propagation (both batch-level and per-item), and graceful shutdown.

Design notes

  • Should be fully generic over key/value types (the prototype predates the feat: migrate GoBatch API to generics #60 generics migration and needs to align with Batch[T]).
  • Natural foundation for concrete adapters — see the Redis source/sink adapter issue.

Source (for recovery — branch is being deleted)

feature/sync-package @ 0fd80a9 — files: sync/doc.go, sync/types.go, sync/reader.go, sync/writer.go, plus tests and sync/example_test.go.


Related: #72 (Redis adapter builds on this API)

Activity

  1. changed the title [-]Synchronous (blocking) batch Reader/Writer API[/-] [+]Add bounded typed RequestBatcher with one outcome per accepted request[/+] on Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions