Code cleanup, buffer pool

master v1.0.0
jdl 2022-07-30 05:58:35 +02:00
parent d90be1a6b5
commit 9b47a64827
5 changed files with 72 additions and 39 deletions

View File

@ -1,7 +1,10 @@
package mdb package mdb
import ( import (
"bytes"
"encoding/json" "encoding/json"
"git.crumpington.com/private/mdb/kvstore"
) )
func decode[T any](data []byte) *T { func decode[T any](data []byte) *T {
@ -11,7 +14,7 @@ func decode[T any](data []byte) *T {
} }
func encode(item any) []byte { func encode(item any) []byte {
buf, err := json.Marshal(item) w := bytes.NewBuffer(kvstore.GetDataBuf(0)[:0])
must(err) must(json.NewEncoder(w).Encode(item))
return buf return w.Bytes()
} }

View File

@ -1,9 +1,36 @@
package kvstore package kvstore
import "time" import (
"time"
)
var ( var (
connTimeout = 16 * time.Second connTimeout = 16 * time.Second
heartbeatInterval = 4 * time.Second pollInterval = 500 * time.Millisecond
pollInterval = 500 * time.Millisecond modQSize = 1024
bufferPool = make(chan []byte, 1024)
poolBufSize = 4096
) )
func GetDataBuf(size int) []byte {
if size > poolBufSize {
return make([]byte, size)
}
select {
case b := <-bufferPool:
return b[:size]
default:
return make([]byte, poolBufSize)[:size]
}
}
func RecycleDataBuf(b []byte) {
if cap(b) != poolBufSize {
return
}
select {
case bufferPool <- b:
default:
}
}

View File

@ -22,23 +22,20 @@ func (kv *KV) SyncRecv(conn net.Conn) {
// It's important that we stop when this routine exits so that // It's important that we stop when this routine exits so that
// all queued writes are committed to the database before syncing // all queued writes are committed to the database before syncing
// has a chance to restart. // has a chance to restart.
//kv.startWriteLoop()
//defer kv.stopWriteLoop()
w := newWriter(kv.db) w := newWriter(kv.db)
w.Start(kv.MaxSeqNum()) w.Start(kv.MaxSeqNum())
defer w.Stop() defer w.Stop()
headerBuf := make([]byte, recHeaderSize) headerBuf := make([]byte, recHeaderSize)
buf := make([]byte, 8) nameBuf := make([]byte, 32)
afterSeqNum := kv.MaxSeqNum() afterSeqNum := kv.MaxSeqNum()
expectedSeqNum := afterSeqNum + 1 expectedSeqNum := afterSeqNum + 1
// Send fromID to the conn. // Send fromID to the conn.
conn.SetWriteDeadline(time.Now().Add(connTimeout)) conn.SetWriteDeadline(time.Now().Add(connTimeout))
binary.LittleEndian.PutUint64(buf, afterSeqNum) binary.LittleEndian.PutUint64(nameBuf, afterSeqNum)
if _, err := conn.Write(buf); err != nil { if _, err := conn.Write(nameBuf); err != nil {
log.Printf("RecvWAL failed to send after sequence number: %v", err) log.Printf("RecvWAL failed to send after sequence number: %v", err)
return return
} }
@ -51,7 +48,7 @@ func (kv *KV) SyncRecv(conn net.Conn) {
log.Printf("RecvWAL failed to read header: %v", err) log.Printf("RecvWAL failed to read header: %v", err)
return return
} }
rec, colLen, dataLen := decodeRecHeader(headerBuf) rec, nameLen, dataLen := decodeRecHeader(headerBuf)
// Heartbeat. // Heartbeat.
if rec.SeqNum == 0 { if rec.SeqNum == 0 {
@ -65,29 +62,29 @@ func (kv *KV) SyncRecv(conn net.Conn) {
} }
expectedSeqNum++ expectedSeqNum++
if cap(buf) < colLen { if cap(nameBuf) < nameLen {
buf = make([]byte, colLen) nameBuf = make([]byte, nameLen)
} }
buf = buf[:colLen] nameBuf = nameBuf[:nameLen]
if _, err := conn.Read(buf); err != nil { if _, err := conn.Read(nameBuf); err != nil {
log.Printf("RecvWAL failed to read collection name: %v", err) log.Printf("RecvWAL failed to read collection name: %v", err)
return return
} }
rec.Collection = string(buf) rec.Collection = string(nameBuf)
if rec.Store { if rec.Store {
rec.Data = make([]byte, dataLen) rec.Data = GetDataBuf(dataLen)
if _, err := conn.Read(rec.Data); err != nil { if _, err := conn.Read(rec.Data); err != nil {
log.Printf("RecvWAL failed to read data: %v", err) log.Printf("RecvWAL failed to read data: %v", err)
return return
} }
w.StoreAsync(rec.Collection, rec.ID, rec.Data)
kv.onStore(rec.Collection, rec.ID, rec.Data) kv.onStore(rec.Collection, rec.ID, rec.Data)
w.StoreAsync(rec.Collection, rec.ID, rec.Data)
} else { } else {
w.DeleteAsync(rec.Collection, rec.ID)
kv.onDelete(rec.Collection, rec.ID) kv.onDelete(rec.Collection, rec.ID)
w.DeleteAsync(rec.Collection, rec.ID)
} }
} }
} }

View File

@ -14,7 +14,6 @@ func (kv *KV) SyncSend(conn net.Conn) {
seqNumBuf = make([]byte, 8) seqNumBuf = make([]byte, 8)
headerBuf = make([]byte, recHeaderSize) headerBuf = make([]byte, recHeaderSize)
empty = make([]byte, recHeaderSize) empty = make([]byte, recHeaderSize)
tStart time.Time
err error err error
) )
@ -29,14 +28,13 @@ func (kv *KV) SyncSend(conn net.Conn) {
POLL: POLL:
conn.SetWriteDeadline(time.Now().Add(connTimeout)) for i := 0; i < 8; i++ {
tStart = time.Now()
for time.Since(tStart) < heartbeatInterval {
if kv.MaxSeqNum() > afterSeqNum { if kv.MaxSeqNum() > afterSeqNum {
goto REPLAY goto REPLAY
} }
time.Sleep(pollInterval) time.Sleep(pollInterval)
} }
goto HEARTBEAT goto HEARTBEAT
HEARTBEAT: HEARTBEAT:
@ -91,6 +89,9 @@ func (kv *KV) replay(afterSeqNum uint64, each func(rec record) error) error {
must(err) must(err)
defer rows.Close() defer rows.Close()
rec.Data = GetDataBuf(0)
defer RecycleDataBuf(rec.Data)
for rows.Next() { for rows.Next() {
must(rows.Scan( must(rows.Scan(
&rec.SeqNum, &rec.SeqNum,
@ -98,6 +99,7 @@ func (kv *KV) replay(afterSeqNum uint64, each func(rec record) error) error {
&rec.ID, &rec.ID,
&rec.Store, &rec.Store,
&rec.Data)) &rec.Data))
if err = each(rec); err != nil { if err = each(rec); err != nil {
return err return err
} }

View File

@ -17,7 +17,7 @@ func newWriter(db *sql.DB) *writer {
return &writer{ return &writer{
db: db, db: db,
stop: make(chan struct{}, 1), stop: make(chan struct{}, 1),
modQ: make(chan modJob, 1024), modQ: make(chan modJob, modQSize),
} }
} }
@ -34,6 +34,7 @@ func (w *writer) Stop() {
w.wg.Wait() w.wg.Wait()
} }
// Takes ownership of the incoming data.
func (w *writer) Store(collection string, id uint64, data []byte) { func (w *writer) Store(collection string, id uint64, data []byte) {
job := modJob{ job := modJob{
Collection: collection, Collection: collection,
@ -59,6 +60,7 @@ func (w *writer) Delete(collection string, id uint64) {
job.Ready.Wait() job.Ready.Wait()
} }
// Takes ownership of the incoming data.
func (w *writer) StoreAsync(collection string, id uint64, data []byte) { func (w *writer) StoreAsync(collection string, id uint64, data []byte) {
w.modQ <- modJob{ w.modQ <- modJob{
Collection: collection, Collection: collection,
@ -80,7 +82,7 @@ func (w *writer) run(maxSeqNum uint64) {
defer w.wg.Done() defer w.wg.Done()
var ( var (
job modJob mod modJob
tx *sql.Tx tx *sql.Tx
insertData *sql.Stmt insertData *sql.Stmt
insertKV *sql.Stmt insertKV *sql.Stmt
@ -102,7 +104,7 @@ BEGIN:
wgs = wgs[:0] wgs = wgs[:0]
select { select {
case job = <-w.modQ: case mod = <-w.modQ:
case <-w.stop: case <-w.stop:
return return
} }
@ -117,12 +119,12 @@ BEGIN:
LOOP: LOOP:
if job.Ready != nil { if mod.Ready != nil {
wgs = append(wgs, job.Ready) wgs = append(wgs, mod.Ready)
} }
newSeqNum++ newSeqNum++
if job.Store { if mod.Store {
goto STORE goto STORE
} else { } else {
goto DELETE goto DELETE
@ -137,13 +139,15 @@ STORE:
must(err) must(err)
} }
_, err = insertData.Exec(newSeqNum, job.Data) _, err = insertData.Exec(newSeqNum, mod.Data)
must(err) must(err)
_, err = insertKV.Exec(job.Collection, job.ID, newSeqNum) _, err = insertKV.Exec(mod.Collection, mod.ID, newSeqNum)
must(err) must(err)
_, err = insertLog.Exec(newSeqNum, now, job.Collection, job.ID, true) _, err = insertLog.Exec(newSeqNum, now, mod.Collection, mod.ID, true)
must(err) must(err)
RecycleDataBuf(mod.Data)
goto NEXT goto NEXT
DELETE: DELETE:
@ -155,11 +159,11 @@ DELETE:
must(err) must(err)
} }
_, err = deleteData.Exec(job.Collection, job.ID) _, err = deleteData.Exec(mod.Collection, mod.ID)
must(err) must(err)
_, err = deleteKV.Exec(job.Collection, job.ID) _, err = deleteKV.Exec(mod.Collection, mod.ID)
must(err) must(err)
_, err = insertLog.Exec(newSeqNum, now, job.Collection, job.ID, false) _, err = insertLog.Exec(newSeqNum, now, mod.Collection, mod.ID, false)
must(err) must(err)
goto NEXT goto NEXT
@ -167,7 +171,7 @@ DELETE:
NEXT: NEXT:
select { select {
case job = <-w.modQ: case mod = <-w.modQ:
goto LOOP goto LOOP
default: default:
} }