Přeskočit obsah

Garbage Collector — model napříč typy dat

Status: v0.1 (rozšířeno 2026-05-14 pro sync dirs) Související: sync-backup.md, s3backup.md, auth-organization.md

1. Cíl

Sjednotit mazání S3 dat napříč AVAX komponentami pomocí jediného mechanismu — storage_deletion_requests s grace period + Celery GC worker. Žádné komponenty by neměly mazat ze S3 přímo — vše jde přes deletion request flow.

2. Důvody (proč grace period a ne direct delete)

  • Anti-rage-delete — uživatel odebere adresář v afektu → 30 dní si to může rozmyslet.
  • Forensika / audit — kdo, co, kdy odstranil. storage_deletion_requests row je důkaz.
  • Recovery — super_admin nebo cancel mechanismus může operaci zvrátit do delete_after deadline.
  • Bulk consistency — když uživatel maže 10 souborů, je atomická 1 request, ne 10 race-condition operations.

3. Datový model — storage_deletion_requests

Sloupec Typ Význam
id UUID PK
company_id UUID FK Pro filtr / multi-tenant
requested_by UUID FK Kdo iniciátor (může být system user pro auto-clean)
approved_by UUID FK NULL Schvalovatel (NULL pro auto-approved)
status VARCHAR(30) pending_approval / approved / rejected / cancelled / completed
approve_deadline TIMESTAMP NULL Pro pending_approval — kdy expiruje (defaultně +14d)
delete_after TIMESTAMP NULL Pro approved — kdy GC smaže (defaultně approved+30d)
reason TEXT NULL Lidský popis
file_count INT NULL Pro UI feedback
total_bytes BIGINT NULL Pro storage usage tracking
s3_keys JSONB NULL Seznam explicitních S3 klíčů — pro malé deletion (do 100 souborů)
s3_prefix VARCHAR(512) NULL NOVĚ: Alternativa k s3_keys pro velké adresáře — GC paginate-deletes pod prefixem
source VARCHAR(30) NOVĚ: Typ deletion: user_request / sync_dir_remove / app_uninstall / chat_export / gdpr_anonymize / ...
created_at TIMESTAMP
completed_at TIMESTAMP NULL

Migrace 019 — přidává s3_prefix + source. Existující řádky dostávají source='user_request' (default).

4. Flow per source type

4.1 user_request (explicitní žádost o smazání — současné)

POST /storage/deletion-requests {s3_keys, reason}
  → status: pending_approval, file_count: len(s3_keys)
  → notifikace company_adminovi

POST /storage/deletion-requests/{id}/approve  (admin)
  → status: approved, delete_after: now + 30d

GC v 02:00:
  → vybere approved s delete_after <= now
  → smaže jednotlivé s3_keys
  → status: completed

4.2 sync_dir_remove (uživatel odebírá vlastní sync adresář — NOVĚ)

DELETE /storage/sync-dirs/{id}
  V services/devices.py:remove_sync_dir:
  → načti entry.s3_prefix
  → IF entry.share_id is None (vlastní dir, ne subscribed):
       create StorageDeletionRequest(
         status='approved',  ← auto-approve, žádný admin nutný
         source='sync_dir_remove',
         s3_prefix=entry.s3_prefix,
         delete_after=now + 30d,
         requested_by=user.id,
         approved_by=user.id,  ← uživatel implicitně schválil
       )
  → IF entry.share_id is not None (subscribed):
       skip deletion request — cizí source data zůstávají!
  → db.delete(entry); commit

GC v 02:00 (stejný worker):
  → vybere approved s delete_after <= now
  → IF s3_prefix: list_objects_v2 paginate + delete_objects batch (max 1000/batch)
  → IF s3_keys: stará cesta — per-key delete
  → status: completed

Důležité: Uživatel nemusí čekat na approval (sám se rozhodl). Ale fyzický delete čeká 30 dní → recovery možný přes POST /storage/deletion-requests/{id}/cancel.

4.3 app_uninstall (budoucnost)

Když firma odhlásí předplatné nebo uživatel smaže AVAX app: - S3 prefix companies/{cid}/apps/{slug}/ - Auto-approve, 30d grace - Notifikace ownerovi

4.4 chat_export (budoucnost)

Po manuálním exportu chat konverzace: - S3 prefix companies/{cid}/chat/exported/{conv_id}/ - Auto-approve, 7d grace (kratší — export je dobrovolný)

4.5 gdpr_anonymize (budoucnost)

Anonymizace uživatele: - Všechny S3 prefixy patřící uživateli - Auto-approve, 0d grace (GDPR požaduje okamžitě) - Status='approved' + delete_after=now

5. GC worker — gc.delete_expired

Celery beat schedule: každou noc 02:00. Manual trigger: POST /admin/storage/gc/run.

Pseudo-code:

async def run_gc(db):
    expired = SELECT * FROM storage_deletion_requests
              WHERE status='approved' AND delete_after <= now()
    for req in expired:
        if req.s3_prefix:
            # Paginated prefix delete
            for batch in list_objects_v2_paginator(prefix=req.s3_prefix, max=1000):
                delete_objects(batch)
        else:
            # Legacy explicit keys
            for key in req.s3_keys:
                delete_object(key)
        req.status = 'completed'
        req.completed_at = now()

Retry: při failure jednotlivého klíče/objektu se uloží do s3_keys nebo zachová s3_prefix (s update file_count na zbývající) a GC zkusí znovu příští noc.

6. Cancel flow

POST /storage/deletion-requests/{id}/cancel — povolen pro: - requester (do delete_after) - company_admin / super_admin (kdykoli před completed)

Cancel u sync_dir_remove ale nevrací sync_dir řádek do device_sync_dirs — ten byl už smazán z DB. Uživatel může: - Vytvořit nový sync_dir se stejným local_path - Backup data jsou stále na S3 → můžou si je obnovit pomocí Full Restore flow

7. Pravidla pro nové komponenty

Pokud nová komponenta píše do S3 a může to chtít smazat:

  1. Nikdy delete_object přímo — vždy přes services/deletion.py:create_deletion_request
  2. Pokud user-initiated → status='pending_approval' (čeká schválení admin)
  3. Pokud system-initiated (auto-clean, uninstall) → status='approved', vyplnit approved_by=system_user_id
  4. Pro velké S3 adresáře (>100 souborů) použít s3_prefix, ne s3_keys
  5. Nastavit source na nový identifikátor pro tracking / metrics

8. Otevřené otázky

  • Notification ownera při auto-approved sync_dir_remove — pošleme e-mail "soubor budou smazány za 30 dní"? Riziko spamu pokud user často edituje sync dirs.
  • S3 bucket versioning interakce — bucket claudeai má versioning Enabled. delete_object vytvoří delete marker, soubory zůstávají versionované. Pro skutečné mazání je nutný delete_object s VersionId nebo lifecycle pravidlo. TODO: ověřit chování + případně přidat list_object_versions + per-version delete v GC.
  • Per-company storage usagetotal_bytes v deletion request nese info o tom co bude smazáno. Mělo by se to započítávat do storage_usage view: "approved deletes pending" = nevykazují se do current usage ale do reserved-for-deletion?
  • Audit log entry — každá deletion request creation → audit_log row? Asi ano pro security.

9. Migration checklist

  • Migrace 019: s3_prefix, source columns
  • remove_sync_dir v services/devices.py vytvoří auto-approved request
  • run_gc umí s3_prefix (paginated delete)
  • UI: BackupScreen ukazuje pending deletion requests + cancel button
  • UI: confirmation dialog při DELETE sync-dir s textem o 30denní grace
  • Reuse pro app_uninstall (až bude flow)
  • Reuse pro gdpr_anonymize (až bude flow)