Flush sealed head chunks to ULID-named block directories on disk. Each block contains CRC'd chunk segment files (mmap'd for reads), a binary index (symbol table, series, postings with TOC), and a meta.json written last as the immutability gate. WAL is truncated after block fsync, preserving the crash-safety ording invariant.
106 lines
2.3 KiB
Go
106 lines
2.3 KiB
Go
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()
|
|
}
|
|
|
|
// forEach calls fn for every series in the map. The series lock is NOT held.
|
|
func (sm *seriesMap) forEach(fn func(s *memSeries)) {
|
|
for i := range sm.refStripes {
|
|
rs := &sm.refStripes[i]
|
|
rs.mu.RLock()
|
|
for _, s := range rs.m {
|
|
fn(s)
|
|
}
|
|
rs.mu.RUnlock()
|
|
}
|
|
}
|
|
|
|
// 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()
|
|
}
|