core/rawdb: implement size reporting for live items in freezer_table (#28525)

This is the fix to issue #27483. A new hiddenBytes() is introduced to calculate the byte size of hidden items in the freezer table. When reporting the size of the freezer table, size of the hidden items will be subtracted from the total size.

---------

Co-authored-by: Yifan <Yifan Wang>
Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
wangyifan 2023-12-18 11:10:54 -08:00 committed by GitHub
parent 54a400ee71
commit cd58897f18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 62 additions and 10 deletions

View File

@ -467,6 +467,20 @@ func (t *freezerTable) truncateHead(items uint64) error {
return nil return nil
} }
// sizeHidden returns the total data size of hidden items in the freezer table.
// This function assumes the lock is already held.
func (t *freezerTable) sizeHidden() (uint64, error) {
hidden, offset := t.itemHidden.Load(), t.itemOffset.Load()
if hidden <= offset {
return 0, nil
}
indices, err := t.getIndices(hidden-1, 1)
if err != nil {
return 0, err
}
return uint64(indices[1].offset), nil
}
// truncateTail discards any recent data before the provided threshold number. // truncateTail discards any recent data before the provided threshold number.
func (t *freezerTable) truncateTail(items uint64) error { func (t *freezerTable) truncateTail(items uint64) error {
t.lock.Lock() t.lock.Lock()
@ -495,6 +509,12 @@ func (t *freezerTable) truncateTail(items uint64) error {
newTail.unmarshalBinary(buffer) newTail.unmarshalBinary(buffer)
newTailId = newTail.filenum newTailId = newTail.filenum
} }
// Save the old size for metrics tracking. This needs to be done
// before any updates to either itemHidden or itemOffset.
oldSize, err := t.sizeNolock()
if err != nil {
return err
}
// Update the virtual tail marker and hidden these entries in table. // Update the virtual tail marker and hidden these entries in table.
t.itemHidden.Store(items) t.itemHidden.Store(items)
if err := writeMetadata(t.meta, newMetadata(items)); err != nil { if err := writeMetadata(t.meta, newMetadata(items)); err != nil {
@ -509,18 +529,12 @@ func (t *freezerTable) truncateTail(items uint64) error {
if t.tailId > newTailId { if t.tailId > newTailId {
return fmt.Errorf("invalid index, tail-file %d, item-file %d", t.tailId, newTailId) return fmt.Errorf("invalid index, tail-file %d, item-file %d", t.tailId, newTailId)
} }
// Hidden items exceed the current tail file, drop the relevant
// data files. We need to truncate, save the old size for metrics
// tracking.
oldSize, err := t.sizeNolock()
if err != nil {
return err
}
// Count how many items can be deleted from the file. // Count how many items can be deleted from the file.
var ( var (
newDeleted = items newDeleted = items
deleted = t.itemOffset.Load() deleted = t.itemOffset.Load()
) )
// Hidden items exceed the current tail file, drop the relevant data files.
for current := items - 1; current >= deleted; current -= 1 { for current := items - 1; current >= deleted; current -= 1 {
if _, err := t.index.ReadAt(buffer, int64((current-deleted+1)*indexEntrySize)); err != nil { if _, err := t.index.ReadAt(buffer, int64((current-deleted+1)*indexEntrySize)); err != nil {
return err return err
@ -680,6 +694,7 @@ func (t *freezerTable) releaseFilesBefore(num uint32, remove bool) {
func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) { func (t *freezerTable) getIndices(from, count uint64) ([]*indexEntry, error) {
// Apply the table-offset // Apply the table-offset
from = from - t.itemOffset.Load() from = from - t.itemOffset.Load()
// For reading N items, we need N+1 indices. // For reading N items, we need N+1 indices.
buffer := make([]byte, (count+1)*indexEntrySize) buffer := make([]byte, (count+1)*indexEntrySize)
if _, err := t.index.ReadAt(buffer, int64(from*indexEntrySize)); err != nil { if _, err := t.index.ReadAt(buffer, int64(from*indexEntrySize)); err != nil {
@ -870,14 +885,18 @@ func (t *freezerTable) size() (uint64, error) {
return t.sizeNolock() return t.sizeNolock()
} }
// sizeNolock returns the total data size in the freezer table without obtaining // sizeNolock returns the total data size in the freezer table. This function
// the mutex first. // assumes the lock is already held.
func (t *freezerTable) sizeNolock() (uint64, error) { func (t *freezerTable) sizeNolock() (uint64, error) {
stat, err := t.index.Stat() stat, err := t.index.Stat()
if err != nil { if err != nil {
return 0, err return 0, err
} }
total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size()) hidden, err := t.sizeHidden()
if err != nil {
return 0, err
}
total := uint64(t.maxFileSize)*uint64(t.headId-t.tailId) + uint64(t.headBytes) + uint64(stat.Size()) - hidden
return total, nil return total, nil
} }

View File

@ -658,6 +658,13 @@ func TestFreezerOffset(t *testing.T) {
} }
} }
func assertTableSize(t *testing.T, f *freezerTable, size int) {
t.Helper()
if got, err := f.size(); got != uint64(size) {
t.Fatalf("expected size of %d bytes, got %d, err: %v", size, got, err)
}
}
func TestTruncateTail(t *testing.T) { func TestTruncateTail(t *testing.T) {
t.Parallel() t.Parallel()
rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge() rm, wm, sg := metrics.NewMeter(), metrics.NewMeter(), metrics.NewGauge()
@ -692,6 +699,9 @@ func TestTruncateTail(t *testing.T) {
5: getChunk(20, 0xaa), 5: getChunk(20, 0xaa),
6: getChunk(20, 0x11), 6: getChunk(20, 0x11),
}) })
// maxFileSize*fileCount + headBytes + indexFileSize - hiddenBytes
expected := 20*7 + 48 - 0
assertTableSize(t, f, expected)
// truncate single element( item 0 ), deletion is only supported at file level // truncate single element( item 0 ), deletion is only supported at file level
f.truncateTail(1) f.truncateTail(1)
@ -707,6 +717,8 @@ func TestTruncateTail(t *testing.T) {
5: getChunk(20, 0xaa), 5: getChunk(20, 0xaa),
6: getChunk(20, 0x11), 6: getChunk(20, 0x11),
}) })
expected = 20*7 + 48 - 20
assertTableSize(t, f, expected)
// Reopen the table, the deletion information should be persisted as well // Reopen the table, the deletion information should be persisted as well
f.Close() f.Close()
@ -739,6 +751,8 @@ func TestTruncateTail(t *testing.T) {
5: getChunk(20, 0xaa), 5: getChunk(20, 0xaa),
6: getChunk(20, 0x11), 6: getChunk(20, 0x11),
}) })
expected = 20*5 + 36 - 0
assertTableSize(t, f, expected)
// Reopen the table, the above testing should still pass // Reopen the table, the above testing should still pass
f.Close() f.Close()
@ -760,6 +774,23 @@ func TestTruncateTail(t *testing.T) {
6: getChunk(20, 0x11), 6: getChunk(20, 0x11),
}) })
// truncate 3 more elements( item 2, 3, 4), the file 1 should be deleted
// file 2 should only contain item 5
f.truncateTail(5)
checkRetrieveError(t, f, map[uint64]error{
0: errOutOfBounds,
1: errOutOfBounds,
2: errOutOfBounds,
3: errOutOfBounds,
4: errOutOfBounds,
})
checkRetrieve(t, f, map[uint64][]byte{
5: getChunk(20, 0xaa),
6: getChunk(20, 0x11),
})
expected = 20*3 + 24 - 20
assertTableSize(t, f, expected)
// truncate all, the entire freezer should be deleted // truncate all, the entire freezer should be deleted
f.truncateTail(7) f.truncateTail(7)
checkRetrieveError(t, f, map[uint64]error{ checkRetrieveError(t, f, map[uint64]error{
@ -771,6 +802,8 @@ func TestTruncateTail(t *testing.T) {
5: errOutOfBounds, 5: errOutOfBounds,
6: errOutOfBounds, 6: errOutOfBounds,
}) })
expected = 12
assertTableSize(t, f, expected)
} }
func TestTruncateHead(t *testing.T) { func TestTruncateHead(t *testing.T) {