Skip to content

feat(#116): setBatch() helper with mutation-budget enforcement - #117

Merged
s2x merged 1 commit into
masterfrom
feat/116-setbatch
Sep 9, 2026
Merged

s2x merged 1 commit into
masterfrom
feat/116-setbatch

Conversation

@s2x

@s2x s2x commented Sep 9, 2026

Copy link
Copy Markdown
Member

Summary

Implements the batch write helpers from #116:

  • Transaction::setBatch(iterable $pairs) — queues [key, value] pairs (string or KeyConvertible keys) into the current transaction. The total key+value byte size is validated against FoundationDB's 10,000,000 B per-transaction mutation budget before any mutation is queued; an oversized batch throws the new BatchTooLargeException (public readonly batchSize / maxBatchSize) at the call site instead of an opaque server-side error during commit(). Nothing is written.
  • Database::setBatch(iterable $pairs, bool $split = false) — single-transaction mode via transact() (retries included), or split: true mode that groups entries under MutationBudget::SPLIT_TARGET_BYTES (8,000,000 B) and commits each group in its own retried transaction. Per-key atomicity guaranteed; no cross-key snapshot consistency and no all-or-nothing commit — documented in the PHPDoc and docs/batch-writes.md (deliberately not symmetric with the chunked-values meta-swap from [Feature] Chunked values: setValueChunked() / getValueChunked() #115).
  • MutationBudget (@internal) — pure byte-accounting shared infrastructure, deliberately ignorant of the chunked key-space; the chunked layer ([Feature] Chunked values: setValueChunked() / getValueChunked() #115) will reuse it but not the \x00 key-space codec.
  • Generator-backed iterable input is consumed lazily (no double materialization).

Semantics

  • Blind writes: two concurrent setBatch() calls do not conflict with each other (last commit wins); read-modify-write conflict detection comes from reads in the same transaction.
  • Duplicate keys: last entry wins; ordering not guaranteed.
  • Transaction::setBatch always throws above budget (splitting inside a caller-owned transaction makes no sense); split exists only on Database::setBatch.

Closes #116

Testing

  • composer lint — PHPCS + Rector + PHPStan level 9: clean
  • composer test:unit — 602 tests OK (incl. new MutationBudgetTest)
  • composer test:integration — 292 tests OK (incl. new SetBatchTest: round-trip at value-limit boundaries, oversized batch rejected before any mutation and DB stays usable, split mode across multiple transactions, generator input, empty batch no-op, read-modify-write conflict/retry)

Docs & changelog

- Transaction::setBatch(iterable $pairs): queues [key, value] pairs
  (string|KeyConvertible keys) into the current transaction; total
  key+value bytes validated against the 10 MB per-transaction mutation
  budget BEFORE any mutation is queued, throwing BatchTooLargeException
  (public readonly batchSize/maxBatchSize) at the call site
- Database::setBatch(iterable $pairs, bool $split = false): single
  transaction via transact() (retries included), or split: true mode
  grouping entries under MutationBudget::SPLIT_TARGET_BYTES (8 MB) with
  per-group retried transactions (per-key atomicity, no cross-key
  snapshot consistency — documented)
- Shared @internal MutationBudget accounting (pure byte math, reusable
  by the upcoming chunked-values layer from #115)
- Generator-backed iterables consumed lazily
- Unit tests (MutationBudgetTest) + integration tests (SetBatchTest);
  docs/batch-writes.md + CHANGELOG entry
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] setBatch() helper — multi-key writes with transaction budget enforcement

1 participant