diff --git a/compactor.go b/compactor.go index bfa05ddf9..04dd0d7d6 100644 --- a/compactor.go +++ b/compactor.go @@ -31,6 +31,12 @@ type Compactor struct { // policies handle retention instead. Local file cleanup still occurs. RetentionEnabled bool + // SpillDir, when set, lets the ltx encoder spill the output page index of + // a very large compaction to a temp file in that directory instead of + // holding it in memory (ltx#96). DB sets it to the database meta + // directory, which is writable even in the hardened image. + SpillDir string + // CompactionVerifyErrorCounter is incremented when post-compaction // verification fails. Optional; if nil, no metric is recorded. CompactionVerifyErrorCounter prometheus.Counter @@ -162,7 +168,9 @@ func (c *Compactor) Compact(ctx context.Context, dstLevel int) (*ltx.FileInfo, e _ = pw.CloseWithError(fmt.Errorf("new ltx compactor: %w", err)) return } + defer func() { _ = comp.Cleanup() }() comp.HeaderFlags = ltx.HeaderFlagNoChecksum + comp.SetSpillDir(c.SpillDir) _ = pw.CloseWithError(comp.Compact(ctx)) }() diff --git a/db.go b/db.go index b44242add..98d1dcb7c 100644 --- a/db.go +++ b/db.go @@ -1113,6 +1113,7 @@ func (db *DB) init(ctx context.Context) (err error) { if err := internal.MkdirAll(db.metaPath, db.dirInfo); err != nil { return err } + db.compactor.SpillDir = db.metaPath // Ensure WAL has at least one frame in it. if err := db.ensureWALExists(ctx); err != nil { @@ -2883,6 +2884,11 @@ func (db *DB) snapshotReader(ctx context.Context, pos *snapshotReadPosition) (io pw.CloseWithError(fmt.Errorf("new ltx encoder: %w", err)) return } + // Very large snapshots spill their page index into the meta + // directory rather than holding it in memory; Cleanup covers the + // cancellation paths that never reach enc.Close. + enc.SetSpillDir(db.MetaPath()) + defer enc.Cleanup() if err := enc.EncodeHeader(ltx.Header{ Version: ltx.Version, Flags: ltx.HeaderFlagNoChecksum, diff --git a/go.mod b/go.mod index 528fa67b9..530a151e0 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/prometheus/client_golang v1.17.0 github.com/psanford/sqlite3vfs v0.0.0-20260519004904-f9180fa2acc9 // direct github.com/studio-b12/gowebdav v0.11.0 - github.com/superfly/ltx v0.5.3-0.20260827162011-d457a1ab7844 + github.com/superfly/ltx v0.5.3-0.20260828134549-493c5d2d9e9f golang.org/x/crypto v0.52.0 golang.org/x/sys v0.45.0 google.golang.org/api v0.155.0 diff --git a/go.sum b/go.sum index 447ce4f9a..47a573405 100644 --- a/go.sum +++ b/go.sum @@ -270,8 +270,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/studio-b12/gowebdav v0.11.0 h1:qbQzq4USxY28ZYsGJUfO5jR+xkFtcnwWgitp4Zp1irU= github.com/studio-b12/gowebdav v0.11.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE= -github.com/superfly/ltx v0.5.3-0.20260827162011-d457a1ab7844 h1:4TKqDSdHlpV4Me/BoP/D7gszAPidElRDf64KBwnFg+U= -github.com/superfly/ltx v0.5.3-0.20260827162011-d457a1ab7844/go.mod h1:0OtLSLHHHPa3qDrAmwkLW5pUBfj365JS+bR9FEJDrM4= +github.com/superfly/ltx v0.5.3-0.20260828134549-493c5d2d9e9f h1:/PPQGMNVM3bfmpv33zC8aTXtE1vyUk0xu3viTUZkR9k= +github.com/superfly/ltx v0.5.3-0.20260828134549-493c5d2d9e9f/go.mod h1:0OtLSLHHHPa3qDrAmwkLW5pUBfj365JS+bR9FEJDrM4= github.com/tetratelabs/wazero v1.2.1 h1:J4X2hrGzJvt+wqltuvcSjHQ7ujQxA9gb6PeMs4qlUWs= github.com/tetratelabs/wazero v1.2.1/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= github.com/wasilibs/go-re2 v1.3.0 h1:LFhBNzoStM3wMie6rN2slD1cuYH2CGiHpvNL3UtcsMw= diff --git a/replica.go b/replica.go index 2584abd34..5180279f8 100644 --- a/replica.go +++ b/replica.go @@ -743,6 +743,7 @@ func (r *Replica) Restore(ctx context.Context, opt RestoreOptions) (err error) { pw.CloseWithError(fmt.Errorf("new ltx compactor: %w", err)) return } + defer func() { _ = c.Cleanup() }() c.HeaderFlags = ltx.HeaderFlagNoChecksum _ = pw.CloseWithError(c.Compact(ctx)) }() diff --git a/vfs.go b/vfs.go index bf2a96d65..3a6c0edf3 100644 --- a/vfs.go +++ b/vfs.go @@ -703,6 +703,7 @@ func (h *Hydrator) Restore(ctx context.Context, infos []*ltx.FileInfo) error { if err != nil { return fmt.Errorf("new ltx compactor: %w", err) } + defer func() { _ = c.Cleanup() }() c.HeaderFlags = ltx.HeaderFlagNoChecksum h.compactor = c