dgraph-io/badger · critical

Arena too small, toWrite:%d newTotal:%d limit:%d

Error message

Arena too small, toWrite:%d newTotal:%d limit:%d

What it means

The skiplist Arena is a fixed-size buffer; putNode reserves space for a new node via an atomic counter and asserts the total stays within the arena capacity. When the allocation would exceed the buffer, this assertion panics. In Badger this normally cannot happen because the memtable is rotated before the arena fills, so hitting it indicates the size accounting/rotation logic was bypassed.

Source

Thrown at skl/arena.go:54

	out.n.Store(1)
	return out
}

func (s *Arena) size() int64 {
	return int64(s.n.Load())
}

// putNode allocates a node in the arena. The node is aligned on a pointer-sized
// boundary. The arena offset of the node is returned.
func (s *Arena) putNode(height int) uint32 {
	// Compute the amount of the tower that will never be used, since the height
	// is less than maxHeight.
	unusedSize := (maxHeight - height) * offsetSize

	// Pad the allocation with enough bytes to ensure pointer alignment.
	l := uint32(MaxNodeSize - unusedSize + nodeAlign)
	n := s.n.Add(l)
	y.AssertTruef(int(n) <= len(s.buf),
		"Arena too small, toWrite:%d newTotal:%d limit:%d",
		l, n, len(s.buf))

	// Return the aligned offset.
	m := (n - l + uint32(nodeAlign)) & ^uint32(nodeAlign)
	return m
}

// Put will *copy* val into arena. To make better use of this, reuse your input
// val buffer. Returns an offset into buf. User is responsible for remembering
// size of val. We could also store this size inside arena but the encoding and
// decoding will incur some overhead.
func (s *Arena) putVal(v y.ValueStruct) uint32 {
	l := v.EncodedSize()
	n := s.n.Add(l)
	y.AssertTruef(int(n) <= len(s.buf),
		"Arena too small, toWrite:%d newTotal:%d limit:%d",
		l, n, len(s.buf))

View on GitHub (pinned to 2a001d466f)

Solutions

  1. Ensure memtable rotation: use badger's MemTable/DB layer which calls arena.ShouldRotate/rotate before capacity is reached, rather than writing directly to a raw skl.Skiplist.
  2. If constructing a skiplist yourself, allocate the arena large enough for your workload (buf size >= total of MaxNodeSize per node + key/value bytes).
  3. Keep keys/values within limits (maxKeySize/MaxValueSize) so a single entry cannot exceed arena size.
  4. Check for regressions if you modified skl or levels.go rotation logic; restore upstream capacity checks.

Example fix

// before
arena := skl.NewArena(1 << 10) // 1KB, too small for workload
list := skl.NewSkiplist(arena)
list.Put(bigKey, bigVal) // panics: Arena too small
// after
arena := skl.NewArena(64 << 20)
list := skl.NewSkiplist(arena)
list.Put(bigKey, bigVal)
Defensive patterns

Strategy: validation

Validate before calling

// before writing to a manually managed skiplist
if int(arena.Size())+skl.MaxNodeSize >= arena.Capacity() || arena.ShouldRotate() {
	rotateMemtable()
}

Try / catch

defer func() {
	if r := recover(); r != nil {
		if strings.Contains(fmt.Sprint(r), "Arena too small") {
			log.Printf("skiplist arena exhausted: %v", r)
		}
		panic(r)
	}
}()

Prevention

When it happens

Trigger: Inserting a node (via skl.Skiplist.newNode, driven by memtable Put) whose required size (key + value + maxHeight offsets) pushes the arena offset past len(s.buf) — i.e. writes continued after the arena's capacity was reached without rotating the memtable.

Common situations: Custom code constructing skl.Skiplist directly with a manually allocated arena that is too small; modifications to memtable rotation thresholds; embedded use of the skiplist package outside badger's memtable management.

Related errors


AI-assisted analysis of dgraph-io/badger@2a001d466f (2026-09-05). Data as JSON: /api/errors/57d0743fa3fbee69. Report an issue: GitHub.