cauchy1988 opened a new pull request, #2411:
URL: https://github.com/apache/incubator-pegasus/pull/2411

   ## Problem (#1564)
   
   When `manual_compact.periodic.trigger_time` is set to a time of day that has
   already passed (e.g. `1:00` while it is now `14:00`), periodic manual
   compaction fires **immediately**. This is catastrophic for **duplication
   full-app duplicate**: the backup cluster creates a brand-new app and copies
   the primary's manual-compact envs, so the backup's RocksDB performs
   compaction before it is fully initialized → undefined behavior → **all nodes
   in the backup cluster crash**.
   
   Closes #1564.
   
   ## Root cause
   
   Each partition uses `_manual_compact_last_finish_time_ms`, initialized to 
`0`,
   as the **"never compacted"** sentinel. But `check_periodic_compact()` treats
   that `0` as a distant-past lower bound:
   
   ```cpp
   // before
   if (_manual_compact_last_finish_time_ms.load() < t_ms && t_ms < now)
       return true;
   //     ^ 0 < t_ms is always true  =>  reduces to "trigger time already 
passed"
   ```
   
   The new-DB path (which is exactly what a duplication backup app is — a 
freshly
   created app) never calls `init_last_finish_time_ms`, so `last_finish` stays 
`0`.
   `check_manual_compact_state()` also sees `== 0` and lets it through. So an
   already-passed trigger retro-fires at the very start of the backup app's life
   (the check even runs from `update_app_envs_before_open_db`, i.e. before the 
DB
   is fully open), before RocksDB is ready.
   
   ## Fix
   
   Keep the `last_finish == 0` "never compacted" sentinel intact, and use the
   **service start time** as the trigger-time lower bound only when
   `last_finish == 0`:
   
   ```cpp
   // after
   uint64_t last_finish = _manual_compact_last_finish_time_ms.load();
   uint64_t lower_bound = last_finish == 0 ? _app_start_time_ms.load() : 
last_finish;
   for (auto t : trigger_time) {
       auto t_ms = t * 1000;
       if (lower_bound < t_ms && t_ms < now)
           return true;
   }
   ```
   
   `_app_start_time_ms` is a new member fixed at construction time 
(`dsn_now_ms()`).
   
   Why this design:
   
   - **Preserves the `0` sentinel.** `check_manual_compact_state()`'s 
min-interval
     branch and `query_compact_state()`'s display both rely on `last_finish == 
0`
     meaning "never compacted". We do not touch 
`_manual_compact_last_finish_time_ms`.
   - **One mechanism, both DB paths.** The floor keys off `last_finish == 0` at
     check time, so it covers both the new-DB path (no `init` call) and the
     existing-but-never-compacted path (`init(0)`). No change to
     `init_last_finish_time_ms` is needed.
   - **No deadlock.** The floor is the fixed start time, not a dynamic `now`, so
     the first periodic trigger after startup (e.g. start 14:00, trigger 18:00)
     still fires normally once `now` passes it.
   - **Timing-safe.** The periodic check only runs after the app is open, so a
     trigger within `(start, now)` fires on an already-initialized RocksDB; only
     triggers that already passed *before* startup (the retro-fire) are 
suppressed.
   
   Apps that have already been compacted keep using `last_finish`, so the
   existing "catch up on a missed daily trigger after a restart" behavior is
   unchanged.
   
   ## Test
   
   New regression test `check_periodic_compact_never_compacted` mocks the
   never-compacted state (`last_finish == 0`, app start `14:00`):
   
   - trigger `1:00` (already passed before startup) → must **not** fire
   - trigger `18:00` not yet reached (`now == 14:00`) → must **not** fire
   - advance `now` to `19:00` → `18:00` now lies in `(start, now)` → **fires**
     (proves no deadlock)
   
   Forward run: all `manual_compact_service_test.*` pass (14 cases).
   Reverse check (temporarily neutering the floor): the new case fails as
   expected, confirming the test actually catches the bug.
   
   ## Scope / notes
   
   - `check_once_compact()` has analogous behavior when `last_finish == 0`, but
     #1564 is specifically about `periodic.trigger_time`; the once-trigger is 
out
     of scope here.
   - Residual narrow window: if a backup app starts *before* a trigger time and
     the check runs after that time passes but before duplication is ready, it
     could still fire. Fully closing that requires duplication-readiness
     awareness, which lives in the replica layer (out of scope for this fix).
     This PR fixes the primary scenario — crash on startup.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to