Skip to content

perf(txn): reduce per-commit allocations on the write path#2307

Open
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:feat/txn-alloc-reduction
Open

perf(txn): reduce per-commit allocations on the write path#2307
shaunpatterson wants to merge 1 commit into
dgraph-io:mainfrom
shaunpatterson:feat/txn-alloc-reduction

Conversation

@shaunpatterson

Copy link
Copy Markdown
Contributor

Motivation

Two allocations fire on every write/commit. pendingWrites map[string]*Entry allocates a string per insert (m[string(key)] = e is not elided by the compiler, only the lookup conversion is), and commitAndSend allocates a fresh y.KeyWithTs buffer per key. On a high-throughput managed write path this is steady GC pressure.

What changed

  • pendingWrites becomes a fingerprint-keyed store (map[uint64][]*Entry keyed by z.MemHash, with byte-compare collision buckets) — no per-insert string allocation. Read-your-writes, last-write-wins for same (key, version), and the managed multi-version → duplicateWrites rule are preserved exactly.
  • Commit carves all key+ts buffers from a single arena (keyWithTs) instead of one make per key; output is byte-identical to y.KeyWithTs, cap-clamped so slices can't corrupt each other, and kept alive by the Entry.Key references handed to the write channel.

Compatibility & correctness

Semantics are unchanged for all update transactions (default and managed); conflict detection (conflictKeys) is a separate, untouched map. The representation is fully encapsulated in txn.go (no external readers; iteration order was already unspecified). -benchmem: ~1 fewer alloc per write at realistic batch sizes (e.g. 1187→1061 allocs/op at 128 keys/commit).

Testing

go build, go vet, full go test . -count=1 green under -race. Tests cover collision buckets, same/different-version dedup, a range-vs-map oracle over thousands of ops, read-your-writes (incl. deletes), managed multi-version round-trips, conflict detection, the sorted pending-writes iterator, and keyWithTs byte-identity/non-aliasing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NtGkC4K2J2XYwcAKwjhHbM

Replace pendingWrites map[string]*Entry with a fingerprint-keyed store
(pendingEntries: map[uint64][]*Entry with byte-compare collision buckets).
This removes the per-write string(e.Key) allocation that Go performs on
every map insert. Get/read-your-writes, last-write-wins, managed
duplicate-version handling and iteration semantics are preserved.

Carve all key+ts buffers at commit from a single per-commit arena
(keyArena) instead of one y.KeyWithTs allocation per key. The arena
copies user keys (never aliases them) and is kept alive by the Entry.Key
slices handed to the async write channel.

Add white-box and end-to-end regression tests and BenchmarkManagedCommit
(-benchmem) demonstrating the allocs/op reduction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NtGkC4K2J2XYwcAKwjhHbM
@shaunpatterson shaunpatterson requested a review from a team as a code owner June 22, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant