@ -107,8 +107,8 @@ type Head struct {
// All series addressable by their ID or hash.
series * stripeSeries
deleted Mtx sync . Mutex
deleted map [ chunks . HeadSeriesRef ] int // Deleted series , and what WAL segment they must be kept until.
walExpiries Mtx sync . Mutex
walExpiries map [ chunks . HeadSeriesRef ] int // Series no longer in the head , and what WAL segment they must be kept until.
// TODO(codesome): Extend MemPostings to return only OOOPostings, Set OOOStatus, ... Like an additional map of ooo postings.
postings * index . MemPostings // Postings lists for terms.
@ -340,7 +340,7 @@ func (h *Head) resetInMemoryState() error {
h . exemplars = es
h . postings = index . NewUnorderedMemPostings ( )
h . tombstones = tombstones . NewMemTombstones ( )
h . deleted = map [ chunks . HeadSeriesRef ] int { }
h . walExpiries = map [ chunks . HeadSeriesRef ] int { }
h . chunkRange . Store ( h . opts . ChunkRange )
h . minTime . Store ( math . MaxInt64 )
h . maxTime . Store ( math . MinInt64 )
@ -762,7 +762,7 @@ func (h *Head) Init(minValidTime int64) error {
// A corrupted checkpoint is a hard error for now and requires user
// intervention. There's likely little data that can be recovered anyway.
if err := h . loadWAL ( wlog . NewReader ( sr ) , syms , multiRef , mmappedChunks , oooMmappedChunks ) ; err != nil {
if err := h . loadWAL ( wlog . NewReader ( sr ) , syms , multiRef , mmappedChunks , oooMmappedChunks , endAt ) ; err != nil {
return fmt . Errorf ( "backfill checkpoint: %w" , err )
}
h . updateWALReplayStatusRead ( startFrom )
@ -795,7 +795,7 @@ func (h *Head) Init(minValidTime int64) error {
if err != nil {
return fmt . Errorf ( "segment reader (offset=%d): %w" , offset , err )
}
err = h . loadWAL ( wlog . NewReader ( sr ) , syms , multiRef , mmappedChunks , oooMmappedChunks )
err = h . loadWAL ( wlog . NewReader ( sr ) , syms , multiRef , mmappedChunks , oooMmappedChunks , endAt )
if err := sr . Close ( ) ; err != nil {
h . logger . Warn ( "Error while closing the wal segments reader" , "err" , err )
}
@ -1262,6 +1262,34 @@ func (h *Head) IsQuerierCollidingWithTruncation(querierMint, querierMaxt int64)
return false , false , 0
}
func ( h * Head ) getWALExpiry ( id chunks . HeadSeriesRef ) ( int , bool ) {
h . walExpiriesMtx . Lock ( )
defer h . walExpiriesMtx . Unlock ( )
keepUntil , ok := h . walExpiries [ id ]
return keepUntil , ok
}
func ( h * Head ) setWALExpiry ( id chunks . HeadSeriesRef , keepUntil int ) {
h . walExpiriesMtx . Lock ( )
defer h . walExpiriesMtx . Unlock ( )
h . walExpiries [ id ] = keepUntil
}
// keepSeriesInWALCheckpoint is used to determine whether a series record should be kept in the checkpoint
// last is the last WAL segment that was considered for checkpointing.
func ( h * Head ) keepSeriesInWALCheckpoint ( id chunks . HeadSeriesRef , last int ) bool {
// Keep the record if the series exists in the head.
if h . series . getByID ( id ) != nil {
return true
}
// Keep the record if the series has an expiry set.
keepUntil , ok := h . getWALExpiry ( id )
return ok && keepUntil > last
}
// truncateWAL removes old data before mint from the WAL.
func ( h * Head ) truncateWAL ( mint int64 ) error {
h . chunkSnapshotMtx . Lock ( )
@ -1295,17 +1323,8 @@ func (h *Head) truncateWAL(mint int64) error {
return nil
}
keep := func ( id chunks . HeadSeriesRef ) bool {
if h . series . getByID ( id ) != nil {
return true
}
h . deletedMtx . Lock ( )
keepUntil , ok := h . deleted [ id ]
h . deletedMtx . Unlock ( )
return ok && keepUntil > last
}
h . metrics . checkpointCreationTotal . Inc ( )
if _ , err = wlog . Checkpoint ( h . logger , h . wal , first , last , keep , mint ) ; err != nil {
if _ , err = wlog . Checkpoint ( h . logger , h . wal , first , last , h . keepSeriesInWALCheckpoint , mint ) ; err != nil {
h . metrics . checkpointCreationFail . Inc ( )
var cerr * chunks . CorruptionErr
if errors . As ( err , & cerr ) {
@ -1320,15 +1339,15 @@ func (h *Head) truncateWAL(mint int64) error {
h . logger . Error ( "truncating segments failed" , "err" , err )
}
// The checkpoint is written and segments before it is truncated, so we no
// longer need to track deleted series that are before it .
h . deleted Mtx. Lock ( )
for ref , segment := range h . deleted {
// The checkpoint is written and segments before it is truncated, so stop
// tracking expired series .
h . walExpiries Mtx. Lock ( )
for ref , segment := range h . walExpiries {
if segment <= last {
delete ( h . deleted , ref )
delete ( h . walExpiries , ref )
}
}
h . deleted Mtx. Unlock ( )
h . walExpiries Mtx. Unlock ( )
h . metrics . checkpointDeleteTotal . Inc ( )
if err := wlog . DeleteCheckpoints ( h . wal . Dir ( ) , last ) ; err != nil {
@ -1595,7 +1614,7 @@ func (h *Head) gc() (actualInOrderMint, minOOOTime int64, minMmapFile int) {
if h . wal != nil {
_ , last , _ := wlog . Segments ( h . wal . Dir ( ) )
h . deleted Mtx. Lock ( )
h . walExpiries Mtx. Lock ( )
// Keep series records until we're past segment 'last'
// because the WAL will still have samples records with
// this ref ID. If we didn't keep these series records then
@ -1603,9 +1622,9 @@ func (h *Head) gc() (actualInOrderMint, minOOOTime int64, minMmapFile int) {
// that reads the WAL, wouldn't be able to use those
// samples since we would have no labels for that ref ID.
for ref := range deleted {
h . deleted [ chunks . HeadSeriesRef ( ref ) ] = last
h . walExpiries [ chunks . HeadSeriesRef ( ref ) ] = last
}
h . deleted Mtx. Unlock ( )
h . walExpiries Mtx. Unlock ( )
}
return actualInOrderMint , minOOOTime , minMmapFile