In-memory head with WAL
Head connects chunkenc and WAL: label-to-ref resolution with striped concurrent maps, active Gorilla chunk per series sealing at 120 samples, and an Appender that buffers samples, writes WAL on commit, then applies to head. OOO rejection checks both committed and batch state. WAL replay on Open rebuilds the full in-memory state. Exported ChunkAppender/ChunkIterator interfaces from chunkenc.
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package head
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"git.dvdt.dev/david/ingot/labels"
|
||||
)
|
||||
|
||||
const numStripes = 128
|
||||
|
||||
// seriesMap is a concurrent map of series, striped by label hash for hash
|
||||
// lookups and by ref for ref lookups.
|
||||
type seriesMap struct {
|
||||
hashStripes [numStripes]hashStripe
|
||||
refStripes [numStripes]refStripe
|
||||
}
|
||||
|
||||
type hashStripe struct {
|
||||
mu sync.RWMutex
|
||||
m map[uint64][]*memSeries // label hash -> series (slice for collision)
|
||||
}
|
||||
|
||||
type refStripe struct {
|
||||
mu sync.RWMutex
|
||||
m map[uint64]*memSeries
|
||||
}
|
||||
|
||||
func newSeriesMap() *seriesMap {
|
||||
sm := &seriesMap{}
|
||||
for i := range sm.hashStripes {
|
||||
sm.hashStripes[i].m = make(map[uint64][]*memSeries)
|
||||
}
|
||||
for i := range sm.refStripes {
|
||||
sm.refStripes[i].m = make(map[uint64]*memSeries)
|
||||
}
|
||||
return sm
|
||||
}
|
||||
|
||||
// getByRef returns the series with the given ref, or nil.
|
||||
func (sm *seriesMap) getByRef(ref uint64) *memSeries {
|
||||
s := &sm.refStripes[ref%numStripes]
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return s.m[ref]
|
||||
}
|
||||
|
||||
// getByHash returns the series with the given hash and labels, or nil.
|
||||
func (sm *seriesMap) getByHash(hash uint64, ls []labels.Label) *memSeries {
|
||||
s := &sm.hashStripes[hash%numStripes]
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
for _, series := range s.m[hash] {
|
||||
if labels.Equal(series.labels, ls) {
|
||||
return series
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// set inserts a series into both maps. Caller must ensure the ref is unique.
|
||||
func (sm *seriesMap) set(hash uint64, s *memSeries) {
|
||||
hs := &sm.hashStripes[hash%numStripes]
|
||||
hs.mu.Lock()
|
||||
hs.m[hash] = append(hs.m[hash], s)
|
||||
hs.mu.Unlock()
|
||||
|
||||
rs := &sm.refStripes[s.ref%numStripes]
|
||||
rs.mu.Lock()
|
||||
rs.m[s.ref] = s
|
||||
rs.mu.Unlock()
|
||||
}
|
||||
|
||||
// remove deletes a series from both maps.
|
||||
func (sm *seriesMap) remove(hash uint64, s *memSeries) {
|
||||
hs := &sm.hashStripes[hash%numStripes]
|
||||
hs.mu.Lock()
|
||||
bucket := hs.m[hash]
|
||||
for i, existing := range bucket {
|
||||
if existing == s {
|
||||
hs.m[hash] = append(bucket[:i], bucket[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(hs.m[hash]) == 0 {
|
||||
delete(hs.m, hash)
|
||||
}
|
||||
hs.mu.Unlock()
|
||||
|
||||
rs := &sm.refStripes[s.ref%numStripes]
|
||||
rs.mu.Lock()
|
||||
delete(rs.m, s.ref)
|
||||
rs.mu.Unlock()
|
||||
}
|
||||
Reference in New Issue
Block a user