Skip to content

Releases: indexexchange/haproxy-spoe-go

v1.0.10 — Zero-allocation frame encode: pooled write buffers, Marshal appends in place

Choose a tag to compare

@gfeyer gfeyer released this 25 Jul 01:43
c3bd263

Write-path allocation elimination

Production profiling showed the frame write path accounted for ~46% of all allocated
bytes in a high-throughput SPOA under load: Action.Marshal grew a fresh payload
slice per ACK (27.2%), and Frame.Encode built every response in a throwaway
bytes.Buffer which writeFrame then copied into a second one (18.4%). Combined
with the KV.Reset fix in v1.0.9, this release removes the SPOE library from the
allocation profile almost entirely.

Changes

  • Frame.Encode writes from a pooled buffer — the full wire frame (length
    prefix included) is built in a writeBuf retained on the pooled frame across
    reuse cycles (mirror of the v1.0.8 read-buffer reuse), then written to the
    connection in a single Write call. The single write is what keeps
    concurrent ACK writers from interleaving frames on the wire — previously
    guaranteed implicitly by the throwaway buffer, now explicit and documented.
  • Action.Marshal appends into the caller's buffer instead of growing a nil
    slice per action; returns the buffer even on error so pooled callers never
    lose it.
  • typeddata.Encode appends in place with stack-allocated varint scratch
    instead of make([]byte, 8) per value.
  • All per-frame bytes.Buffers removed — writeFrame, sendAgentHello,
    sendAgentDisconnect, and the test client now encode straight to the
    connection.

Benchmarks

BenchmarkFrame_EncodeAck (pooled frame, 4 SetVar actions, go1.26):

ns/op B/op allocs/op
before 272.8 488 11
after 69.9 0 0

Production additionally saves writeFrame's second buffer copy, not captured in
the benchmark.

Validation

Load-tested at 100k QPS before merge: Action.Marshal, KV.UnmarshalNB, and the
encode-buffer bytes.growSlice sites — ~72% of allocated bytes on the previous
release — no longer appear in the allocation profile. GC+malloc CPU share dropped
from ~10% to ~2%; GC cycle rate fell 6×.

v1.0.9 — KV.Reset keeps pooled slice capacity; Go 1.26

Choose a tag to compare

@gfeyer gfeyer released this 24 Jul 22:29

Performance

  • KV.Reset() now keeps slice capacity across pool reuses. Previously it rebuilt
    the backing slice with make([]Item, 0) on every pool release, forcing each pooled
    KV to re-grow from zero per message — ~24% of all allocated bytes in production
    profiles of a high-throughput agent (300k QPS). Reset now clears the items (so
    pooled KVs don't pin the previous request's values) and truncates in place.

  • New payload/kv tests guard the behavior: capacity retention, value clearing, and
    pool round-trip.

Benchmark

BenchmarkAddAfterReset — Reset + 16 Adds, the per-message pooled-KV pattern
(Intel Ultra 7 265HX, go1.26):

ns/op B/op allocs/op
before (make([]Item, 0)) 243.3 992 5
after (clear + kv.m[:0]) 27.4 0 0

Toolchain

  • Go directive bumped 1.19 → 1.26 (matches consumers; enables the clear builtin).
    CI updated to Go 1.26.
  • Fixed a pre-existing vet error in worker/worker_test.go (t.Fatal from a non-test
    goroutine) surfaced by the newer toolchain.

Note: the Go directive bump means consumers must build with Go 1.26+.

v1.0.8 — First Index Exchange release: high-throughput tuning & HAProxy 3.x support

Choose a tag to compare

@gfeyer gfeyer released this 24 Jul 21:05

First release under github.com/indexexchange/haproxy-spoe-go, forked from
negasus/haproxy-spoe-go v1.0.7 and tuned for high-throughput, high-concurrency
workloads on many-CPU machines.

Changes since upstream v1.0.7

  • Module renamed to github.com/indexexchange/haproxy-spoe-go (importable from Go projects)
  • Reuse read buffer across pooled frames to reduce allocation churn (negasus#28)
  • Treat connection reset as normal close — HAProxy 3.x mux_spop compatibility (negasus#27)

Install

go get github.com/indexexchange/haproxy-spoe-go@v1.0.8