Skip to content

Latest commit

 

History

History
69 lines (55 loc) · 3.39 KB

File metadata and controls

69 lines (55 loc) · 3.39 KB

Attachment storage and recovery

Linked Notes stores attachment metadata in PostgreSQL and bytes in the attachment_data Docker volume mounted at /data/attachments. Back up and restore them together; either one alone is incomplete.

Storage contract

  • Final filenames are random UUIDs generated by the server. They have no client path or extension.
  • In-progress files live under .staging/<uuid>.part on the same volume so the final rename is atomic.
  • The database records the display name, detected safe MIME type, byte count, SHA-256, optional image dimensions, note ID, and timestamps.
  • ATTACHMENTS_DIR must be absolute. MAX_UPLOAD_BYTES is configurable and defaults to 104,857,600 bytes (100 MiB).
  • PNG, JPEG, GIF, and WebP may render inline. All other content downloads as an attachment; unknown or misleading content uses application/octet-stream.

The current schema needed no Phase 4 migration: the versioned foundational migration already created the Attachment table, indexes, and cascading note relationship. Phase 4 activates that reviewed schema and adds filesystem integrity behavior around it.

Failure behavior

Oversized, empty, length-mismatched, interrupted, or invalid requests remove their staged bytes and do not create metadata. If the note changes before commit, the optimistic conflict rolls back metadata/version changes and removes the final byte. Removal commits metadata first and deletes bytes afterward; an unlink failure is logged as attachment_orphan_cleanup_failed with an opaque storage name and is repaired by reconciliation.

If a referenced file is absent or its size differs, the attachment card remains visible with an unavailable state and download returns HTTP 410. Metadata is not silently deleted. Workspace Settings can run a checksum reconciliation and remove only unreferenced final bytes and staging files older than 24 hours.

Phase 4 measurements

Measured on 19 July 2026 using the loopback Compose installation, Docker Engine 29.6.1, and the non-root read-only app container:

  • 96 MiB deterministic upload in 64 KiB chunks
  • 4.079 seconds, 23.53 MiB/s
  • app memory: 69.43 MiB baseline, 79.98 MiB peak, 10.55 MiB increase
  • server SHA-256: 49542702b710c0b50cd7bc83caee0e8e0fe03c8863a052b52d3f30f9101350ad
  • the client-generated and server-generated SHA-256 values matched; cleanup removed the measurement fixture

After an app-container restart, health remained green and a fixture re-downloaded with the same pre-restart SHA-256. Post-restart reconciliation reported equal metadata and stored-file counts with zero missing, corrupt, orphaned, or stale files. These local numbers demonstrate bounded behavior for the agreed fixture; they are not a universal throughput guarantee.

Backup and recovery

Workspace Settings now creates one portable archive containing the relational workspace and checksum-verified attachment bytes. Restore stages and verifies the complete archive before changing live data; replacement creates a retained safety backup first. See backup, restore, and recovery for the format, limits, merge semantics, and step-by-step recovery procedure.

Host-level PostgreSQL and attachment_data volume snapshots remain useful as a second independent recovery layer, but must be captured together while writes are stopped. Do not delete either volume with docker compose down --volumes unless permanent data loss is intended.