refactor(usage_reconcile): explicit naming to prevent confusion with storage (backend)

This commit is contained in:
Edouard Vanbelle
2026-08-02 13:36:20 +02:00
parent db5ae1cc90
commit 9902a6f8fe
12 changed files with 42 additions and 42 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ operator benefit.
| Job name | Cadence | Force semantic | Service |
|---|---|---|---|
| `trash_cleanup` | 24 h (hardcoded in DI, no env var yet) | ignored | [`trash_cleanup_service.rs`](../../src/infrastructure/services/trash_cleanup_service.rs) |
| `storage_reconcile`| `OXICLOUD_STORAGE_USAGE_RECONCILE_SECS` (default 600s, min 30s) | ignored | [`storage_usage_service.rs`](../../src/application/services/storage_usage_service.rs) |
| `usage_reconcile` | `OXICLOUD_STORAGE_USAGE_RECONCILE_SECS` (default 600s, min 30s) | ignored | [`storage_usage_service.rs`](../../src/application/services/storage_usage_service.rs) |
| `dedup_gc` | on-demand only (trash cleanup runs it inline as its tail step) | `force=true` → `garbage_collect_force()` (skip orphan grace) | [`dedup_service.rs`](../../src/infrastructure/services/dedup_service.rs) |
| `grant_cleanup` | `OXICLOUD_GRANT_CLEANUP_INTERVAL_HOURS` (default 24h) — feature-gated by `OXICLOUD_GRANT_CLEANUP_ENABLED` | `force=true` → `purge(Some(0))` (grace_days=0) | [`grant_cleanup_service.rs`](../../src/infrastructure/services/grant_cleanup_service.rs) |
+2 -2
View File
@@ -787,7 +787,7 @@ reference for any external tool that still expects the old paths:
| Legacy (retired) | Replacement |
|---|---|
| `POST /admin/internal/trigger-sweep` | `POST /admin/jobs/storage_reconcile/trigger` |
| `POST /admin/internal/trigger-sweep` | `POST /admin/jobs/usage_reconcile/trigger` |
| `POST /admin/internal/trigger-gc?force=X` | `POST /admin/jobs/dedup_gc/trigger?force=X` |
| `POST /admin/internal/trigger-grant-cleanup?force=X` | `POST /admin/jobs/grant_cleanup/trigger?force=X` |
@@ -816,7 +816,7 @@ complete. Rough shape:
│ Name Cadence Last run Status Actions │
│ ───────────────────────────────────────────────────────────────────│
│ trash_cleanup every 24 h 3h ago ok [Run] │
│ storage_reconcile every 10 m 4m ago ok [Run] │
│ usage_reconcile every 10 m 4m ago ok [Run] │
│ dedup_gc on-demand 1d ago ok [Run] │
│ grant_cleanup every 24 h never — [Run] │
│ drives_consistency on-demand never — [Run] │
@@ -65,7 +65,7 @@ impl StorageUsageService {
/// `GET /api/drives` therefore lags by up to the cache TTL (30 s),
/// which matches the sibling caches' accepted UX phantom for
/// drive-name staleness. Tests / operators that need immediate
/// freshness call `POST /api/admin/jobs/storage_reconcile/trigger`,
/// freshness call `POST /api/admin/jobs/usage_reconcile/trigger`,
/// which runs `update_all_drives_storage_usage` → this method.
///
/// Security posture unaffected: `check_drive_quota` reads
@@ -576,7 +576,7 @@ impl StorageUsageService {
}
}
pub const STORAGE_RECONCILE_JOB_NAME: &str = "storage_reconcile";
pub const USAGE_RECONCILE_JOB_NAME: &str = "usage_reconcile";
use crate::infrastructure::scheduler::{JobHandler, JobOutcome, JobRegistry, JobRunArgs};
use async_trait::async_trait;
@@ -600,7 +600,7 @@ impl StorageUsageService {
#[async_trait]
impl JobHandler for StorageUsageService {
fn name(&self) -> &str {
STORAGE_RECONCILE_JOB_NAME
USAGE_RECONCILE_JOB_NAME
}
/// Runs both reconciliation sweeps — drives first, then users —
+1 -1
View File
@@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize};
/// Semantics of `force`, per job:
/// - `dedup_gc` — skip the orphan grace window (grace = 0).
/// - `grant_cleanup` — grace = 0.
/// - Others (trash_cleanup, storage_reconcile, …) — ignored.
/// - Others (trash_cleanup, usage_reconcile, …) — ignored.
///
/// Semantics of `deep`, per job:
/// - `consistency_batch` — propagate to sub-jobs; only `storage_consistency`
@@ -6,7 +6,7 @@
//! writes never decrement on `move_to_trash`) and the sweep at
//! `storage_usage_service.rs::update_all_drives_storage_usage`.
//! **Read-only** — reports drift as findings but does
//! NOT fix it. The existing `storage_reconcile` job (Part 1) is what
//! NOT fix it. The existing `usage_reconcile` job (Part 1) is what
//! corrects the counter; this check surfaces WHEN drift happens so
//! operators can trace it back to root cause (missed delta call,
//! delta failed silently, race, etc.).
@@ -145,7 +145,7 @@ impl RecoverableJobHandler for DrivesConsistencyCheck {
// Fetch next batch of drives + their actual SUM in one
// query. LEFT JOIN via correlated subquery gets us both
// sides in one round-trip; the storage_reconcile sweep
// sides in one round-trip; the usage_reconcile sweep
// uses the same shape.
// Grace window: skip drives created within the last hour.
// A drive being created RIGHT NOW may still have its first
+1 -1
View File
@@ -2335,7 +2335,7 @@ pub async fn list_jobs(State(state): State<Arc<AppState>>) -> impl IntoResponse
/// `force=true` requests acceleration semantics from handlers that
/// support it (dedup_gc → grace = 0, grant_cleanup → grace = 0).
/// Silently ignored by handlers that don't (trash_cleanup,
/// storage_reconcile).
/// usage_reconcile).
///
/// `deep=true` opts into slow variants — `consistency_batch` fans it
/// out to sub-jobs; `storage_consistency` (when implemented) will
+4 -4
View File
@@ -7,7 +7,7 @@
#
# Coverage:
# * Listing returns the four registered tenants
# (trash_cleanup, storage_reconcile, dedup_gc, grant_cleanup).
# (trash_cleanup, usage_reconcile, dedup_gc, grant_cleanup).
# * Scheduled jobs report `interval_ms`; on-demand jobs
# (`dedup_gc`) omit it via `skip_serializing_if=None`.
# * Triggering a job updates its `last_outcome` in the next list.
@@ -52,7 +52,7 @@ bob_token: jsonpath "$.access_token"
# ─────────────────────────────────────────────────────────────
# Step 1 — Admin lists jobs. All four Part 1 tenants must appear.
# Scheduled jobs (trash_cleanup, storage_reconcile, grant_cleanup)
# Scheduled jobs (trash_cleanup, usage_reconcile, grant_cleanup)
# report `interval_ms`; on-demand jobs (`dedup_gc`) omit it via
# serde's `skip_serializing_if = "Option::is_none"`.
#
@@ -69,7 +69,7 @@ HTTP 200
[Asserts]
# Names — the four scheduler tenants.
jsonpath "$[*].name" contains "trash_cleanup"
jsonpath "$[*].name" contains "storage_reconcile"
jsonpath "$[*].name" contains "usage_reconcile"
jsonpath "$[*].name" contains "dedup_gc"
jsonpath "$[*].name" contains "grant_cleanup"
@@ -90,7 +90,7 @@ jsonpath "$..interval_ms" count == 3
# Every entry carries a `running` bool — same aggregate primitive.
# Count matches the registered-tenant count: 4 Part 1 periodics
# (trash_cleanup, storage_reconcile, dedup_gc, grant_cleanup) + 5
# (trash_cleanup, usage_reconcile, dedup_gc, grant_cleanup) + 5
# Part 2 recoverables (drives_consistency, folders_consistency,
# files_consistency, blobs_consistency, backend_consistency —
# wrapped by RecoverableAdapter so they appear here alongside the
+5 -5
View File
@@ -30,7 +30,7 @@
# same `v_dest_drive_id` variable, so (a) passing implies
# file rows used the same value and (b) cross-checks it.
#
# Sweep convergence: `/api/admin/jobs/storage_reconcile/trigger` is the
# Sweep convergence: `/api/admin/jobs/usage_reconcile/trigger` is the
# deterministic synchronisation point — without it the
# fire-and-forget delta hook may not yet have updated the cached
# `used_bytes` when we read it.
@@ -136,7 +136,7 @@ file_id: jsonpath "$.id"
# numbers, the late hook adds its delta on top, and used_bytes ends
# up high by exactly one file's size. Symptom: expected 32, got 64.
# Real fix is await'ing the hook inline server-side.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -171,7 +171,7 @@ HTTP 200
[Captures]
shared_file_id: jsonpath "$.successful[0].id"
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -259,7 +259,7 @@ HTTP 201
# the file's size into the cached counter. 200 ms is well above
# the tokio task latency on any reasonable box; the deterministic
# fix would be intra-transaction hooks, deferred until D7.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -348,7 +348,7 @@ jsonpath "$.name" == "dc-subtree-inner"
# the Step 6 file copy (32) = 96. Anything other than (96, 96)
# would mean the file INSERT in copy_folder_tree used the wrong
# drive_id.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
+5 -5
View File
@@ -17,7 +17,7 @@
# a file inside and watching the destination drive's
# `used_bytes` jump by the descendant's size (not 0).
#
# Sweep convergence: `/api/admin/jobs/storage_reconcile/trigger` is the
# Sweep convergence: `/api/admin/jobs/usage_reconcile/trigger` is the
# deterministic synchronisation point — it recomputes every
# drive's cached `used_bytes` from `SUM(file.size) WHERE
# drive_id = d.id`. If the file/folder move didn't update
@@ -125,7 +125,7 @@ file_id: jsonpath "$.id"
# Baseline used_bytes after the upload settles. Trigger-sweep is
# the deterministic sync point — but only after the spawn'd hook
# has had a chance to land (bug_trigger_sweep_vs_spawn_hook_race.md).
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -161,7 +161,7 @@ Content-Type: application/json
HTTP 200
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -245,7 +245,7 @@ nested_file_id: jsonpath "$.id"
# size. Symptom: expected 64, got 96 (one extra hook landed late).
# Real fix is await'ing the hook inline server-side; until then this
# delay deflakes the test.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -278,7 +278,7 @@ HTTP 200
# shared: nested hello-copy.txt now charged here (32)
# Anything other than (32, 32) means the descendant file's
# drive_id wasn't cascaded by the trigger.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
+10 -10
View File
@@ -120,7 +120,7 @@ small_file_id: jsonpath "$.id"
# Ed's 2026-07-17 design call: the sweep is the escape hatch
# for tests / operators that need immediate cache freshness;
# per-write invalidation would nuke the cache on every upload.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -157,7 +157,7 @@ HTTP 201
# `used_bytes` climbs to 64 (32 + 32). Same trigger-sweep pattern
# as the first assertion — the delta is fire-and-forget and the
# listing cache lags until the sweep invalidates it.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -194,7 +194,7 @@ HTTP 507
# consumed by the intervening GET which re-populated the cache
# with the pre-refused-write value. Sweep + re-check for
# determinism.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
HTTP 200
@@ -238,7 +238,7 @@ HTTP 201
# Unlimited drive's `used_bytes` climbs to the file's exact size
# (5 MiB = 5_242_880 bytes). Trigger-sweep pattern (see above).
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -266,7 +266,7 @@ jsonpath "$[?(@.id=='{{unlimited_drive_id}}')].used_bytes" == 5242880
# tight drive; the 5 MiB is in the unlimited one).
# b) Permanently delete via empty-trash.
# c) Trigger the reconciliation sweep on demand —
# `POST /api/admin/jobs/storage_reconcile/trigger`.
# `POST /api/admin/jobs/usage_reconcile/trigger`.
# d) `GET /api/drives` now shows the corrected counter.
# ─────────────────────────────────────────────────────────────
DELETE {{base_url}}/api/files/{{small_file_id}}
@@ -284,7 +284,7 @@ HTTP 200
# Sweep is fire-and-forget on a ticker (default 600 s). Run it now
# so the assertion below is deterministic instead of polling.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
HTTP 200
@@ -310,7 +310,7 @@ jsonpath "$[?(@.id=='{{unlimited_drive_id}}')].used_bytes" == 5242880
# the route in production configs; here we just confirm a
# non-admin caller is refused even when the feature is on.
# ─────────────────────────────────────────────────────────────
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{owner_token}}
HTTP 403
@@ -415,7 +415,7 @@ HTTP 200
# operations above never wrote anything. Trigger-sweep so the
# check reads live SQL (see the class doc on the earlier
# sweep + GET pair for the design rationale).
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
HTTP 200
@@ -570,7 +570,7 @@ HTTP 201
soft_shrink_file_id: jsonpath "$.id"
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -654,7 +654,7 @@ Authorization: Bearer {{owner_token}}
HTTP 200
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
HTTP 200
+3 -3
View File
@@ -268,7 +268,7 @@ log "API confirms trash is empty."
# disk state to be quiescent NOW. The two JobRegistry admin triggers
# below (production surface, always on) make this deterministic:
#
# 1. storage_reconcile — reconciles users.storage_used_bytes and
# 1. usage_reconcile — reconciles users.storage_used_bytes and
# drives.used_bytes from SUM(size) — keeps
# the cached counters honest for any quota
# assertions that follow.
@@ -280,8 +280,8 @@ log "API confirms trash is empty."
# row-delete → unlink window the grace
# normally protects.
curl -sf -X POST -H "$AUTH" "$base_url/api/admin/jobs/storage_reconcile/trigger" >/dev/null \
|| fail "storage_reconcile trigger failed"
curl -sf -X POST -H "$AUTH" "$base_url/api/admin/jobs/usage_reconcile/trigger" >/dev/null \
|| fail "usage_reconcile trigger failed"
log "Reconciliation sweep triggered."
GC_RESULT=$(curl -sf -X POST -H "$AUTH" "$base_url/api/admin/jobs/dedup_gc/trigger?force=true")
+5 -5
View File
@@ -19,7 +19,7 @@
# 4. Sweep self-heals — after trashing the personal file and
# `trigger-sweep`, `/me.storage_used_bytes` returns to 0.
#
# `POST /api/admin/jobs/storage_reconcile/trigger` is the
# `POST /api/admin/jobs/usage_reconcile/trigger` is the
# deterministic synchronisation point: it runs the drive-side sweep
# then the user-side sweep (both under the periodic scheduler), so
# both cached counters are authoritative ground-truth by the time
@@ -139,7 +139,7 @@ HTTP 201
# acts as the synchronisation point for the user-envelope
# assertion below — the sweep is the authoritative
# ground-truth for both drive- and user-side counters.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
[Options]
delay: 200ms
@@ -159,7 +159,7 @@ jsonpath "$[?(@.id=='{{shared_drive_id}}')].used_bytes" == 32
# If the delta path incorrectly fired the user counter, the sweep
# would still correct it back to 0 (the new SQL excludes shared
# drives) — this also validates the sweep formula.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
HTTP 200
@@ -207,7 +207,7 @@ jsonpath "$.storage_used_bytes" == 32
# Confirm the sweep agrees with the delta — both code paths must
# give the same number.
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
HTTP 200
@@ -237,7 +237,7 @@ Authorization: Bearer {{owner_token}}
HTTP 200
POST {{base_url}}/api/admin/jobs/storage_reconcile/trigger
POST {{base_url}}/api/admin/jobs/usage_reconcile/trigger
Authorization: Bearer {{admin_token}}
HTTP 200