mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-08 15:27:43 +02:00
The maintenance operations built manifest, manifest-list, data-file and
metadata-log paths with a bare path.Join("metadata", ...), so a single
maintenance run wrote scheme-less relative paths into the new snapshot,
the metadata-log and the table xattr. SeaweedFS reads those back fine
because normalizeIcebergPath accepts both forms, but strict readers
resolve every location through S3FileIO and fail with "Invalid S3 URI,
cannot determine scheme", leaving the whole table unreadable after any
maintenance commit.
Add absoluteIcebergPath, the inverse of normalizeIcebergPath, and apply
it at every site that authors locations: rewrite_manifests, compact,
rewrite_position_delete_files, and the shared commit path. The base is
derived from the bucket and table path since that is where the worker
physically writes, matching the locations the REST catalog generates.
596 lines
18 KiB
Go
596 lines
18 KiB
Go
package iceberg
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"path"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/apache/iceberg-go"
|
|
"github.com/apache/iceberg-go/table"
|
|
"github.com/parquet-go/parquet-go"
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
|
|
)
|
|
|
|
type deleteRewriteInput struct {
|
|
Entry iceberg.ManifestEntry
|
|
ReferencedPath string
|
|
Positions []int64
|
|
}
|
|
|
|
type deleteRewriteGroup struct {
|
|
SpecID int32
|
|
Partition map[int]any
|
|
PartitionKey string
|
|
ReferencedPath string
|
|
Inputs []deleteRewriteInput
|
|
TotalSize int64
|
|
}
|
|
|
|
type positionDeleteRow struct {
|
|
FilePath string `parquet:"file_path"`
|
|
Pos int64 `parquet:"pos"`
|
|
}
|
|
|
|
func hasEligibleDeleteRewrite(
|
|
ctx context.Context,
|
|
filerClient filer_pb.SeaweedFilerClient,
|
|
bucketName, tablePath string,
|
|
manifests []iceberg.ManifestFile,
|
|
config Config,
|
|
meta table.Metadata,
|
|
predicate *partitionPredicate,
|
|
) (bool, error) {
|
|
groups, _, err := collectDeleteRewriteGroups(ctx, filerClient, bucketName, tablePath, manifests)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, group := range groups {
|
|
if predicate != nil {
|
|
spec, ok := specByID(meta)[int(group.SpecID)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
match, err := predicate.Matches(spec, group.Partition)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !match {
|
|
continue
|
|
}
|
|
}
|
|
if groupEligibleForRewrite(group, config) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func collectDeleteRewriteGroups(
|
|
ctx context.Context,
|
|
filerClient filer_pb.SeaweedFilerClient,
|
|
bucketName, tablePath string,
|
|
manifests []iceberg.ManifestFile,
|
|
) (map[string]*deleteRewriteGroup, []iceberg.ManifestEntry, error) {
|
|
groups := make(map[string]*deleteRewriteGroup)
|
|
var allPositionEntries []iceberg.ManifestEntry
|
|
|
|
for _, mf := range manifests {
|
|
if mf.ManifestContent() != iceberg.ManifestContentDeletes {
|
|
continue
|
|
}
|
|
|
|
manifestData, err := loadFileByIcebergPath(ctx, filerClient, bucketName, tablePath, mf.FilePath())
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("read delete manifest %s: %w", mf.FilePath(), err)
|
|
}
|
|
entries, err := iceberg.ReadManifest(mf, bytes.NewReader(manifestData), true)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("parse delete manifest %s: %w", mf.FilePath(), err)
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.DataFile().ContentType() != iceberg.EntryContentPosDeletes {
|
|
continue
|
|
}
|
|
|
|
allPositionEntries = append(allPositionEntries, entry)
|
|
|
|
fileDeletes, err := readPositionDeleteFile(ctx, filerClient, bucketName, tablePath, entry.DataFile().FilePath())
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("read position delete file %s: %w", entry.DataFile().FilePath(), err)
|
|
}
|
|
if len(fileDeletes) != 1 {
|
|
// Phase 1 only rewrites files that target a single data file.
|
|
continue
|
|
}
|
|
|
|
var referencedPath string
|
|
var positions []int64
|
|
for fp, pos := range fileDeletes {
|
|
referencedPath = normalizeIcebergPath(fp, bucketName, tablePath)
|
|
positions = append(positions, pos...)
|
|
}
|
|
sort.Slice(positions, func(i, j int) bool { return positions[i] < positions[j] })
|
|
|
|
partKey := partitionKey(entry.DataFile().Partition())
|
|
groupKey := fmt.Sprintf("spec%d\x00%s\x00%s", entry.DataFile().SpecID(), partKey, referencedPath)
|
|
group, ok := groups[groupKey]
|
|
if !ok {
|
|
group = &deleteRewriteGroup{
|
|
SpecID: entry.DataFile().SpecID(),
|
|
Partition: entry.DataFile().Partition(),
|
|
PartitionKey: partKey,
|
|
ReferencedPath: referencedPath,
|
|
}
|
|
groups[groupKey] = group
|
|
}
|
|
group.Inputs = append(group.Inputs, deleteRewriteInput{
|
|
Entry: entry,
|
|
ReferencedPath: referencedPath,
|
|
Positions: positions,
|
|
})
|
|
group.TotalSize += entry.DataFile().FileSizeBytes()
|
|
}
|
|
}
|
|
|
|
return groups, allPositionEntries, nil
|
|
}
|
|
|
|
func groupEligibleForRewrite(group *deleteRewriteGroup, config Config) bool {
|
|
if group == nil {
|
|
return false
|
|
}
|
|
if len(group.Inputs) < 2 {
|
|
return false
|
|
}
|
|
if group.TotalSize > config.DeleteMaxFileGroupSizeBytes {
|
|
return false
|
|
}
|
|
target := config.DeleteTargetFileSizeBytes
|
|
if target <= 0 {
|
|
target = defaultDeleteTargetFileSizeMB * 1024 * 1024
|
|
}
|
|
outputFiles := int64(estimatedDeleteOutputFiles(group.TotalSize, target))
|
|
if config.DeleteMaxOutputFiles > 0 && outputFiles > config.DeleteMaxOutputFiles {
|
|
return false
|
|
}
|
|
return int64(len(group.Inputs)) >= config.DeleteMinInputFiles
|
|
}
|
|
|
|
func estimatedDeleteOutputFiles(totalSize, targetSize int64) int {
|
|
if totalSize <= 0 || targetSize <= 0 {
|
|
return 1
|
|
}
|
|
count := int(math.Ceil(float64(totalSize) / float64(targetSize)))
|
|
if count < 1 {
|
|
return 1
|
|
}
|
|
return count
|
|
}
|
|
|
|
func manifestEntrySeqNum(entry iceberg.ManifestEntry) *int64 {
|
|
seqNum := entry.SequenceNum()
|
|
if seqNum < 0 {
|
|
return nil
|
|
}
|
|
return &seqNum
|
|
}
|
|
|
|
func manifestEntryFileSeqNum(entry iceberg.ManifestEntry) *int64 {
|
|
if fileSeqNum := entry.FileSequenceNum(); fileSeqNum != nil {
|
|
value := *fileSeqNum
|
|
return &value
|
|
}
|
|
return manifestEntrySeqNum(entry)
|
|
}
|
|
|
|
func writeManifestWithContent(
|
|
filename string,
|
|
version int,
|
|
spec iceberg.PartitionSpec,
|
|
schema *iceberg.Schema,
|
|
snapshotID int64,
|
|
entries []iceberg.ManifestEntry,
|
|
content iceberg.ManifestContent,
|
|
) (iceberg.ManifestFile, []byte, error) {
|
|
var manifestBuf bytes.Buffer
|
|
mf, err := iceberg.WriteManifest(filename, &manifestBuf, version, spec, schema, snapshotID, entries)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
manifestBytes := manifestBuf.Bytes()
|
|
if content == iceberg.ManifestContentDeletes {
|
|
manifestBytes, err = patchManifestContentBytesToDeletes(manifestBytes)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
rebuilt := iceberg.NewManifestFile(version, filename, int64(len(manifestBytes)), int32(spec.ID()), snapshotID).
|
|
Content(content).
|
|
AddedFiles(mf.AddedDataFiles()).
|
|
ExistingFiles(mf.ExistingDataFiles()).
|
|
DeletedFiles(mf.DeletedDataFiles()).
|
|
AddedRows(mf.AddedRows()).
|
|
ExistingRows(mf.ExistingRows()).
|
|
DeletedRows(mf.DeletedRows()).
|
|
Partitions(mf.Partitions()).
|
|
Build()
|
|
return rebuilt, manifestBytes, nil
|
|
}
|
|
|
|
func patchManifestContentBytesToDeletes(manifestBytes []byte) ([]byte, error) {
|
|
old := append([]byte{0x0e}, []byte("content")...)
|
|
old = append(old, 0x08)
|
|
old = append(old, []byte("data")...)
|
|
|
|
new := append([]byte{0x0e}, []byte("content")...)
|
|
new = append(new, 0x0e)
|
|
new = append(new, []byte("deletes")...)
|
|
|
|
result := bytes.Replace(manifestBytes, old, new, 1)
|
|
if bytes.Equal(result, manifestBytes) {
|
|
return nil, fmt.Errorf("delete manifest content patch failed")
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func writePositionDeleteFile(rows []positionDeleteRow) ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
writer := parquet.NewWriter(&buf, parquet.SchemaOf(new(positionDeleteRow)))
|
|
for _, row := range rows {
|
|
if err := writer.Write(&row); err != nil {
|
|
return nil, fmt.Errorf("write position delete row: %w", err)
|
|
}
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
return nil, fmt.Errorf("close position delete file: %w", err)
|
|
}
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
func (h *Handler) rewritePositionDeleteFiles(
|
|
ctx context.Context,
|
|
filerClient filer_pb.SeaweedFilerClient,
|
|
bucketName, tablePath string,
|
|
config Config,
|
|
) (string, map[string]int64, error) {
|
|
start := time.Now()
|
|
meta, metadataFileName, err := loadCurrentMetadata(ctx, filerClient, bucketName, tablePath)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("load metadata: %w", err)
|
|
}
|
|
|
|
currentSnap := meta.CurrentSnapshot()
|
|
if currentSnap == nil || currentSnap.ManifestList == "" {
|
|
return "no current snapshot", nil, nil
|
|
}
|
|
|
|
manifestListData, err := loadFileByIcebergPath(ctx, filerClient, bucketName, tablePath, currentSnap.ManifestList)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("read manifest list: %w", err)
|
|
}
|
|
manifests, err := iceberg.ReadManifestList(bytes.NewReader(manifestListData))
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("parse manifest list: %w", err)
|
|
}
|
|
|
|
var dataManifests []iceberg.ManifestFile
|
|
var allEqualityEntries []iceberg.ManifestEntry
|
|
for _, mf := range manifests {
|
|
switch mf.ManifestContent() {
|
|
case iceberg.ManifestContentData:
|
|
dataManifests = append(dataManifests, mf)
|
|
case iceberg.ManifestContentDeletes:
|
|
manifestData, readErr := loadFileByIcebergPath(ctx, filerClient, bucketName, tablePath, mf.FilePath())
|
|
if readErr != nil {
|
|
return "", nil, fmt.Errorf("read delete manifest %s: %w", mf.FilePath(), readErr)
|
|
}
|
|
entries, parseErr := iceberg.ReadManifest(mf, bytes.NewReader(manifestData), true)
|
|
if parseErr != nil {
|
|
return "", nil, fmt.Errorf("parse delete manifest %s: %w", mf.FilePath(), parseErr)
|
|
}
|
|
for _, entry := range entries {
|
|
if entry.DataFile().ContentType() == iceberg.EntryContentEqDeletes {
|
|
allEqualityEntries = append(allEqualityEntries, entry)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
groupMap, allPositionEntries, err := collectDeleteRewriteGroups(ctx, filerClient, bucketName, tablePath, manifests)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
if len(groupMap) == 0 {
|
|
return "no position delete files eligible for rewrite", nil, nil
|
|
}
|
|
|
|
type artifact struct {
|
|
dir, fileName string
|
|
}
|
|
var writtenArtifacts []artifact
|
|
committed := false
|
|
defer func() {
|
|
if committed || len(writtenArtifacts) == 0 {
|
|
return
|
|
}
|
|
cleanupCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
for _, a := range writtenArtifacts {
|
|
if err := deleteFilerFile(cleanupCtx, filerClient, a.dir, a.fileName); err != nil {
|
|
glog.Warningf("iceberg delete rewrite: failed to clean up artifact %s/%s: %v", a.dir, a.fileName, err)
|
|
}
|
|
}
|
|
}()
|
|
|
|
specByID := specByID(meta)
|
|
predicate, err := parsePartitionPredicate(config.Where, meta)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
|
|
type specEntries struct {
|
|
specID int32
|
|
entries []iceberg.ManifestEntry
|
|
}
|
|
specEntriesMap := make(map[int32]*specEntries)
|
|
addToSpec := func(specID int32, entry iceberg.ManifestEntry) {
|
|
se, ok := specEntriesMap[specID]
|
|
if !ok {
|
|
se = &specEntries{specID: specID}
|
|
specEntriesMap[specID] = se
|
|
}
|
|
se.entries = append(se.entries, entry)
|
|
}
|
|
|
|
newSnapID := time.Now().UnixMilli()
|
|
version := meta.Version()
|
|
snapshotID := currentSnap.SnapshotID
|
|
seqNum := currentSnap.SequenceNumber + 1
|
|
metaDir := path.Join(s3tables.TablesPath, bucketName, tablePath, "metadata")
|
|
dataDir := path.Join(s3tables.TablesPath, bucketName, tablePath, "data")
|
|
artifactSuffix := compactRandomSuffix()
|
|
|
|
replacedPaths := make(map[string]struct{})
|
|
var rewrittenGroups int64
|
|
var skippedGroups int64
|
|
var deleteFilesRewritten int64
|
|
var deleteFilesWritten int64
|
|
var deleteBytesRewritten int64
|
|
|
|
sortedKeys := make([]string, 0, len(groupMap))
|
|
for key := range groupMap {
|
|
sortedKeys = append(sortedKeys, key)
|
|
}
|
|
sort.Strings(sortedKeys)
|
|
|
|
for _, key := range sortedKeys {
|
|
group := groupMap[key]
|
|
if predicate != nil {
|
|
spec, ok := specByID[int(group.SpecID)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
match, err := predicate.Matches(spec, group.Partition)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
if !match {
|
|
skippedGroups++
|
|
continue
|
|
}
|
|
}
|
|
if !groupEligibleForRewrite(group, config) {
|
|
skippedGroups++
|
|
continue
|
|
}
|
|
rows := make([]positionDeleteRow, 0)
|
|
for _, input := range group.Inputs {
|
|
for _, pos := range input.Positions {
|
|
rows = append(rows, positionDeleteRow{FilePath: input.ReferencedPath, Pos: pos})
|
|
}
|
|
replacedPaths[input.Entry.DataFile().FilePath()] = struct{}{}
|
|
deleteFilesRewritten++
|
|
deleteBytesRewritten += input.Entry.DataFile().FileSizeBytes()
|
|
}
|
|
sort.Slice(rows, func(i, j int) bool {
|
|
if rows[i].FilePath != rows[j].FilePath {
|
|
return rows[i].FilePath < rows[j].FilePath
|
|
}
|
|
return rows[i].Pos < rows[j].Pos
|
|
})
|
|
|
|
outputFiles := estimatedDeleteOutputFiles(group.TotalSize, config.DeleteTargetFileSizeBytes)
|
|
rowsPerFile := (len(rows) + outputFiles - 1) / outputFiles
|
|
if rowsPerFile < 1 {
|
|
rowsPerFile = len(rows)
|
|
}
|
|
|
|
for startIdx, fileIdx := 0, 0; startIdx < len(rows); startIdx, fileIdx = startIdx+rowsPerFile, fileIdx+1 {
|
|
endIdx := startIdx + rowsPerFile
|
|
if endIdx > len(rows) {
|
|
endIdx = len(rows)
|
|
}
|
|
outputRows := rows[startIdx:endIdx]
|
|
deleteBytes, err := writePositionDeleteFile(outputRows)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
fileName := fmt.Sprintf("rewrite-delete-%d-%s-%d.parquet", newSnapID, artifactSuffix, deleteFilesWritten)
|
|
if err := ensureFilerDir(ctx, filerClient, dataDir); err != nil {
|
|
return "", nil, fmt.Errorf("ensure data dir: %w", err)
|
|
}
|
|
if err := saveFilerFile(ctx, filerClient, dataDir, fileName, deleteBytes); err != nil {
|
|
return "", nil, fmt.Errorf("save rewritten delete file: %w", err)
|
|
}
|
|
writtenArtifacts = append(writtenArtifacts, artifact{dir: dataDir, fileName: fileName})
|
|
|
|
spec, ok := specByID[int(group.SpecID)]
|
|
if !ok {
|
|
return "", nil, fmt.Errorf("partition spec %d not found", group.SpecID)
|
|
}
|
|
dfBuilder, err := iceberg.NewDataFileBuilder(
|
|
spec,
|
|
iceberg.EntryContentPosDeletes,
|
|
absoluteIcebergPath(bucketName, tablePath, "data", fileName),
|
|
iceberg.ParquetFile,
|
|
group.Partition,
|
|
nil, nil,
|
|
int64(len(outputRows)),
|
|
int64(len(deleteBytes)),
|
|
)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("build rewritten delete file: %w", err)
|
|
}
|
|
entry := iceberg.NewManifestEntry(iceberg.EntryStatusADDED, &newSnapID, nil, nil, dfBuilder.Build())
|
|
addToSpec(group.SpecID, entry)
|
|
deleteFilesWritten++
|
|
}
|
|
|
|
for _, input := range group.Inputs {
|
|
delEntry := iceberg.NewManifestEntry(
|
|
iceberg.EntryStatusDELETED,
|
|
&newSnapID,
|
|
manifestEntrySeqNum(input.Entry),
|
|
manifestEntryFileSeqNum(input.Entry),
|
|
input.Entry.DataFile(),
|
|
)
|
|
addToSpec(group.SpecID, delEntry)
|
|
}
|
|
rewrittenGroups++
|
|
}
|
|
|
|
if rewrittenGroups == 0 {
|
|
return "no position delete files eligible for rewrite", nil, nil
|
|
}
|
|
|
|
for _, entry := range allEqualityEntries {
|
|
existingEntry := iceberg.NewManifestEntry(
|
|
iceberg.EntryStatusEXISTING,
|
|
func() *int64 { id := entry.SnapshotID(); return &id }(),
|
|
manifestEntrySeqNum(entry),
|
|
manifestEntryFileSeqNum(entry),
|
|
entry.DataFile(),
|
|
)
|
|
addToSpec(entry.DataFile().SpecID(), existingEntry)
|
|
}
|
|
|
|
for _, entry := range allPositionEntries {
|
|
if _, replaced := replacedPaths[entry.DataFile().FilePath()]; replaced {
|
|
continue
|
|
}
|
|
existingEntry := iceberg.NewManifestEntry(
|
|
iceberg.EntryStatusEXISTING,
|
|
func() *int64 { id := entry.SnapshotID(); return &id }(),
|
|
manifestEntrySeqNum(entry),
|
|
manifestEntryFileSeqNum(entry),
|
|
entry.DataFile(),
|
|
)
|
|
addToSpec(entry.DataFile().SpecID(), existingEntry)
|
|
}
|
|
|
|
sortedSpecIDs := make([]int32, 0, len(specEntriesMap))
|
|
for specID := range specEntriesMap {
|
|
sortedSpecIDs = append(sortedSpecIDs, specID)
|
|
}
|
|
sort.Slice(sortedSpecIDs, func(i, j int) bool { return sortedSpecIDs[i] < sortedSpecIDs[j] })
|
|
|
|
allManifests := make([]iceberg.ManifestFile, 0, len(dataManifests)+len(sortedSpecIDs))
|
|
allManifests = append(allManifests, dataManifests...)
|
|
|
|
for _, specID := range sortedSpecIDs {
|
|
spec, ok := specByID[int(specID)]
|
|
if !ok {
|
|
return "", nil, fmt.Errorf("partition spec %d not found", specID)
|
|
}
|
|
manifestName := fmt.Sprintf("rewrite-delete-%d-%s-spec%d.avro", newSnapID, artifactSuffix, specID)
|
|
manifestPath := absoluteIcebergPath(bucketName, tablePath, "metadata", manifestName)
|
|
mf, manifestBytes, err := writeManifestWithContent(
|
|
manifestPath,
|
|
version,
|
|
spec,
|
|
meta.CurrentSchema(),
|
|
newSnapID,
|
|
specEntriesMap[specID].entries,
|
|
iceberg.ManifestContentDeletes,
|
|
)
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("write delete manifest for spec %d: %w", specID, err)
|
|
}
|
|
if err := saveFilerFile(ctx, filerClient, metaDir, manifestName, manifestBytes); err != nil {
|
|
return "", nil, fmt.Errorf("save delete manifest for spec %d: %w", specID, err)
|
|
}
|
|
writtenArtifacts = append(writtenArtifacts, artifact{dir: metaDir, fileName: manifestName})
|
|
allManifests = append(allManifests, mf)
|
|
}
|
|
|
|
var manifestListBuf bytes.Buffer
|
|
if err := iceberg.WriteManifestList(version, &manifestListBuf, newSnapID, &snapshotID, &seqNum, 0, allManifests); err != nil {
|
|
return "", nil, fmt.Errorf("write delete manifest list: %w", err)
|
|
}
|
|
manifestListName := fmt.Sprintf("snap-%d-%s.avro", newSnapID, artifactSuffix)
|
|
if err := saveFilerFile(ctx, filerClient, metaDir, manifestListName, manifestListBuf.Bytes()); err != nil {
|
|
return "", nil, fmt.Errorf("save delete manifest list: %w", err)
|
|
}
|
|
writtenArtifacts = append(writtenArtifacts, artifact{dir: metaDir, fileName: manifestListName})
|
|
|
|
manifestListLocation := absoluteIcebergPath(bucketName, tablePath, "metadata", manifestListName)
|
|
err = h.commitWithRetry(ctx, filerClient, bucketName, tablePath, metadataFileName, config, func(currentMeta table.Metadata, builder *table.MetadataBuilder) error {
|
|
cs := currentMeta.CurrentSnapshot()
|
|
if cs == nil || cs.SnapshotID != snapshotID {
|
|
return errStalePlan
|
|
}
|
|
newSnapshot := &table.Snapshot{
|
|
SnapshotID: newSnapID,
|
|
ParentSnapshotID: &snapshotID,
|
|
SequenceNumber: seqNum,
|
|
TimestampMs: newSnapID,
|
|
ManifestList: manifestListLocation,
|
|
Summary: &table.Summary{
|
|
Operation: table.OpReplace,
|
|
Properties: map[string]string{
|
|
"maintenance": "rewrite_position_delete_files",
|
|
"delete-files-rewritten": fmt.Sprintf("%d", deleteFilesRewritten),
|
|
"delete-files-written": fmt.Sprintf("%d", deleteFilesWritten),
|
|
"delete-groups": fmt.Sprintf("%d", rewrittenGroups),
|
|
},
|
|
},
|
|
SchemaID: func() *int {
|
|
id := meta.CurrentSchema().ID
|
|
return &id
|
|
}(),
|
|
}
|
|
if err := builder.AddSnapshot(newSnapshot); err != nil {
|
|
return err
|
|
}
|
|
return builder.SetSnapshotRef(table.MainBranch, newSnapID, table.BranchRef)
|
|
})
|
|
if err != nil {
|
|
return "", nil, fmt.Errorf("commit delete rewrite: %w", err)
|
|
}
|
|
|
|
committed = true
|
|
metrics := map[string]int64{
|
|
MetricDeleteFilesRewritten: deleteFilesRewritten,
|
|
MetricDeleteFilesWritten: deleteFilesWritten,
|
|
MetricDeleteBytesRewritten: deleteBytesRewritten,
|
|
MetricDeleteGroupsPlanned: rewrittenGroups,
|
|
MetricDeleteGroupsSkipped: skippedGroups,
|
|
MetricDurationMs: time.Since(start).Milliseconds(),
|
|
}
|
|
return fmt.Sprintf(
|
|
"rewrote %d position delete files into %d across %d group(s)",
|
|
deleteFilesRewritten,
|
|
deleteFilesWritten,
|
|
rewrittenGroups,
|
|
), metrics, nil
|
|
}
|