diff --git a/cmd/cgat/main.go b/cmd/cgat/main.go
index 20108d1978150527696a92dbf065d2c383eaea22..6058bc691b9cbb5ad01422795d8b90d3255ba39e 100644
--- a/cmd/cgat/main.go
+++ b/cmd/cgat/main.go
@@ -8,7 +8,7 @@ import (
 	"pggat2/lib/bouncer/backends/v0"
 	"pggat2/lib/bouncer/bouncers/v0"
 	"pggat2/lib/bouncer/frontends/v0"
-	"pggat2/lib/middleware/middlewares/unread"
+	"pggat2/lib/middleware/middlewares/onebuffer"
 	"pggat2/lib/middleware/middlewares/unterminate"
 	"pggat2/lib/rob"
 	"pggat2/lib/rob/schedulers/v2"
@@ -61,15 +61,16 @@ func main() {
 			client := zio.MakeReadWriter(conn)
 			ut := unterminate.MakeUnterminate(&client)
 			frontends.Accept(ut)
+			ob := onebuffer.MakeOnebuffer(&client)
 			done := make(chan struct{})
 			defer close(done)
 			for {
-				u, err := unread.NewUnread(&ut)
+				err = ob.Buffer()
 				if err != nil {
 					break
 				}
 				source.Schedule(job{
-					client: u,
+					client: &ob,
 					done:   done,
 				}, 0)
 				<-done
diff --git a/lib/README.md b/lib/README.md
index 3a0c0beb933338280d771c815c771bbd7942d8be..dd8a64fc30b6af2bcb4e336fad9a48cfdf54a953 100644
--- a/lib/README.md
+++ b/lib/README.md
@@ -6,7 +6,7 @@ In general, the top level folder will hold the interface. A subfolder (generally
 ## auth
 All authentication functions. Protocol unspecific.
 
-## bounce
+## bouncer
 All routing: accepting frontends, backends, and handling transactions.
 
 ## middleware
@@ -15,14 +15,14 @@ Intercept packets and perform operations on them
 ## perror
 Special postgres error types
 
-## pnet
-Zero allocation network handling
-
-### pnet/packet
-Packet reading/writing helpers
-
 ## rob
 A fair-share scheduler
 
 ## util
 Project generic helper structures and functions
+
+## zap
+Zero allocation packet handling
+
+### zap/packets
+Packet reading/writing helpers
diff --git a/lib/middleware/middlewares/onebuffer/onebuffer.go b/lib/middleware/middlewares/onebuffer/onebuffer.go
new file mode 100644
index 0000000000000000000000000000000000000000..3ecca273fe48d68b44f96ee178c4a7d71d04efa0
--- /dev/null
+++ b/lib/middleware/middlewares/onebuffer/onebuffer.go
@@ -0,0 +1,57 @@
+package onebuffer
+
+import (
+	"pggat2/lib/util/decorator"
+	"pggat2/lib/zap"
+)
+
+type Onebuffer struct {
+	noCopy decorator.NoCopy
+
+	in   zap.In
+	read bool
+	zap.ReadWriter
+}
+
+func MakeOnebuffer(inner zap.ReadWriter) Onebuffer {
+	return Onebuffer{
+		read:       true,
+		ReadWriter: inner,
+	}
+}
+
+func (T *Onebuffer) Buffer() error {
+	if !T.read {
+		panic("a packet is already buffered in the Onebuffer!")
+	}
+	var err error
+	T.in, err = T.ReadWriter.Read()
+	T.read = false
+	return err
+}
+
+func (T *Onebuffer) BufferUntyped() error {
+	if !T.read {
+		panic("a packet is already buffered in the Onebuffer!")
+	}
+	var err error
+	T.in, err = T.ReadWriter.ReadUntyped()
+	T.read = false
+	return err
+}
+
+func (T *Onebuffer) Read() (zap.In, error) {
+	if !T.read {
+		T.read = true
+		return T.in, nil
+	}
+	return T.ReadWriter.Read()
+}
+
+func (T *Onebuffer) ReadUntyped() (zap.In, error) {
+	if !T.read {
+		T.read = true
+		return T.in, nil
+	}
+	return T.ReadWriter.ReadUntyped()
+}
diff --git a/lib/middleware/middlewares/unread/unread.go b/lib/middleware/middlewares/unread/unread.go
deleted file mode 100644
index c5a860215c675e2607fe530c25c06c8d1316db7f..0000000000000000000000000000000000000000
--- a/lib/middleware/middlewares/unread/unread.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package unread
-
-import (
-	"pggat2/lib/zap"
-)
-
-type Unread struct {
-	in   zap.In
-	read bool
-	zap.ReadWriter
-}
-
-func NewUnread(inner zap.ReadWriter) (*Unread, error) {
-	in, err := inner.Read()
-	if err != nil {
-		return nil, err
-	}
-	return &Unread{
-		in:         in,
-		ReadWriter: inner,
-	}, nil
-}
-
-func NewUnreadUntyped(inner zap.ReadWriter) (*Unread, error) {
-	in, err := inner.ReadUntyped()
-	if err != nil {
-		return nil, err
-	}
-	return &Unread{
-		in:         in,
-		ReadWriter: inner,
-	}, nil
-}
-
-func (T *Unread) Read() (zap.In, error) {
-	if !T.read {
-		T.read = true
-		return T.in, nil
-	}
-	return T.ReadWriter.Read()
-}
-
-func (T *Unread) ReadUntyped() (zap.In, error) {
-	if !T.read {
-		T.read = true
-		return T.in, nil
-	}
-	return T.ReadWriter.ReadUntyped()
-}
-
-var _ zap.ReadWriter = (*Unread)(nil)
diff --git a/lib/util/rbtree/rbtree.go b/lib/util/rbtree/rbtree.go
index 5c93f00d08189a3337f6ea9de99e2b1fa9ae3238..f22265cb43f656584a54e60cfc28bbb96b2138c1 100644
--- a/lib/util/rbtree/rbtree.go
+++ b/lib/util/rbtree/rbtree.go
@@ -9,6 +9,22 @@ type order interface {
 // RBTree is a left-leaning red-black BST
 type RBTree[K order, V any] struct {
 	root *node[K, V]
+	// pool of node to reuse memory
+	pool []*node[K, V]
+}
+
+func (T *RBTree[K, V]) free(n *node[K, V]) {
+	T.pool = append(T.pool, n)
+}
+
+func (T *RBTree[K, V]) alloc() *node[K, V] {
+	if len(T.pool) > 0 {
+		v := T.pool[len(T.pool)-1]
+		T.pool = T.pool[:len(T.pool)-1]
+		*v = node[K, V]{}
+		return v
+	}
+	return new(node[K, V])
 }
 
 func (T *RBTree[K, V]) Get(key K) (V, bool) {
@@ -32,11 +48,11 @@ func (T *RBTree[K, V]) Set(key K, value V) {
 
 func (T *RBTree[K, V]) put(n *node[K, V], key K, value V) *node[K, V] {
 	if n == nil {
-		return &node[K, V]{
-			key:   key,
-			value: value,
-			color: red,
-		}
+		n = T.alloc()
+		n.key = key
+		n.value = value
+		n.color = red
+		return n
 	}
 
 	if key > n.key {
@@ -76,6 +92,7 @@ func (T *RBTree[K, V]) delete(n *node[K, V], key K) *node[K, V] {
 			n = T.rotateRight(n)
 		}
 		if key == n.key && n.right == nil {
+			T.free(n)
 			return nil
 		}
 		if n.right.getColor() == black && n.right.left.getColor() == black {
@@ -95,6 +112,7 @@ func (T *RBTree[K, V]) delete(n *node[K, V], key K) *node[K, V] {
 
 func (T *RBTree[K, V]) deleteMin(n *node[K, V]) *node[K, V] {
 	if n.left == nil {
+		T.free(n)
 		return nil
 	}
 
diff --git a/lib/util/rbtree/rbtree_test.go b/lib/util/rbtree/rbtree_test.go
index 7227adaf7cac0eaa036f029610b11f43010cb5a4..4878b46dea81c05b8877d2b34b479f6d4b06d4b9 100644
--- a/lib/util/rbtree/rbtree_test.go
+++ b/lib/util/rbtree/rbtree_test.go
@@ -101,11 +101,37 @@ func TestRBTree_Stress(t *testing.T) {
 
 	tree := new(RBTree[int, int])
 
+	existing := make(map[int]struct{})
+
 	for i := 0; i < n; i++ {
-		k := rand.Int()
-		v := rand.Int()
-		tree.Set(k, v)
-		tree.Set(rand.Int(), rand.Int())
-		tree.Delete(k)
+		k1 := rand.Int()
+		v1 := rand.Int()
+		k2 := rand.Int()
+		v2 := rand.Int()
+		tree.Set(k1, v1)
+		tree.Set(k2, v2)
+		tree.Delete(k1)
+		delete(existing, k1)
+		existing[k2] = struct{}{}
+	}
+
+	// make sure all values we expect are there
+	for k := range existing {
+		if _, ok := tree.Get(k); !ok {
+			t.Error("expected value to exist")
+		}
+	}
+
+	// iterate through to make sure there aren't any extra values
+	prev := 0
+	for k, v, ok := tree.Min(); ok; k, v, ok = tree.Next(k) {
+		if k < prev {
+			t.Error("expected tree to be correctly ordered")
+		}
+		prev = k
+		if _, ok = existing[k]; !ok {
+			t.Error("unexpected value in tree")
+		}
+		_ = v
 	}
 }