mirror of
https://github.com/luxfi/database.git
synced 2026-10-30 15:01:18 +00:00
Replicates a zapdb to S3 via hanzoai/vfs with no keyspace scan in steady state and post-quantum encryption out of the box. - CDC incrementals via db.Subscribe (the WAL tail), not Backup(since)'s O(total) scan — a 100-key delta out of 200k keys: 2.76us/19KB vs 20ms/1GB (~7000x). - Physical SST-copy snapshots (sparse, hole-aware): 134ms/25MB vs logical 158ms/1.25GB/2.2M allocs. Hydrated before Badger opens; incrementals replay on top. - Streaming codec: Backup -> zstd -> age into a pooled buffer (peak mem = compressed). - Post-quantum (ML-KEM-768 + X25519 via luxfi/age v1.5.0), client-side before S3. Out of the box via REPLICATE_AGE_KEY_FILE (generate+persist, recipient derived). - Per-DB streams (REPLICATE_AUTO_ON_OPEN) for subprocess VM plugins; backer/restorer split (REPLICATE_BACKUP / REPLICATE_SOURCE_ORDINAL) for multi-node. - Public imports only (hanzoai/vfs v0.4.1, no replace). Verified live on an EVM C-chain end-to-end incl. disaster-recovery exact-match. Canonical KV schema (zapdb/zapdb.zap) + zero-copy codec (zapkv.go). Renames engine alias badger->zdb. Co-authored-by: Hanzo Dev <[email protected]>
85 lines
3.3 KiB
Go
85 lines
3.3 KiB
Go
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package zapdb
|
|
|
|
// Canonical ZAP encoding for a KV record — the wire format defined in the
|
|
// colocated schema ./zapdb.zap (written in the ZAP format whose layout rules
|
|
// live in github.com/zap-proto/zap-spec). Unlike luxfi/zapdb's pb.marshal_zap
|
|
// (a sequential [u32 len][bytes]… layout that is "zap" in name only), this is a
|
|
// true ZAP struct: a fixed 48-byte header of scalars and (offset:u32, length:u32)
|
|
// pointer pairs, with payloads in the heap area that follows. Any field resolves
|
|
// in O(1) from its compile-time offset, and the byte accessors return slices that
|
|
// ALIAS the source buffer — zero-copy reads, no per-field allocation.
|
|
//
|
|
// This is the prototype runtime for moving the ZapDB backup codec onto the
|
|
// canonical schema. See zapkv_test.go for byte-stability + zero-copy proof and
|
|
// the package doc note on why this is not yet wired into luxfi/zapdb's backup.
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
// kvHeaderSize is the fixed-area size of a canonical ZAP KV: two u64 scalars
|
|
// (Version, ExpiresAt) + four (offset,length) pointer pairs (Key, Value,
|
|
// UserMeta, Meta), 8 bytes each = 48 bytes. Offsets are from the message start.
|
|
const kvHeaderSize = 48
|
|
|
|
func leU32(b []byte, off int) uint32 { return binary.LittleEndian.Uint32(b[off:]) }
|
|
func leU64(b []byte, off int) uint64 { return binary.LittleEndian.Uint64(b[off:]) }
|
|
|
|
// zapKV is a zero-copy view over a canonical ZAP KV record.
|
|
type zapKV struct{ b []byte }
|
|
|
|
func (k zapKV) Version() uint64 { return leU64(k.b, 0) }
|
|
func (k zapKV) ExpiresAt() uint64 { return leU64(k.b, 8) }
|
|
|
|
// ptr resolves an (offset,length) pointer at header offset hoff to a slice that
|
|
// aliases the underlying message — no copy.
|
|
func (k zapKV) ptr(hoff int) []byte {
|
|
off, length := leU32(k.b, hoff), leU32(k.b, hoff+4)
|
|
if length == 0 {
|
|
return nil
|
|
}
|
|
return k.b[off : off+length]
|
|
}
|
|
|
|
func (k zapKV) Key() []byte { return k.ptr(16) }
|
|
func (k zapKV) Value() []byte { return k.ptr(24) }
|
|
func (k zapKV) UserMeta() []byte { return k.ptr(32) }
|
|
func (k zapKV) Meta() []byte { return k.ptr(40) }
|
|
|
|
// encodeZapKV writes the canonical fixed-header + heap encoding of one KV record.
|
|
func encodeZapKV(version, expiresAt uint64, key, value, userMeta, meta []byte) []byte {
|
|
b := make([]byte, kvHeaderSize+len(key)+len(value)+len(userMeta)+len(meta))
|
|
binary.LittleEndian.PutUint64(b[0:], version)
|
|
binary.LittleEndian.PutUint64(b[8:], expiresAt)
|
|
off := kvHeaderSize
|
|
put := func(hoff int, data []byte) {
|
|
binary.LittleEndian.PutUint32(b[hoff:], uint32(off))
|
|
binary.LittleEndian.PutUint32(b[hoff+4:], uint32(len(data)))
|
|
off += copy(b[off:], data)
|
|
}
|
|
put(16, key)
|
|
put(24, value)
|
|
put(32, userMeta)
|
|
put(40, meta)
|
|
return b
|
|
}
|
|
|
|
// decodeZapKV returns a zero-copy view after bounds-checking every pointer, so an
|
|
// adversarial/truncated buffer can't make an accessor read out of range.
|
|
func decodeZapKV(b []byte) (zapKV, error) {
|
|
if len(b) < kvHeaderSize {
|
|
return zapKV{}, fmt.Errorf("zapkv: record %d bytes < header %d", len(b), kvHeaderSize)
|
|
}
|
|
for _, hoff := range []int{16, 24, 32, 40} {
|
|
off, length := leU32(b, hoff), leU32(b, hoff+4)
|
|
if uint64(off)+uint64(length) > uint64(len(b)) {
|
|
return zapKV{}, fmt.Errorf("zapkv: pointer @%d (off=%d len=%d) exceeds buffer %d", hoff, off, length, len(b))
|
|
}
|
|
}
|
|
return zapKV{b}, nil
|
|
}
|