mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-12 01:07:35 +02:00
filer: fail the request, not the process, when chunks cannot be resolved (#11685)
* filer: fail the request, not the process, when chunks cannot be resolved CompactFileChunks and ViewFromChunks discarded the error from NonOverlappingVisibleIntervals and iterated the nil interval list it returns on failure. A CreateEntry or UpdateEntry whose context was cancelled therefore panicked in SeparateGarbageChunks and took the whole filer down, losing the metadata log it had not yet flushed. Both functions now return the error. cleanupChunks fails the request with it, the mount flush paths return it, the replication sinks and the S3 SSE range reader return it, and a ChunkStreamReader that cannot build its view fails every read with it and reports it as its SourceError instead of reading as an empty file. Fixes #11682 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * filer: propagate chunk resolution failures in scan paths NonOverlappingVisibleIntervals can fail without a cancelled context; the query engine and log-store callers discarded the error and dereferenced nil intervals. * query: propagate chunk resolution errors instead of skipping files When a manifest cannot be resolved, ReadParquetStatistics, countLiveLogRowsExcludingParquetSources, and computeLiveLogMinMax logged a warning and skipped the failed file, so queries returned understated counts and wrong min/max values. Surface the errors instead: the fast-path aggregation collector now returns a DataSourceError so the engine falls back to a full scan, and the row-count helpers return the error rather than a partial total. --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com> Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
20 files changed
+271
-65
No files matched your search
@@ -116,8 +116,9 @@ func (opt *FastPathOptimizer) CollectDataSourcesWithTimeFilter(ctx context.Conte
|
||||
// Read parquet file statistics
|
||||
parquetStats, err := hybridScanner.ReadParquetStatistics(partitionPath)
|
||||
if err != nil {
|
||||
if isDebugMode(ctx) {
|
||||
fmt.Printf(" ERROR: Failed to read parquet statistics: %v\n", err)
|
||||
return dataSources, DataSourceError{
|
||||
Source: "parquet_statistics",
|
||||
Cause: fmt.Errorf("partition %s: %w", partitionPath, err),
|
||||
}
|
||||
} else if len(parquetStats) == 0 {
|
||||
if isDebugMode(ctx) {
|
||||
@@ -141,8 +142,9 @@ func (opt *FastPathOptimizer) CollectDataSourcesWithTimeFilter(ctx context.Conte
|
||||
parquetSources := opt.engine.extractParquetSourceFiles(dataSources.ParquetFiles[partitionPath])
|
||||
liveLogCount, liveLogErr := opt.engine.countLiveLogRowsExcludingParquetSources(ctx, partitionPath, parquetSources)
|
||||
if liveLogErr != nil {
|
||||
if isDebugMode(ctx) {
|
||||
fmt.Printf(" ERROR: Failed to count live log rows: %v\n", liveLogErr)
|
||||
return dataSources, DataSourceError{
|
||||
Source: "live_log_count",
|
||||
Cause: fmt.Errorf("partition %s: %w", partitionPath, liveLogErr),
|
||||
}
|
||||
} else {
|
||||
dataSources.LiveLogRowCount += liveLogCount
|
||||
@@ -308,7 +310,7 @@ func (comp *AggregationComputer) computeGlobalMin(spec AggregationSpec, dataSour
|
||||
|
||||
liveLogMin, _, err := comp.engine.computeLiveLogMinMax(partition, spec.Column, partitionParquetSources)
|
||||
if err != nil {
|
||||
continue // Skip partitions with errors
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if liveLogMin != nil {
|
||||
@@ -384,7 +386,7 @@ func (comp *AggregationComputer) computeGlobalMax(spec AggregationSpec, dataSour
|
||||
|
||||
_, liveLogMax, err := comp.engine.computeLiveLogMinMax(partition, spec.Column, partitionParquetSources)
|
||||
if err != nil {
|
||||
continue // Skip partitions with errors
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if liveLogMax != nil {
|
||||
|
||||
+29
-25
@@ -3894,8 +3894,7 @@ func (e *SQLEngine) computeLiveLogMinMax(partitionPath string, columnName string
|
||||
// Scan this log file for MIN/MAX values
|
||||
fileMin, fileMax, err := e.computeFileMinMax(filerClient, filePath, columnName)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to compute min/max for file %s: %v\n", filePath, err)
|
||||
return nil // Continue with other files
|
||||
return err
|
||||
}
|
||||
|
||||
// Update global min/max
|
||||
@@ -4030,7 +4029,10 @@ func (e *SQLEngine) eachLogEntryInFile(filerClient filer_pb.FilerClient, filePat
|
||||
|
||||
// Read file chunks and process them (pattern from countRowsInLogFile)
|
||||
fileSize := filer.FileSize(fileEntry)
|
||||
visibleIntervals, _ := filer.NonOverlappingVisibleIntervals(context.Background(), lookupFileIdFn, fileEntry.Chunks, 0, int64(fileSize))
|
||||
visibleIntervals, err := filer.NonOverlappingVisibleIntervals(context.Background(), lookupFileIdFn, fileEntry.Chunks, 0, int64(fileSize))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chunkViews := filer.ViewFromVisibleIntervals(visibleIntervals, 0, int64(fileSize))
|
||||
|
||||
for x := chunkViews.Front(); x != nil; x = x.Next {
|
||||
@@ -4185,8 +4187,7 @@ func (e *SQLEngine) countLiveLogRowsExcludingParquetSources(ctx context.Context,
|
||||
// Count rows in live log file
|
||||
rowCount, err := e.countRowsInLogFile(filerClient, partitionPath, entry)
|
||||
if err != nil {
|
||||
fmt.Printf("Warning: failed to count rows in %s/%s: %v\n", partitionPath, entry.Name, err)
|
||||
return nil // Continue with other files
|
||||
return err
|
||||
}
|
||||
totalRows += rowCount
|
||||
return nil
|
||||
@@ -4354,7 +4355,10 @@ func (e *SQLEngine) countRowsInLogFile(filerClient filer_pb.FilerClient, partiti
|
||||
|
||||
// Read file chunks and process them (pattern from read_log_from_disk.go)
|
||||
fileSize := filer.FileSize(entry)
|
||||
visibleIntervals, _ := filer.NonOverlappingVisibleIntervals(context.Background(), lookupFileIdFn, entry.Chunks, 0, int64(fileSize))
|
||||
visibleIntervals, err := filer.NonOverlappingVisibleIntervals(context.Background(), lookupFileIdFn, entry.Chunks, 0, int64(fileSize))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
chunkViews := filer.ViewFromVisibleIntervals(visibleIntervals, 0, int64(fileSize))
|
||||
|
||||
for x := chunkViews.Front(); x != nil; x = x.Next {
|
||||
@@ -4456,23 +4460,22 @@ func (e *SQLEngine) getTopicTotalRowCount(ctx context.Context, namespace, topicN
|
||||
// For each partition, count both parquet and live log rows
|
||||
for _, partition := range partitions {
|
||||
// Count parquet rows
|
||||
parquetStats, parquetErr := hybridScanner.ReadParquetStatistics(partition)
|
||||
if parquetErr == nil {
|
||||
for _, stats := range parquetStats {
|
||||
totalRowCount += stats.RowCount
|
||||
}
|
||||
parquetStats, err := hybridScanner.ReadParquetStatistics(partition)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("read parquet statistics for %s: %w", partition, err)
|
||||
}
|
||||
for _, stats := range parquetStats {
|
||||
totalRowCount += stats.RowCount
|
||||
}
|
||||
|
||||
// Count live log rows (with deduplication)
|
||||
parquetSourceFiles := make(map[string]bool)
|
||||
if parquetErr == nil {
|
||||
parquetSourceFiles = e.extractParquetSourceFiles(parquetStats)
|
||||
}
|
||||
parquetSourceFiles := e.extractParquetSourceFiles(parquetStats)
|
||||
|
||||
liveLogCount, liveLogErr := e.countLiveLogRowsExcludingParquetSources(ctx, partition, parquetSourceFiles)
|
||||
if liveLogErr == nil {
|
||||
totalRowCount += liveLogCount
|
||||
liveLogCount, err := e.countLiveLogRowsExcludingParquetSources(ctx, partition, parquetSourceFiles)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("count live log rows for %s: %w", partition, err)
|
||||
}
|
||||
totalRowCount += liveLogCount
|
||||
}
|
||||
|
||||
return totalRowCount, nil
|
||||
@@ -4509,17 +4512,18 @@ func (e *SQLEngine) getActualRowsScannedForFastPath(ctx context.Context, namespa
|
||||
// (parquet files use metadata/statistics, so they contribute 0 to scan count)
|
||||
for _, partition := range partitions {
|
||||
// Get parquet files to determine what was converted
|
||||
parquetStats, parquetErr := hybridScanner.ReadParquetStatistics(partition)
|
||||
parquetSourceFiles := make(map[string]bool)
|
||||
if parquetErr == nil {
|
||||
parquetSourceFiles = e.extractParquetSourceFiles(parquetStats)
|
||||
parquetStats, err := hybridScanner.ReadParquetStatistics(partition)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("read parquet statistics for %s: %w", partition, err)
|
||||
}
|
||||
parquetSourceFiles := e.extractParquetSourceFiles(parquetStats)
|
||||
|
||||
// Count only live log rows that haven't been converted to parquet
|
||||
liveLogCount, liveLogErr := e.countLiveLogRowsExcludingParquetSources(ctx, partition, parquetSourceFiles)
|
||||
if liveLogErr == nil {
|
||||
totalScannedRows += liveLogCount
|
||||
liveLogCount, err := e.countLiveLogRowsExcludingParquetSources(ctx, partition, parquetSourceFiles)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("count live log rows for %s: %w", partition, err)
|
||||
}
|
||||
totalScannedRows += liveLogCount
|
||||
|
||||
// Note: Parquet files contribute 0 to scan count since we use their metadata/statistics
|
||||
}
|
||||
|
||||
@@ -1140,9 +1140,7 @@ func (h *HybridMessageScanner) ReadParquetStatistics(partitionPath string) ([]*P
|
||||
// Extract statistics from this parquet file
|
||||
stats, err := h.extractParquetFileStats(entry, lookupFileIdFn, chunkCache)
|
||||
if err != nil {
|
||||
// Log error but continue processing other files
|
||||
fmt.Printf("Warning: failed to extract stats from %s: %v\n", entry.Name, err)
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
if stats != nil {
|
||||
@@ -1164,7 +1162,10 @@ func (h *HybridMessageScanner) extractParquetFileStats(entry *filer_pb.Entry, lo
|
||||
}
|
||||
// Create reader for the parquet file
|
||||
fileSize := filer.FileSize(entry)
|
||||
visibleIntervals, _ := filer.NonOverlappingVisibleIntervals(context.Background(), lookupFileIdFn, entry.Chunks, 0, int64(fileSize))
|
||||
visibleIntervals, err := filer.NonOverlappingVisibleIntervals(context.Background(), lookupFileIdFn, entry.Chunks, 0, int64(fileSize))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve chunks of %s: %w", entry.Name, err)
|
||||
}
|
||||
chunkViews := filer.ViewFromVisibleIntervals(visibleIntervals, 0, int64(fileSize))
|
||||
readerCache := filer.NewReaderCache(32, chunkCache, lookupFileIdFn, nil)
|
||||
readerAt := filer.NewChunkReaderAtFromClient(context.Background(), readerCache, chunkViews, int64(fileSize), filer.DefaultPrefetchCount)
|
||||
|
||||
Reference in new issue
Block a user