mirror of
https://github.com/binwiederhier/ntfy.git
synced 2026-05-09 08:26:00 +02:00
[PR #1672] [MERGED] Attachment fixes to address inconsistencies between DB and backend store #1686
Labels
No labels
ai-generated
android-app
android-app
android-app
🪲 bug
build
build
dependencies
docs
enhancement
enhancement
🔥 HOT
in-progress 🏃
ios
prio:low
prio:low
pull-request
question
🔒 security
server
server
unified-push
web-app
website
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/ntfy#1686
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/binwiederhier/ntfy/pull/1672
Author: @binwiederhier
Created: 3/24/2026
Status: ✅ Merged
Merged: 3/25/2026
Merged by: @binwiederhier
Base:
main← Head:attachment-fixes📝 Commits (5)
e55d1ceAttachment fixes to address inconsistencies between DB and backend store071543eFixesca59cfcWords3ff8bacUpdate changelogf256a41Fix test📊 Changes
18 files changed (+161 additions, -167 deletions)
View changed files
📝
attachment/store.go(+17 -11)📝
attachment/store_file_test.go(+2 -1)📝
attachment/store_s3_test.go(+2 -2)📝
docs/releases.md(+1 -1)📝
message/cache.go(+19 -45)📝
message/cache_postgres.go(+4 -8)📝
message/cache_postgres_schema.go(+42 -4)📝
message/cache_sqlite.go(+4 -8)📝
message/cache_sqlite_schema.go(+14 -1)📝
message/cache_sqlite_test.go(+2 -2)📝
message/cache_test.go(+11 -29)📝
server/config.go(+12 -5)📝
server/server.go(+5 -2)📝
server/server_account_test.go(+2 -0)📝
server/server_manager.go(+18 -28)📝
server/server_payments_test.go(+2 -0)📝
server/server_test.go(+4 -2)📝
web/package-lock.json(+0 -18)📄 Description
Summary
Fixes attachment handling to prevent orphaned S3/filesystem files and phantom DB records (DB records pointing to missing files). These issues were caused by race conditions during server restarts and missing safeguards in the cleanup logic.
Attachment expiry cap
attachment_expiresis now capped to never exceedmessage.expires, ensuring attachments can never outlive their messageSingle owner for file deletion
pruneAttachments(),pruneMessages(), andsync()could delete attachment files, creating race conditions during restarts (files deleted but DB rows surviving → phantom records causing user-facing 404s)sync()deletes files from storage.pruneAttachments()andpruneMessages()only update the DB. If the process is killed mid-operation, the worst case is orphaned files (cleaned up bysync()) rather than phantom DB recordsBatched message/attachment pruning
pruneMessages()deleted rows one-by-one in a transaction (242K individualDELETE WHERE mid=$1over remote Postgres = 115 seconds, blocking the entire manager cycle)DELETE ... WHERE mid IN (SELECT mid ... LIMIT $2)query, capped atManagerBatchSize(default 30K) per cycle. Backlog clears over multiple 1-minute cycles (~2s each) instead of one long blocking operationMissing Postgres index
idx_message_attachment_expires ON message (attachment_expires) WHERE attachment_deleted = FALSE— SQLite already had this, Postgres didn't. Without it,AttachmentsExpiredandAttachmentsWithSizesqueries did full table scans on 1M+ rowsConfigurable orphan grace period
orphanGracePeriodis now injected through the store constructor viaConfig.AttachmentOrphanGracePeriodinstead of being a package-level constantRemoved dead code
DeleteMessages(),MessagesExpired(),AttachmentsExpired(),MarkAttachmentsDeleted(), and their per-row queries — all replaced by the batched equivalentsTest plan
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.