Name each database target's archive for its webhook and target (closes #376)
check / check (push) Waiting to run
check / check (push) Waiting to run
Each database target now writes its own archive file, archive-WEBHOOKNAME-TARGETNAME-TARGETID.db, named by delivery.ArchiveFileName, in place of one archive per webhook keyed on its UUID. Renaming a webhook or a target renames its archive files (with any -wal and -shm) before the new name is saved, never over an existing file, and moves every one back if a rename or the save fails. The webhook edit, the target edit and target creation share one lock so no two interleave. Deleting a webhook or target leaves its files on disk. Nothing looks for the old archive-WEBHOOKID.db files. The README gives the naming and the recovery steps. Model: opus-5-5
This commit was merged in pull request #418.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"go.uber.org/fx"
|
||||
"gorm.io/gorm"
|
||||
"sneak.berlin/go/webhooker/internal/config"
|
||||
"sneak.berlin/go/webhooker/internal/database"
|
||||
"sneak.berlin/go/webhooker/internal/lifecycle"
|
||||
@@ -25,14 +26,14 @@ type ArchiveSweeperParams struct {
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// ArchiveSweeper periodically prunes expired rows from
|
||||
// per-webhook archive databases whose database target carries a
|
||||
// positive expiry.
|
||||
// ArchiveSweeper periodically prunes expired rows from the
|
||||
// archive databases of database targets that carry a positive
|
||||
// expiry.
|
||||
//
|
||||
// Without it, pruning happens only when an archive is
|
||||
// (re)opened, and archives are only ever reopened by writes: an
|
||||
// archive belonging to a webhook that has stopped receiving
|
||||
// events would keep its expired rows forever. The sweep closes
|
||||
// archive whose target has stopped receiving events would keep
|
||||
// its expired rows forever. The sweep closes
|
||||
// that gap without changing anything for archives whose expiry
|
||||
// is unset or "never".
|
||||
//
|
||||
@@ -155,7 +156,7 @@ func (s *ArchiveSweeper) run(ctx context.Context) {
|
||||
// soft-deleted along with it, so GORM's default scope already
|
||||
// excludes them.
|
||||
//
|
||||
// A failure for one webhook is logged and the sweep continues,
|
||||
// A failure for one target is logged and the sweep continues,
|
||||
// matching how the write path already treats a prune error as
|
||||
// non-fatal.
|
||||
func (s *ArchiveSweeper) sweep(ctx context.Context) {
|
||||
@@ -210,19 +211,20 @@ func (s *ArchiveSweeper) sweepTarget(target *database.Target) {
|
||||
return
|
||||
}
|
||||
|
||||
err = s.eng.dbTarget.sweepWebhook(target.WebhookID, expiry)
|
||||
err = s.eng.dbTarget.sweepArchive(target.ID, expiry)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// A writer evicted underneath the sweep means the operator
|
||||
// deleted the webhook (or its last database target) while the
|
||||
// sweep was walking the target list. That is an ordinary
|
||||
// A writer evicted, or a target row gone, underneath the sweep
|
||||
// means the operator deleted the target or its webhook while
|
||||
// the sweep was walking the target list. That is an ordinary
|
||||
// interleaving, not a failure, so it must not produce an
|
||||
// error line.
|
||||
if errors.Is(err, errArchiveWriterEvicted) {
|
||||
if errors.Is(err, errArchiveWriterEvicted) ||
|
||||
errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.log.Debug(
|
||||
"archive sweep: writer evicted mid-sweep",
|
||||
"archive sweep: target deleted mid-sweep",
|
||||
"webhook_id", target.WebhookID,
|
||||
"target_id", target.ID,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user