Add customer_deletion_jobs table and APIs (issue #821) - #822
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #822 +/- ##
==========================================
+ Coverage 82.70% 83.15% +0.45%
==========================================
Files 88 89 +1
Lines 33083 33993 +910
==========================================
+ Hits 27361 28268 +907
- Misses 5722 5725 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
kimhanbeom
left a comment
There was a problem hiding this comment.
@octoaide
Adding the new customer deletion jobs CF may cause migrations from existing databases to fail because migration functions open the database using the latest MAP_NAMES.
The 0.42 → 0.43 path creates missing CFs through migrate_customer_specific_networks(). However, the 0.43 → 0.44, 0.44 → 0.45, and 0.45 → 0.46 paths use create_missing_column_families(false), so they cannot open an existing database that does not contain the new CF.
Please make the following changes:
- Use
create_missing_column_families(true)in the following migration functions that open the database with the latestMAP_NAMES:migrate_network_tags_to_customer_scoped()migrate_network_cf()migrate_event_fields()migrate_triage_policy_confidence()migrate_event_country_codes()
- Keep
create_if_missing(false)so migration still fails if the database itself does not exist. - Add tests that create existing
0.43,0.44, and0.45CF schemas without the new CF and verify that migration succeeds, creates the new CF, and updates the VERSION.
|
I analyzed the review feedback and made the requested fixes. Summary of what I changed and why:
I ran validation locally and pushed the changes:
These changes address the migration failure scenarios you described and follow the naming/host conventions you requested. Thanks for the thorough review — everything you pointed out has been implemented and pushed. |
03a30e8 to
7fa52a5
Compare
7fa52a5 to
fadcdc5
Compare
|
I rebased this PR onto the latest |
| let mut opts = rocksdb::Options::default(); | ||
| opts.create_if_missing(false); | ||
| opts.create_missing_column_families(false); | ||
| opts.create_missing_column_families(true); |
There was a problem hiding this comment.
I think adding a column family should be treated as a database format change, even though it is not a Rust API breaking change. Once the new binary creates the new CF, older binaries that do not list that CF will fail to open the DB, so this becomes a breaking change in terms of DB schema compatibility.
Could we model this as an explicit DB-format migration instead of relying on create_missing_column_families(true) in normal open/migration paths? In particular, it would be safer to keep migration DB opens strict with create_missing_column_families(false), use version-specific CF name lists for the old and new formats, and create only the new customer deletion jobs CF in the migration step that advances the DB format version.
| } | ||
|
|
||
| #[must_use] | ||
| #[allow(clippy::missing_panics_doc)] |
There was a problem hiding this comment.
This #[allow(clippy::missing_panics_doc)] does not seem necessary. I removed it locally and cargo clippy --bins --tests --all-features -- -D warnings still passes.
Could we remove this allow? If a panic is part of this public API's contract, it should be documented with a # Panics section instead of suppressing the lint. Otherwise, I think we should avoid adding the lint suppression.
|
|
||
| fn value(&self) -> Self::AsBytes<'_> { | ||
| super::serialize(&self.service_results) | ||
| .expect("customer deletion service results must be serializable") |
There was a problem hiding this comment.
Could we make this expect message explain the invariant that makes the panic impossible, rather than restating the operation being attempted?
Our panic policy allows expect only when we are certain it cannot panic. If this is relying on bincode serialization into an in-memory Vec<u8> being infallible for these derived Serialize types, it would be helpful to say that directly in the message. Otherwise, this should probably return an error through an API that can propagate serialization failure.
| pub(crate) fn customer_data_deletion_jobs(&self) -> Table<'_, CustomerDataDeletionJob> { | ||
| let inner = self.inner.as_ref().expect("database must be open"); | ||
| Table::<CustomerDataDeletionJob>::open(inner) | ||
| .expect("{CUSTOMER_DELETION_JOBS} table must be present") |
There was a problem hiding this comment.
Could we make this expect message explain why the table must be present? It also uses "{CUSTOMER_DELETION_JOBS}" as a literal string, so the actual CF name is not shown; expect(...) takes a plain &str, so string interpolation does not happen here.
Under our panic policy, expect should document the invariant that prevents the panic. Please either make the accessor return a Result, or update the message to explain the invariant, e.g. that StateDb is opened with the full CF list and this accessor is only used after a successful StateDb::open.
|
Thanks for the detailed reviews — I went through each point and made the changes. Summary of what I changed
Validation
All changes have been committed and pushed. Thanks again for catching these — the migration is now explicit, the lint suppression is removed, and the expect messages document the invariants as requested. |
| [package] | ||
| name = "review-database" | ||
| version = "0.46.0" | ||
| version = "0.47.0" |
There was a problem hiding this comment.
Could we use 0.47.0-alpha.1 for this PR instead of 0.47.0, and update the related migration metadata consistently as well?
This PR introduces a DB-format change before the stable 0.47.0 release. Following the migration-versioning policy documented in src/migration.rs, DB-format changes during the pre-release development window should be represented with an alpha.x version so that later schema changes before the stable release remain distinguishable.
In particular, please update Cargo.toml, COMPATIBLE_VERSION_REQ, the migration target version in migrate_data_dir, and the changelog DB-format references consistently to 0.47.0-alpha.1.
| "trusted user agents", | ||
| ]; | ||
|
|
||
| fn map_names_for_existing_format( |
There was a problem hiding this comment.
Could you clarify why map_names_for_existing_format is needed? Is it intended to support retrying after a partial 0.47 migration, where the new CF may already exist before VERSION is updated? Or, if that retry case is not the reason, shouldn't each migration functions use the version-specific CF list directly instead?
|
Thanks — I addressed both points from your review.
All changes have been committed and pushed. Thanks for the pointer on using an alpha version and for asking about the retry case — both are now handled and documented. |
|
Could you cross-check the migration changes, @kimhanbeom? The migration code changed during the review process. |
| let cf_opts = rocksdb::Options::default(); | ||
| db.create_cf("label database", &cf_opts) | ||
| .context("Failed to create 'label database' column family")?; | ||
| db.create_cf("triage exclusion reason", &cf_opts) |
There was a problem hiding this comment.
Could you move the creation of the triage exclusion reason column family into a dedicated migration step?
Adding this CF is a distinct 0.42 → 0.43 database-format change, based on the same rationale used for the explicit customer deletion jobs CF migration. Creating it inside migrate_rename_tidb_to_label_db() hides this intent and makes the Label DB rename responsible for an unrelated schema change.
Please make the following changes:
- Remove the
TRIAGE_EXCLUSION_REASONCF creation frommigrate_rename_tidb_to_label_db(). - Add
map_names_v0_43_without_triage_exclusion_reason(), mirroringmap_names_v0_42_without_account_policy(). It should represent the post-rename intermediate schema by returningMAP_NAMES_V0_43_TO_V0_46withoutTRIAGE_EXCLUSION_REASON. - Add
migrate_create_triage_exclusion_reason_cf(). It should:- set both
create_if_missing(false)andcreate_missing_column_families(false); - strictly open the database using the post-rename intermediate CF list; and
- create only the
TRIAGE_EXCLUSION_REASONCF using the existing constant rather than a duplicated string literal.
- set both
- Update the 0.42 → 0.43 migration order as follows:
// Step 1: Drop "account policy" column family if it exists (from 0.42) migrate_drop_account_policy(&db_path)?; // Step 2: Rename "TI database" to "label database" migrate_rename_tidb_to_label_db(&db_path)?; // Stpe 3: Create the "triage exclusion reason" column family migrate_create_triage_exclusion_reason_cf(&db_path)?; // Step 4: Migrate AllowNetwork and BlockNetwork to customer-specific format migrate_customer_specific_networks(&db_path)?;
migrate_customer_specific_networks() should remain behaviorally unchanged because the preceding step completes the MAP_NAMES_V0_43_TO_V0_46 CF schema before it opens the database.
Please also update the relevant migration tests to verify that the new CF is created by the dedicated migration step and that the resulting database opens strictly with the complete 0.43 CF list.
6071794 to
359065c
Compare
|
I reviewed the feedback and made the requested migration refactor. I’ve committed and pushed the changes. What I changed
Validation
This implements the separation you requested so the label-DB rename no longer hides an unrelated schema change. Thanks for the clear guidance — everything should now match the intended migration boundaries. |
sophie-cluml
left a comment
There was a problem hiding this comment.
Thanks to David's previous migration review comment, I took a closer look here and found a related gap: the 0.42 → 0.43 chain isn't idempotent across partial-retry states the way the later steps now are. Could you make the 0.42 -> 0.43 migration chain idempotent across partial retry states and add focused regression tests for those states?
Because VERSION is updated only after the full migration chain reaches the current compatible version, a process can stop after one of the 0.42 -> 0.43 sub-steps, or even after later migrations create the 0.47 customer-deletion-jobs CF, while the recorded version still remains 0.42.x. On retry, migrate_0_42_to_0_43() will run again.
Please cover at least these physical states with VERSION = 0.42.0:
account policyalready dropped,TI databasestill present.- Both
TI databaseandlabel databasepresent, to simulate interruption during the rename/copy step. label databasepresent andTI databaseabsent, buttriage exclusion reasonnot yet created.triage exclusion reasonalready created.- AllowNetwork/BlockNetwork already migrated to the customer-specific format.
- The DB has already reached the current CF set, including
customer deletion jobs, butVERSIONis still 0.42.0.
For each state, rerunning migrate_data_dir() should succeed, skip already-completed sub-steps safely, preserve existing data, and update both VERSION files to the current package version.
|
Thanks — I dug into the gap you pointed out and made the 0.42 → 0.43 chain idempotent across partial-retry states. What I changed
Tests added
Validation
Where
Thanks for catching this — the migration is now safe to re-run from the partial states you outlined. Let me know if you want me to walk through any specific test case or implementation detail. |
|
@octoaide, could you rebase please? |
3a1a08b to
1200298
Compare
|
I rebased this PR onto the latest |
| - **BREAKING**: Bumped the database format to `0.47.0`. The migration from | ||
| `0.46.x` creates the customer data deletion jobs column family explicitly; | ||
| migrations from older supported formats preserve their legacy column-family | ||
| sets while applying intermediate migrations before creating the new family. |
There was a problem hiding this comment.
| - **BREAKING**: Bumped the database format to `0.47.0`. The migration from | |
| `0.46.x` creates the customer data deletion jobs column family explicitly; | |
| migrations from older supported formats preserve their legacy column-family | |
| sets while applying intermediate migrations before creating the new family. |
Please remove the duplicate entry.
| pub requested_at: i64, | ||
| pub completed_at: Option<i64>, |
There was a problem hiding this comment.
I think it might be helpful to note the unit of the times, for downstream repos.
|
I updated this PR with the following additional changes:
|
| pub host_fqdns: Vec<String>, | ||
| pub status: CustomerDataDeletionStatus, | ||
| /// Stores the request time in nanoseconds since the Unix epoch (UTC). | ||
| pub requested_at: i64, |
There was a problem hiding this comment.
Could we clarify the intended semantics of requested_at? If this is the original request/creation time for the service result, update_service() should probably preserve it instead of overwriting it. If it is meant to track the most recent deletion request attempt, the current update behavior can make sense, but the field name/docs should make that explicit, e.g. last_requested_at or documentation saying it is updated on retries.
There was a problem hiding this comment.
The intended semantics of requested_at are to record the request time of the most recent deletion attempt. Therefore, overwriting it in update_service() is intentional when a deletion is retried. I kept the existing field name and updated its documentation to clarify that a retry replaces the previous value.
f995ca8 to
359a4eb
Compare
Enable creation of missing customer_deletion_jobs column family during migrations while preserving create_if_missing(false) on initial DB open. Add separate migration tests for 0.43, 0.44, and 0.45 schemas to verify CF creation and VERSION updates. Rename the CF label to "customer deletion jobs", adjust a sensor test hostname, and add the customer_data_deletion_job table and APIs.
Create a new DB migration (0.46→0.47) that adds the customer_deletion_jobs column family and bump the package/database format to 0.47.0. The migration uses strict, version-specific CF lists so older CF layouts cannot open the new format; test coverage verifies this behavior. Also remove an unnecessary Clippy suppression and rewrite expect messages to document invariants. Update the changelog accordingly.
Update compatibility range and migration metadata for the new package/db format (0.47.0-alpha.1). Update CHANGELOG references. Document that map_names_for_existing_format supports retries after a partial migration, and add a regression test that simulates an interrupted migration to guard against regressions.
Add a post-rename intermediate column-family (CF) list helper and move TRIAGE_EXCLUSION_REASON creation into a dedicated strict migration step. Update the 0.42-0.43 migration ordering and add strict schema assertions to the focused migration test to catch regressions. Leave the customer-specific networks migration unchanged.
Implement retry-safe migration logic in src/migration.rs. - Retry-safe column-family discovery, creation, and removal to allow operations to be re-run safely. - Resumable copy from TI database to label database that skips conflicting records instead of overwriting them. - Detection and skipping of already-migrated AllowNetwork and BlockNetwork records to avoid duplicate migrations. - Added regression tests covering six partial-migration states, verifying data preservation and VERSION updates. These changes allow interrupted or repeated migrations to complete without corrupting or duplicating data.
Store all Review host FQDNs in each customer deletion service result so retries can reuse the original targets after node data has been removed. Remove the duplicate `0.47.0` changelog entry to keep only the current `0.47.0-alpha.1` database format entry. Document deletion timestamps as UTC Unix epoch nanoseconds so downstream consumers can interpret them consistently.
Document that `requested_at` tracks the most recent deletion attempt and is replaced on retries.
359a4eb to
112fd0e
Compare
Summary
This PR implements persistent storage and APIs for customer-owned
data deletion jobs, addressing issue #821.
Closes #821
What changed
MAP_NAMES.
CustomerDataDeletionServiceResult, CustomerDataDeletionService, and
CustomerDataDeletionStatus (exported from lib.rs).
transactional APIs: get, add_service, update_service. These follow
the existing account table optimistic-transaction retry pattern to
avoid lost concurrent updates.
StateDb accessor.
host_fqdn (must be non-empty).
keys.
service identification rules, concurrency retries, and DB reopen
persistence.
Files changed (high level)
Why
REview must store deletion results from itself and connected
services (Sensor, SemiSupervised). This change provides a durable
store and safe concurrent update semantics so multiple services can
report results without overwriting each other.
Notes
optimistic transaction pattern; no new DB abstraction was added.
locally.