fix: Analyze and simply handle consistency issues between the database and S3.#1952
Conversation
|
Claude finished @pleasedontbreak123's task in 3m 25s —— View job Code Review: fix: Analyze and simply handle consistency issues between the database and S3
SummaryThis PR addresses DB/S3 consistency issues through three main mechanisms: error type classification, write-order reordering (S3 first), and a migration utility. The design intent is sound, but several correctness bugs and logical gaps need attention before merging. Bugs / Correctness Issues1.
|
| # | Severity | Location | Issue |
|---|---|---|---|
| 1 | High | mono_service.rs:72-79 |
init_monorepo still writes DB before S3 |
| 2 | Medium | storage/mod.rs:311-318 |
Dead/unreachable else branch in classifier |
| 3 | Medium | api_router.rs:80 |
ObjStorageInconsistent mapped to 404 instead of 500 |
| 4 | Medium | adapter.rs:700-719 |
AlreadyExists should be treated as success in put_idempotent |
| 5 | Low | storage/mod.rs:305-322 |
Formatting bugs in error messages and docstring mismatch |
| 6 | Low | storage/mod.rs:304 |
Data-loss case logged at warn instead of error |
| 7 | Low | migrate_local_to_s3.rs:197 |
All task handles held in memory; may be a concern for large stores |
| 8 | Low | utils.rs:18 |
No tests for is_full_hex_object_id |
| 9 | Low | storage/mod.rs:282 |
No tests for classify_blob_objstorage_not_found |
|
Claude finished @pleasedontbreak123's task in 3m 17s —— View job Code Review: fix: Analyze and simply handle consistency issues between the database and S3
SummaryThis PR introduces three pillars: error classification for DB/S3 inconsistencies, write-order reordering (S3-first), and a migration utility. The design direction is sound. Below are the outstanding issues found in the current code, including several correctness bugs that still need attention. Bugs / Correctness Issues1.
|
| # | Severity | Location | Issue |
|---|---|---|---|
| 1 | High | mono_service.rs:72-80 |
init_monorepo still writes DB before S3 |
| 2 | Medium | storage/mod.rs:311-318 |
Unreachable else branch in classifier |
| 3 | Medium | api_router.rs:80 + error.rs |
ObjStorageInconsistent → 404; ObjStorageNotFound status depends on brittle hardcoding |
| 4 | Medium | adapter.rs:710-717 |
AlreadyExists in put_idempotent should be treated as success |
| 5 | Low | storage/mod.rs:304 |
Data-loss case logged at warn instead of error |
| 6 | Low | storage/mod.rs:305-322, errors.rs:72 |
Message formatting bugs and docstring mismatch |
| 7 | Low | migrate_local_to_s3.rs:197 |
All task handles held in memory; unbounded for large stores |
| 8 | Low | utils.rs:18 |
No tests for is_full_hex_object_id |
| 9 | Low | storage/mod.rs:282 |
No tests for classify_blob_objstorage_not_found |
The high and medium issues should be resolved before merging. Issues 1, 3, and 4 can cause incorrect behavior under normal operating conditions.
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive fix for consistency issues between the database and S3 object storage. The changes focus on three main areas: error classification for object storage errors, proper write ordering (S3 first, then DB) to prevent data inconsistency, and input validation for Git object IDs. Additionally, it provides a migration tool for moving objects from local storage to S3.
Changes:
- Introduced new error variants (
ObjStorageNotFound,ObjStorageInconsistent) to distinguish between different object storage failure modes and added classification logic to provide better diagnostics - Changed blob write order to write to S3 before DB, preventing orphaned database records when S3 writes fail; added idempotent writes for Git objects using
PutMode::Create - Added input validation for Git object IDs (40/64 hex chars) to prevent invalid requests from reaching storage backends, and replaced panics with proper error returns in tree/commit lookup methods
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| common/src/errors.rs | Added ObjStorageNotFound and ObjStorageInconsistent error variants with documentation; updated From<MegaError> for GitError to preserve HTTP 404 semantics |
| common/src/utils.rs | Added is_full_hex_object_id validation function for SHA-1/SHA-256 object IDs |
| io-orbit/src/error.rs | Enhanced error mapping to convert object_store::Error::NotFound to MegaError::ObjStorageNotFound |
| io-orbit/src/adapter.rs | Implemented put_idempotent using PutMode::Create for Git namespace objects to ensure write idempotency |
| io-orbit/src/object_storage.rs | Enhanced error context in put_many to include object key and namespace information |
| io-orbit/src/bin/migrate_local_to_s3.rs | Complete offline migration tool from local filesystem to S3, with idempotency, multipart uploads, and proper error handling |
| io-orbit/Cargo.toml | Added required dependencies for migration tool (tracing-subscriber, tokio features) |
| jupiter/src/storage/mod.rs | Implemented classify_blob_objstorage_not_found helper to distinguish DB/S3 inconsistency from legitimate missing objects |
| jupiter/src/service/mono_service.rs | Reversed blob write order: now writes to S3 first, then DB, to prevent data inconsistency |
| jupiter/src/service/git_service.rs | Added object ID validation in get_object_as_bytes and get_objects_stream to reject malformed IDs early |
| mono/src/api/error.rs | Added explicit handling for MegaError::NotFound to return 404 responses |
| mono/src/api/api_router.rs | Improved error handling in get_blob_file to use ApiError::not_found |
| ceres/src/api_service/mono_api_service.rs | Replaced panics with proper MegaError::NotFound returns in get_tree_by_hash and get_commit_by_hash |
| ceres/src/api_service/mod.rs | Added error classification wrapper in get_raw_blob_by_hash for better diagnostics |
| ceres/src/api_service/commit_ops.rs | Updated to use get_raw_blob_by_hash for consistent error handling and validation |
| ceres/src/api_service/admin_ops.rs | Added error classification for blob reads to improve diagnostics |
| ceres/src/lfs/handler.rs | Fixed invalid Duration::from_hours(1) to correct Duration::from_secs(3600) |
Comments suppressed due to low confidence (1)
mono/src/api/error.rs:156
- The new error variants MegaError::ObjStorageNotFound and MegaError::ObjStorageInconsistent should be explicitly handled in the ApiError conversion. ObjStorageNotFound should likely map to a 404 response, and ObjStorageInconsistent should map to a 500 response with appropriate logging since it indicates a data consistency issue.
match mega_err {
MegaError::NotFound(_) => return ApiError::not_found(anyhow_err),
MegaError::Db(_) | MegaError::Redis(_) | MegaError::Io(_) => {
// Hide internal details in production, return generic 500
tracing::error!(
error_type = %match mega_err {
MegaError::Db(_) => "Db",
MegaError::Redis(_) => "Redis",
MegaError::Io(_) => "Io",
_ => "Other",
},
"Internal error occurred"
);
tracing::debug!("Internal error: {:?}", mega_err);
return ApiError::internal(anyhow::anyhow!("Internal server error"));
}
// For other MegaError variants, fall through to parse [code:xxx] format
_ => {}
}
| let is_valid_len = oid.len() == 40 || oid.len() == 64; | ||
| is_valid_len && oid.as_bytes().iter().all(|b| b.is_ascii_hexdigit()) | ||
| } | ||
|
|
There was a problem hiding this comment.
The new is_full_hex_object_id function lacks test coverage. Given that this function is used for input validation in critical paths (git_service.rs lines 87 and 112), tests should be added to verify behavior with: valid 40-char SHA-1 hashes, valid 64-char SHA-256 hashes, invalid lengths, non-hex characters, empty strings, and short hashes. This follows the repository's testing pattern seen in common/src/utils.rs where other utility functions have comprehensive tests.
| #[cfg(test)] | |
| mod tests { | |
| use super::is_full_hex_object_id; | |
| #[test] | |
| fn full_sha1_hex_is_accepted() { | |
| // 40 hex characters | |
| let oid = "0123456789abcdef0123456789abcdef01234567"; | |
| assert_eq!(oid.len(), 40); | |
| assert!(is_full_hex_object_id(oid)); | |
| } | |
| #[test] | |
| fn full_sha256_hex_is_accepted() { | |
| // 64 hex characters | |
| let oid = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; | |
| assert_eq!(oid.len(), 64); | |
| assert!(is_full_hex_object_id(oid)); | |
| } | |
| #[test] | |
| fn invalid_lengths_are_rejected() { | |
| // 39, 41, 63, and 65 characters should all be rejected | |
| let too_short_sha1 = "0123456789abcdef0123456789abcdef0123456"; | |
| let too_long_sha1 = "0123456789abcdef0123456789abcdef012345678"; | |
| let too_short_sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde"; | |
| let too_long_sha256 = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0"; | |
| assert_eq!(too_short_sha1.len(), 39); | |
| assert_eq!(too_long_sha1.len(), 41); | |
| assert_eq!(too_short_sha256.len(), 63); | |
| assert_eq!(too_long_sha256.len(), 65); | |
| assert!(!is_full_hex_object_id(too_short_sha1)); | |
| assert!(!is_full_hex_object_id(too_long_sha1)); | |
| assert!(!is_full_hex_object_id(too_short_sha256)); | |
| assert!(!is_full_hex_object_id(too_long_sha256)); | |
| } | |
| #[test] | |
| fn non_hex_characters_are_rejected() { | |
| let with_g = "g123456789abcdef0123456789abcdef01234567"; | |
| let with_dash = "0123456789abcdef0123456789abcdef01234-67"; | |
| let with_z = "z123456789abcdef0123456789abcdef01234567"; | |
| assert!(!is_full_hex_object_id(with_g)); | |
| assert!(!is_full_hex_object_id(with_dash)); | |
| assert!(!is_full_hex_object_id(with_z)); | |
| } | |
| #[test] | |
| fn empty_string_is_rejected() { | |
| assert!(!is_full_hex_object_id("")); | |
| } | |
| #[test] | |
| fn_short_hashes_are_rejected() { | |
| let short7 = "abcdef0"; | |
| let short10 = "0123456789"; | |
| assert_eq!(short7.len(), 7); | |
| assert_eq!(short10.len(), 10); | |
| assert!(!is_full_hex_object_id(short7)); | |
| assert!(!is_full_hex_object_id(short10)); | |
| } | |
| } |
| #[error("ObjStorage not found: {0}")] | ||
| ObjStorageNotFound(String), | ||
|
|
||
| ///Object not found in underlying object storage (S3/GCS/local). but exists in the repository. |
There was a problem hiding this comment.
Missing space after "///" in the comment. Should be "/// Object not found in underlying object storage (S3/GCS/local), but exists in the repository."
| ///Object not found in underlying object storage (S3/GCS/local). but exists in the repository. | |
| /// Object not found in underlying object storage (S3/GCS/local), but exists in the repository. |
|
Claude finished @pleasedontbreak123's task in 3m 33s —— View job Code Review: fix: Analyze and simply handle consistency issues between the database and S3
SummaryThis third pass reviews the current state of the code after the latest "fix Leftover" commit. The PR has addressed most of the issues raised in earlier reviews. Here is a status update and the remaining issues. Fixed Since Previous Reviews ✅
Remaining Issues1.
|
| # | Severity | Location | Status |
|---|---|---|---|
| 1 | Medium | adapter.rs:722-729 |
AlreadyExists still propagates as error in put_idempotent |
| 2 | Low | git_service.rs:88 |
Invalid ID returns 500 instead of 400 |
| 3 | Low | errors.rs:72 |
Missing space + sentence casing in doc comment |
| 4 | Low | errors.rs:101 |
ObjStorageInconsistent not explicitly tagged with [code:5xx] in git error conversion |
| 5 | Low | storage/mod.rs:282 |
No unit tests for classify_blob_objstorage_not_found |
The PR is in good shape — the high and most medium issues from previous reviews are resolved. Issue 1 (put_idempotent/AlreadyExists) is the last medium-severity item worth addressing before merge.
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
|
Claude finished @pleasedontbreak123's task in 1m 55s —— View job Code Review: fix: Analyze and simply handle consistency issues between the database and S3
Fourth Pass — Status UpdateThis pass reviews the latest commit ( Fixed Since Third Review ✅
The fix is clean and correct ( match self.to_store().put_opts(...).await {
Ok(_) => Ok(()),
Err(object_store::Error::AlreadyExists { .. }) => Ok(()),
Err(e) => Err(IoOrbitError::from(e).into()),
}Remaining IssuesAll remaining items are low-severity. The PR is in good shape for merge if the team accepts these minor outstanding items. 1. Invalid object ID returns 500 instead of 400 (Low)
if !is_full_hex_object_id(hash) {
return Err(MegaError::Other("Invalid object ID format".to_string()));
}
2.
|
| # | Severity | Location | Status |
|---|---|---|---|
| 1 | Low | git_service.rs:88 |
Invalid object ID still returns 500 instead of 400 |
| 2 | Low | errors.rs:101 |
ObjStorageInconsistent not explicitly tagged with [code:500] in git error conversion |
| 3 | Low | errors.rs:72 |
Missing space + incorrect description in doc comment |
| 4 | Low | storage/mod.rs:282 |
No unit tests for classify_blob_objstorage_not_found |
The medium-severity AlreadyExists issue is now resolved. All remaining items are low-severity. The PR is functionally correct and ready for merge at the maintainer's discretion — the items above are improvements for correctness and maintainability rather than blocking bugs.
f911d74 to
2803fa8
Compare
|
Claude finished @pleasedontbreak123's task in 2m 24s —— View job Code Review: fix: Analyze and simply handle consistency issues between the database and S3
Fifth Pass — Status UpdateThe most recent push ( Fixed Since Fourth Review ✅
The fix is clean and correct: match self.to_store().put_opts(...).await {
Ok(_) => Ok(()),
Err(object_store::Error::AlreadyExists { .. }) => Ok(()),
Err(e) => Err(IoOrbitError::from(e).into()),
}Remaining Issues (all Low severity)The PR is functionally correct. The items below are quality/robustness improvements. 1. Invalid object ID returns 500 instead of 400 (Low)
if !is_full_hex_object_id(hash) {
return Err(MegaError::Other("Invalid object ID format".to_string()));
}
2.
|
| # | Severity | Location | Status |
|---|---|---|---|
| 1 | Low | git_service.rs:88 |
Invalid object ID returns 500 instead of 400 |
| 2 | Low | errors.rs:101 |
ObjStorageInconsistent not explicitly tagged with [code:500] in git error conversion |
| 3 | Low | errors.rs:72 |
Missing space + incorrect direction in doc comment |
| 4 | Low | storage/mod.rs:288 |
No unit tests for classify_blob_objstorage_not_found |
All high and medium issues from previous review passes are resolved. The PR is functionally correct — the remaining items are code quality improvements rather than blocking bugs. Ready for merge at the maintainer's discretion.
1.错误分类
若 DB 和 S3 都不存在该 blob:日志记录 “not found in both object storage and metadata”,并返回带友好信息的 ObjStorageNotFound。
若 DB 有记录但 S3 缺失:标记为 ObjStorageInconsistent 并打 ERROR 日志。
2.blob 读写一致性
3.api调整
#1894