core/rawdb: improve freezer TruncateTail

This commit is contained in:
Gary Rong 2026-03-03 11:59:42 +08:00
parent 1cd4b02579
commit 3a90107843
3 changed files with 104 additions and 4 deletions

View file

@ -320,6 +320,8 @@ func (f *Freezer) TruncateTail(tail uint64) (uint64, error) {
}
}
f.tail.Store(tail)
// Update the head if the requested tail exceeds the current head
if f.head.Load() < tail {
f.head.Store(tail)
}

View file

@ -707,12 +707,13 @@ func (t *freezerTable) truncateTail(items uint64) error {
t.lock.Lock()
defer t.lock.Unlock()
// Ensure the given truncate target falls in the correct range
// Short-circuit if the requested tail deletion points to a stale position
if t.itemHidden.Load() >= items {
return nil
}
// If the requested tail exceeds the current head, reset the entire table
if t.items.Load() < items {
return errors.New("truncation above head")
return t.resetTo(items)
}
// Load the new tail index by the given new tail position
var (
@ -835,6 +836,55 @@ func (t *freezerTable) truncateTail(items uint64) error {
return nil
}
// resetTo clears the entire table and sets both the head and tail to the given
// value. It assumes the caller holds the lock and that tail > t.items.
func (t *freezerTable) resetTo(tail uint64) error {
// Sync the entire table before resetting, eliminating the potential
// data corruption.
err := t.Sync()
if err != nil {
return err
}
// Update the index file to reflect the new offset
if err := t.index.Close(); err != nil {
return err
}
entry := &indexEntry{
filenum: t.headId + 1,
offset: uint32(tail),
}
if err := reset(t.index.Name(), entry.append(nil)); err != nil {
return err
}
if err := t.metadata.setFlushOffset(indexEntrySize, true); err != nil {
return err
}
t.index, err = openFreezerFileForAppend(t.index.Name())
if err != nil {
return err
}
// Purge all the existing data file
if err := t.head.Close(); err != nil {
return err
}
t.headId = t.headId + 1
t.tailId = t.headId
t.head, err = t.openFile(t.headId, openFreezerFileForAppend)
if err != nil {
return err
}
t.releaseFilesBefore(t.headId, true)
t.items.Store(tail)
t.itemOffset.Store(tail)
t.itemHidden.Store(tail)
t.sizeGauge.Update(0)
return nil
}
// Close closes all opened files and finalizes the freezer table for use.
// This operation must be completed before shutdown to prevent the loss of
// recent writes.

View file

@ -22,6 +22,19 @@ import (
"path/filepath"
)
func atomicRename(src, dest string) error {
if err := os.Rename(src, dest); err != nil {
return err
}
dir, err := os.Open(filepath.Dir(src))
if err != nil {
return err
}
defer dir.Close()
return dir.Sync()
}
// copyFrom copies data from 'srcPath' at offset 'offset' into 'destPath'.
// The 'destPath' is created if it doesn't exist, otherwise it is overwritten.
// Before the copy is executed, there is a callback can be registered to
@ -73,13 +86,48 @@ func copyFrom(srcPath, destPath string, offset uint64, before func(f *os.File) e
return err
}
f = nil
return os.Rename(fname, destPath)
return atomicRename(fname, destPath)
}
// reset atomically replaces the file at the given path with the provided content.
func reset(path string, content []byte) error {
// Create a temp file in the same dir where we want it to wind up
f, err := os.CreateTemp(filepath.Dir(path), "*")
if err != nil {
return err
}
fname := f.Name()
// Clean up the leftover file
defer func() {
if f != nil {
f.Close()
}
os.Remove(fname)
}()
// Write the content into the temp file
_, err = f.Write(content)
if err != nil {
return err
}
// Permanently persist the content into disk
if err := f.Sync(); err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
f = nil
return atomicRename(fname, path)
}
// openFreezerFileForAppend opens a freezer table file and seeks to the end
func openFreezerFileForAppend(filename string) (*os.File, error) {
// Open the file without the O_APPEND flag
// because it has differing behaviour during Truncate operations
// because it has differing behavior during Truncate operations
// on different OS's
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {