From 0a63e1f593df09bbb1d3a6c9b30c900505385d00 Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Thu, 1 May 2025 14:27:49 +0200 Subject: [PATCH] feat(backend): add age condition for state versions retention Closes #59 --- cmd/tfstated/main_test.go | 2 ++ pkg/database/db.go | 28 ++++++++++++++++++---------- pkg/database/states.go | 22 ++++++++++++---------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/cmd/tfstated/main_test.go b/cmd/tfstated/main_test.go index ecb82ee..dd17871 100644 --- a/cmd/tfstated/main_test.go +++ b/cmd/tfstated/main_test.go @@ -36,6 +36,8 @@ func TestMain(m *testing.M) { return "a528D1m9q3IZxLinSmHmeKxrx3Pmm7GQ3nBzIDxjr0A=" case "TFSTATED_VERSIONS_HISTORY_LIMIT": return "3" + case "TFSTATED_VERSIONS_HISTORY_MINIMUM_DAYS": + return "0" default: return "" } diff --git a/pkg/database/db.go b/pkg/database/db.go index 09d9d19..2c3c908 100644 --- a/pkg/database/db.go +++ b/pkg/database/db.go @@ -28,12 +28,13 @@ func initDB(ctx context.Context, url string) (*sql.DB, error) { } type DB struct { - ctx context.Context - dataEncryptionKey scrypto.AES256Key - readDB *sql.DB - sessionsSalt scrypto.AES256Key - versionsHistoryLimit int - writeDB *sql.DB + ctx context.Context + dataEncryptionKey scrypto.AES256Key + readDB *sql.DB + sessionsSalt scrypto.AES256Key + versionsHistoryLimit int + versionsHistoryMinimumDays int + writeDB *sql.DB } func NewDB(ctx context.Context, url string, getenv func(string) string) (*DB, error) { @@ -60,10 +61,11 @@ func NewDB(ctx context.Context, url string, getenv func(string) string) (*DB, er writeDB.SetMaxOpenConns(1) db := DB{ - ctx: ctx, - readDB: readDB, - versionsHistoryLimit: 64, - writeDB: writeDB, + ctx: ctx, + readDB: readDB, + versionsHistoryLimit: 128, + versionsHistoryMinimumDays: 28, + writeDB: writeDB, } pragmas := []struct { key string @@ -103,6 +105,12 @@ func NewDB(ctx context.Context, url string, getenv func(string) string) (*DB, er return nil, fmt.Errorf("failed to parse the TFSTATED_VERSIONS_HISTORY_LIMIT environment variable, expected an integer: %w", err) } } + versionsHistoryMinimumDays := getenv("TFSTATED_VERSIONS_HISTORY_MINIMUM_DAYS") + if versionsHistoryMinimumDays != "" { + if db.versionsHistoryMinimumDays, err = strconv.Atoi(versionsHistoryMinimumDays); err != nil { + return nil, fmt.Errorf("failed to parse the TFSTATED_VERSIONS_HISTORY_MINIMUM_DAYS environment variable, expected an integer: %w", err) + } + } return &db, nil } diff --git a/pkg/database/states.go b/pkg/database/states.go index 0bc6d17..3429418 100644 --- a/pkg/database/states.go +++ b/pkg/database/states.go @@ -244,19 +244,21 @@ func (db *DB) SetState(path string, accountId uuid.UUID, data []byte, lock strin if err != nil { return fmt.Errorf("failed to touch updated for state: %w", err) } + min := time.Now().Add(time.Duration(db.versionsHistoryMinimumDays) * -24 * time.Hour) _, err = tx.ExecContext(db.ctx, `DELETE FROM versions - WHERE state_id = (SELECT id - FROM states - WHERE path = :path) - AND id < (SELECT MIN(id) - FROM(SELECT versions.id - FROM versions - JOIN states ON states.id = versions.state_id - WHERE states.path = :path - ORDER BY versions.id DESC - LIMIT :limit));`, + WHERE state_id = (SELECT id + FROM states + WHERE path = :path) + AND id < (SELECT MIN(id) + FROM(SELECT versions.id + FROM versions + JOIN states ON states.id = versions.state_id + WHERE states.path = :path AND versions.created < :min + ORDER BY versions.id DESC + LIMIT :limit));`, sql.Named("limit", db.versionsHistoryLimit), + sql.Named("min", min), sql.Named("path", path), ) return err