WriteDatFileAndVacuum

This commit is contained in:
chrislu
2025-08-13 00:28:12 -07:00
parent 1f8ba5c958
commit 117bba794c
2 changed files with 133 additions and 16 deletions

View File

@@ -212,3 +212,124 @@ func min(x, y int64) int64 {
}
return x
}
// WriteDatFileAndVacuum reconstructs volume from EC shards and then vacuums deleted needles
// This reuses existing WriteDatFile and volume compaction logic to achieve the same result more cleanly
func WriteDatFileAndVacuum(baseFileName string, shardFileNames []string) error {
// Step 1: Use existing WriteDatFile to reconstruct the full volume
datFileSize, err := FindDatFileSize(baseFileName, baseFileName)
if err != nil {
return fmt.Errorf("failed to find dat file size: %w", err)
}
tempDatFile := baseFileName + ".tmp.dat"
err = WriteDatFile(tempDatFile, datFileSize, shardFileNames)
if err != nil {
return fmt.Errorf("failed to reconstruct volume with WriteDatFile: %w", err)
}
defer os.Remove(tempDatFile) // cleanup temp file
// Step 2: Create index file with deleted entries marked (existing function)
tempIdxFile := baseFileName + ".tmp.idx"
err = WriteIdxFileFromEcIndex(baseFileName + ".tmp")
if err != nil {
return fmt.Errorf("failed to create index file: %w", err)
}
defer os.Remove(tempIdxFile) // cleanup temp file
// Step 3: Use existing volume compaction logic to filter out deleted needles
version, err := readEcVolumeVersion(baseFileName)
if err != nil {
return fmt.Errorf("failed to read volume version: %w", err)
}
return copyDataBasedOnIndexFileForEcVacuum(
tempDatFile, tempIdxFile, // source files (with deleted entries)
baseFileName+".dat", baseFileName+".idx", // destination files (cleaned)
version,
)
}
// copyDataBasedOnIndexFileForEcVacuum copies only non-deleted needles from source to destination
// This is a simplified version of volume_vacuum.go's copyDataBasedOnIndexFile for EC vacuum use
func copyDataBasedOnIndexFileForEcVacuum(srcDatName, srcIdxName, dstDatName, dstIdxName string, version needle.Version) error {
// Open source data file
dataFile, err := os.Open(srcDatName)
if err != nil {
return fmt.Errorf("failed to open source dat file: %w", err)
}
srcDatBackend := backend.NewDiskFile(dataFile)
defer srcDatBackend.Close()
// Create destination data file
dstDatBackend, err := backend.CreateVolumeFile(dstDatName, 0, 0)
if err != nil {
return fmt.Errorf("failed to create destination dat file: %w", err)
}
defer func() {
dstDatBackend.Sync()
dstDatBackend.Close()
}()
// Load needle map from source index
oldNm := needle_map.NewMemDb()
defer oldNm.Close()
if err := oldNm.LoadFromIdx(srcIdxName); err != nil {
return fmt.Errorf("failed to load index file: %w", err)
}
// Create new needle map for cleaned volume
newNm := needle_map.NewMemDb()
defer newNm.Close()
// Copy superblock with incremented compaction revision
sb := super_block.SuperBlock{}
if existingSb, err := super_block.ReadSuperBlock(srcDatBackend); err == nil {
sb = existingSb
sb.CompactionRevision++
} else {
// Use default superblock if reading fails
sb = super_block.SuperBlock{
Version: version,
ReplicaPlacement: &super_block.ReplicaPlacement{},
CompactionRevision: 1,
}
}
dstDatBackend.WriteAt(sb.Bytes(), 0)
newOffset := int64(sb.BlockSize())
// Copy only non-deleted needles
err = oldNm.AscendingVisit(func(value needle_map.NeedleValue) error {
offset, size := value.Offset, value.Size
// Skip deleted needles (this is the key filtering logic!)
if offset.IsZero() || size.IsDeleted() {
return nil
}
// Read needle from source
n := new(needle.Needle)
if err := n.ReadData(srcDatBackend, offset.ToActualOffset(), size, version); err != nil {
return fmt.Errorf("cannot read needle from source: %w", err)
}
// Write needle to destination
if err := newNm.Set(n.Id, types.ToOffset(newOffset), n.Size); err != nil {
return fmt.Errorf("cannot set needle in new map: %w", err)
}
if _, _, _, err := n.Append(dstDatBackend, sb.Version); err != nil {
return fmt.Errorf("cannot append needle to destination: %w", err)
}
newOffset += n.DiskSize(version)
return nil
})
if err != nil {
return fmt.Errorf("failed to copy needles: %w", err)
}
// Save the new index file
return newNm.SaveToIdx(dstIdxName)
}

View File

@@ -39,8 +39,8 @@ type EcVacuumTask struct {
sourceNodes map[pb.ServerAddress]erasure_coding.ShardBits
tempDir string
grpcDialOption grpc.DialOption
adminAddress string // admin server address for API calls
masterAddress pb.ServerAddress // master server address for activation RPC
adminAddress string // admin server address for API calls
cleanupGracePeriod time.Duration // grace period before cleaning up old generation (1 minute default)
topologyTaskID string // links to ActiveTopology task for capacity tracking
@@ -146,7 +146,7 @@ func (t *EcVacuumTask) Execute(ctx context.Context, params *worker_pb.TaskParams
return fmt.Errorf("failed to collect EC shards: %w", err)
}
// Step 3: Decode EC shards into normal volume on worker (skips deleted entries automatically)
// Step 3: Decode EC shards into normal volume on worker (properly filters out deleted entries using merged .ecj)
if err := t.decodeEcShardsToVolume(); err != nil {
return fmt.Errorf("failed to decode EC shards to volume: %w", err)
}
@@ -356,7 +356,7 @@ func (t *EcVacuumTask) copyFileFromVolumeServer(client volume_server_pb.VolumeSe
return nil
}
// decodeEcShardsToVolume decodes EC shards into a normal volume on worker, automatically skipping deleted entries
// decodeEcShardsToVolume decodes EC shards into a normal volume on worker, properly filtering deleted entries using merged .ecj file
func (t *EcVacuumTask) decodeEcShardsToVolume() error {
t.LogInfo("Decoding EC shards to normal volume on worker", map[string]interface{}{
"volume_id": t.volumeID,
@@ -397,22 +397,18 @@ func (t *EcVacuumTask) decodeEcShardsToVolume() error {
return fmt.Errorf("failed to find dat file size: %w", err)
}
// Step 5: Write .dat file from EC data shards (this automatically skips deleted entries)
err = erasure_coding.WriteDatFile(baseFileName, datFileSize, shardFileNames)
// Step 5: Reconstruct and vacuum volume data (reuses existing functions + compaction logic)
err = erasure_coding.WriteDatFileAndVacuum(baseFileName, shardFileNames)
if err != nil {
return fmt.Errorf("failed to write dat file: %w", err)
return fmt.Errorf("failed to reconstruct and vacuum volume: %w", err)
}
// Step 6: Write .idx file from .ecx and merged .ecj files (skips deleted entries)
err = erasure_coding.WriteIdxFileFromEcIndex(baseFileName)
if err != nil {
return fmt.Errorf("failed to write idx file from ec index: %w", err)
}
t.LogInfo("Successfully decoded EC shards to normal volume", map[string]interface{}{
"dat_file": datFileName,
"idx_file": idxFileName,
"dat_size": datFileSize,
t.LogInfo("Successfully decoded EC shards to filtered normal volume", map[string]interface{}{
"dat_file": datFileName,
"idx_file": idxFileName,
"original_dat_size": datFileSize,
"deleted_entries_filtered": true,
"note": "deleted needles physically removed from volume",
})
return nil