compaction

package
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrConcurrentModification = errors.New("manifest concurrently modified")

ErrConcurrentModification indicates manifest was modified by another process.

View Source
var ErrManifestMetadataContractViolation = errors.New("manifest metadata contract violated")

ErrManifestMetadataContractViolation indicates provider.SaveManifest succeeded but did not advance manifest metadata as required by FileProvider contract.

View Source
var ErrSourceChecksumMismatch = errors.New("compaction source checksum mismatch")

ErrSourceChecksumMismatch marks a rewrite input whose bytes no longer hash to its manifest stamp. The rewrite must NOT proceed: runRewrite merges the sources into the new base and then deletes them, so this gate is the last moment silent corruption (#347) is both detectable and still attributable to a named object. Probe failures never map to this error — a failed GET is transient infrastructure, not a corruption verdict.

Functions

func IsConcurrentModification added in v0.2.0

func IsConcurrentModification(err error) bool

IsConcurrentModification reports whether err is a confirmed HTTP 412 conditional-put rejection — the only save failure that proves the write did not commit. Exported for callers outside this package (the manifest-reconcile tool) that save manifests under an etag without going through the compactor's saveManifestChecked.

Types

type CompactionOutcome added in v0.1.0

type CompactionOutcome string

CompactionOutcome describes the result of a compaction pass for a single schema.

const (
	Noop             CompactionOutcome = "noop"
	PromotionApplied CompactionOutcome = "promotion_applied"
	// RewritePending is reported when the dirty ratio calls for a rewrite but
	// no Merger is wired, so the pass leaves everything untouched.
	RewritePending CompactionOutcome = "rewrite_pending"
	// RewriteApplied is reported when the dirty-ratio rewrite merged the full
	// base+delta set into a new base file and committed the manifest swap.
	RewriteApplied CompactionOutcome = "rewrite_applied"
)

type CompactionResult added in v0.1.0

type CompactionResult struct {
	Outcome    CompactionOutcome
	SchemaID   int16
	Version    int64
	DirtyRatio float64
	BaseMB     int64
	DeltaMB    int64

	// Rewrite metadata, populated only when Outcome is RewriteApplied.
	FilesMerged int    // source files folded into the new base
	RowsIn      int64  // rows read across all source files
	RowsOut     int64  // surviving LWW winners written to the new base
	NewBaseKey  string // S3 key of the merged base parquet
}

CompactionResult carries the outcome and metadata from a compaction pass.

type Compactor

type Compactor struct {
	Logger     *zap.Logger
	Config     cdc.CompactionConfig
	Provider   FileProvider
	Merger     Merger
	S3         cdc.S3ObjectClient
	Bucket     string
	DataPrefix string // root prefix for parquet files
	Resolver   manifest.PathResolver

	// ObjectReader hashes published objects for manifest checksum stamping
	// and pre-merge input verification (#347). Nil disables both.
	ObjectReader cdc.S3GetClient
}

Compactor performs Base/Delta maintenance per schema. The dirty-ratio rewrite (#188) additionally needs Merger, S3, Bucket and DataPrefix; when any is missing a rewrite-eligible pass reports RewritePending instead.

func (*Compactor) RunOnce

func (c *Compactor) RunOnce(ctx context.Context) (CompactionResult, error)

RunOnce executes compaction for a schema and returns a typed result.

type DuckMerger added in v0.2.0

type DuckMerger struct {
	DB *sql.DB
	// CopyOptions overrides the parquet COPY options (defaults to the CDC
	// exporters' FORMAT PARQUET, V2, ZSTD level 3).
	CopyOptions string
	// Logger reports the best-effort manifest stamp probe (#256). Optional —
	// nil is safe and silences it.
	Logger *zap.Logger
	// contains filtered or unexported fields
}

DuckMerger implements Merger over a DuckDB connection that already has httpfs and S3 credentials configured — cdc.NewDuckExporter's DB is the production source of exactly that.

func (*DuckMerger) MergeToTmp added in v0.2.0

func (d *DuckMerger) MergeToTmp(ctx context.Context, sourceURIs []string, tmpURI string) (MergeStats, error)

type FileProvider

type FileProvider interface {
	LoadManifest(ctx context.Context, schemaID int16) (*manifest.Manifest, string, error)
	// SaveManifest persists the manifest as a commit point.
	// Implementations must mutate the provided manifest pointer in-place on
	// successful save:
	// - Version must increase monotonically.
	// - UpdatedAtMs must move forward.
	// Compactor relies on the same pointer carrying updated metadata and does
	// not mutate those fields directly.
	SaveManifest(ctx context.Context, schemaID int16, m *manifest.Manifest, etag string) (string, error)
}

FileProvider fetches manifest and lists actual files (optionally cross-check).

type ManifestProvider

type ManifestProvider struct {
	Store    manifest.Store
	Resolver manifest.PathResolver
}

ManifestProvider adapts manifest.Store + resolver to FileProvider.

func NewFSManifestProvider

func NewFSManifestProvider(cfg cdc.ManifestConfig, rootFS fs.FS) *ManifestProvider

NewFSManifestProvider creates a ManifestProvider backed by local filesystem. Useful for testing and local development.

func NewManifestProvider

func NewManifestProvider(cfg cdc.ManifestConfig, store manifest.Store) *ManifestProvider

NewManifestProvider creates a ManifestProvider from a ManifestConfig and Store. Use this to wire up the provider from configuration.

func NewS3ManifestProvider

func NewS3ManifestProvider(cfg cdc.ManifestConfig, s3Client manifest.S3Client) *ManifestProvider

NewS3ManifestProvider creates a ManifestProvider backed by S3. It accepts any manifest.S3Client (Get/Put) so tests can decorate the real client.

func (*ManifestProvider) LoadManifest

func (p *ManifestProvider) LoadManifest(ctx context.Context, schemaID int16) (*manifest.Manifest, string, error)

func (*ManifestProvider) SaveManifest

func (p *ManifestProvider) SaveManifest(ctx context.Context, schemaID int16, m *manifest.Manifest, etag string) (string, error)

type MergeStats added in v0.2.0

type MergeStats struct {
	RowsIn     int64  // version rows read across all source files
	RowsOut    int64  // surviving LWW winners written to the merged file
	RowIDMin   string // "" when the merge produced zero rows
	RowIDMax   string
	CreatedMin int64
	CreatedMax int64
	// Columns is the merged file's footer schema (name → DuckDB type),
	// stamped into the manifest entry (#256). Nil when the self-describe
	// failed; the entry then stays unstamped and reads fall back to probing.
	Columns map[string]string
}

MergeStats carries what the rewrite orchestration needs to build the new base FileEntry and the CompactionResult counters.

func SingleFileStats added in v0.2.0

func SingleFileStats(ctx context.Context, db *sql.DB, uri string) (MergeStats, error)

SingleFileStats recomputes one parquet file's manifest metadata (row count, row_id min/max, changed_at min/max) from its contents via the given DuckDB session. It runs the same stats query the merge path uses for a freshly merged file, so a manifest entry rebuilt from it matches what compaction itself would have written. The manifest-reconcile tool uses this to repair orphaned delta files (#203) without trusting filenames.

type Merger added in v0.2.0

type Merger interface {
	MergeToTmp(ctx context.Context, sourceURIs []string, tmpURI string) (MergeStats, error)
}

Merger folds a schema's complete base+delta parquet set into one merged base file at tmpURI. Implementations only stage the tmp object; the compactor owns promotion to the final key, the manifest swap, and source cleanup.

type UncoveredRow added in v0.2.0

type UncoveredRow struct {
	RowID     string
	Tombstone bool
}

UncoveredRow is one row_id in an orphan parquet whose newest uncovered version is not superseded by any manifest-listed file. Tombstone reports whether that version is a delete marker — re-appending an uncovered tombstone RESTORES a lost delete, while re-appending an uncovered live version of a Postgres-deleted row resurrects it.

func UncoveredRows added in v0.2.0

func UncoveredRows(ctx context.Context, db *sql.DB, orphanURI string, listedURIs []string) ([]UncoveredRow, error)

UncoveredRows returns, per row_id, the orphan parquet's versions that no listed file supersedes. Coverage is version-aware: a listed version with changed_at >= the orphan version's covers it (the anti-join is >=, so an equal-changed_at listed version counts as covering — post-#274 equal ties are value-identical copies with an unspecified winner), so a same-row lost update — an orphan carrying a NEWER version than anything listed — still counts as uncovered. The manifest-reconcile tool (#203) builds its repair verdict on this: a row_id-only anti-join would misclassify lost updates as deletable leftovers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL