docs(sync): record #9105 staleness-eviction plan, improve prune snack (#9106)

This commit is contained in:
Johannes Millan 2026-07-17 12:53:16 +02:00 committed by GitHub
parent a224eb5fa3
commit fcbb789746
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 47 additions and 4 deletions

View file

@ -433,3 +433,46 @@ via **Dotted Version Vectors** (bound to server vnodes, not devices),
**bounded reclaimable client IDs** (needs a registration/retirement protocol),
or **periodic stable-cut GC** (needs all-to-all clock reporting). None apply to
the current dumb-relay model.
### Future option: staleness-informed eviction (issue #9105 — works in the dumb-relay model)
Pruning today evicts the **lowest-counter** entries, but a low counter
correlates with _importance_ (a fresh import author has counter 1), not with
_deadness_ — the heuristic behind the #9089/#9096 preserve-set bugs. Issue
#9105 tracks the root cause: client IDs are minted per install/profile and
retired almost never, so clocks only grow toward MAX. The decision on #9105
was to **park** the fix — post #9089/#9102 the worst case is the benign extra
round-trip of §5 — and record the agreed direction here.
If pruning stops being rare in practice, evict the **stalest** entries instead
of the lowest-counter ones. Unlike the coordinator options above, this fits
the dumb-relay model with no wire-format change:
- **Server:** the `sync_devices` table already stores `lastSeenAt` per
`(userId, clientId)`, updated on every upload — and uploads are the only
path that creates clock entries. A daily job already GCs rows unseen for
`retentionMs` (45 days), so absence from the registry reads as "stalest".
- **Client (all providers):** keep a small durable `clientId → last-merged-op
time` map, updated where remote clocks are merged (`mergeRemoteOpClocks`) —
every merged op carries its author's ID. Needs no server support, so it
covers WebDAV / LocalFile / Dropbox too.
The safety profile is identical to today's pruning (entries are dropped either
way; a dropped ID that returns costs at most the extra round-trip of §5), but
victim selection is strictly better: a recently-seen ID — e.g. a fresh import
author — survives by definition, making the preserve-set invariant of
#9089/#9096 _emergent_ instead of hand-maintained at each prune site (the
explicit preserve sets stay as belt-and-braces). Staleness knowledge differs
per node, so nodes may evict different victims; that adds clock asymmetry but
no new failure class — comparison treats missing keys as zero, and clients
already prune with differing preserve sets.
The supported GC today is a **full-state import**: the clock reset keeps only
`{import author, self}` (§7), and the once-per-session pruning snack points
users at it (sync all devices first — imports intentionally drop concurrent
ops, see `SyncImportFilterService`).
**Revisit trigger:** client pruning WARN-logs `prunedIds` / `survivingIds`
into the exportable log history. If prune warnings appear in real bug
reports — especially ones evicting _live_ IDs — promote this from parked to
scheduled.

View file

@ -119,15 +119,15 @@ export class SyncEffects {
this._snackService.open({
msg: T.F.SYNC.S.VECTOR_CLOCK_LIMIT_REACHED,
// CUSTOM (not WARNING): this is a benign, self-healing cleanup, not
// an alert the user must act on. Neutral icon, auto-dismiss — but a
// touch longer than the 3s default so the sentence is readable.
// an alert the user must act on. Neutral icon, auto-dismiss — long
// enough to read the optional clear-for-good remedy (#9105).
type: 'CUSTOM',
ico: 'sync',
translateParams: {
originalSize,
maxSize,
},
config: { duration: 5000 },
config: { duration: 8000 },
});
}),
),

View file

@ -1668,7 +1668,7 @@
"UNKNOWN_ERROR": "Unknown Sync Error: {{err}}",
"UPLOAD_ERROR": "Unknown Upload Error (Settings correct?): {{err}}",
"UPLOAD_OPS_REJECTED": "{{count}} operation(s) were permanently rejected by the server and won't be synced.",
"VECTOR_CLOCK_LIMIT_REACHED": "Sync tidied up old device entries ({{originalSize}} → {{maxSize}}). This is normal — no action needed.",
"VECTOR_CLOCK_LIMIT_REACHED": "Sync tidied up old device entries ({{originalSize}} → {{maxSize}}). This is normal. To clear them for good: sync all devices, then export and re-import a backup (Settings → Import/Export).",
"VERSION_TOO_OLD": "Your app version is too old for the synced data. Please update!",
"VERSION_UNSUPPORTED": "Cannot sync: some synced data is from an app version that is no longer supported. Sync is paused before it; updating the app on your other devices may help.",
"WEB_CRYPTO_NOT_AVAILABLE": "Encryption is not available on this device. On Android, encryption requires a secure context (HTTPS) which is not available. Please disable encryption in sync settings or sync from a desktop web browser."