enhance: define storage error taxonomy and preserve diagnostics - #603
enhance: define storage error taxonomy and preserve diagnostics#603xiaofan-luan wants to merge 1 commit into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #603 +/- ##
==========================================
+ Coverage 75.98% 76.27% +0.29%
==========================================
Files 169 169
Lines 16937 16504 -433
Branches 2535 2473 -62
==========================================
- Hits 12870 12589 -281
+ Misses 4067 3915 -152
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| // LoonFFIResult and a consumer can classify a code it has never seen. | ||
| // =========================================================================== | ||
| #define LOON_ERROR_CATEGORY_UNKNOWN 0 | ||
| #define LOON_ERROR_CATEGORY_USER 1 |
There was a problem hiding this comment.
For milvus-storage, the upper layers are segcore and some Go code that calls it through FFI. Is the purpose of classifying errors as User here to consistently report configuration errors to Milvus users?
If so, at the storage layer we cannot actually distinguish whether an error was caused by invalid user input or by an internal Milvus error.
For example, LOON_INVALID_PROPERTIES is not necessarily caused by properties set by a Milvus user; most of these properties are hard-coded in segcore. Similarly, LOON_INVALID_ARGS could also be caused by segcore calling the API incorrectly, in which case it would be an internal logic error.
There was a problem hiding this comment.
You're right, and this is the sharpest problem with the PR as written. Let me answer the question directly and then say what I think the fix is.
No — the category is not meant to say "show this to a human." It says where the fault lies relative to storage: outside this library, in the request we were handed. Whether that request originated from a person typing a URI or from segcore passing a null pointer is knowledge only segcore has, and you're correct that we cannot recover it here.
The problem is that the PR doesn't stop at saying that. ToSegcoreErrorCode maps every User code to 2042 InvalidParameter, and 2042 in milvus does mean "the end user's parameter is bad" and gets surfaced as such. So the mapping asserts exactly the thing you're saying storage can't know. That's a real defect, not a naming quibble.
Concretely, the User codes today are two different things:
| Code | What actually happened | Who must act |
|---|---|---|
LOON_SOURCE_NOT_FOUND 13, LOON_SOURCE_ACCESS_DENIED 14, LOON_SOURCE_URI_INVALID 116 |
a location string the user supplied | the end user |
LOON_INVALID_ARGS 1, LOON_INVALID_PROPERTIES 7, PackedInvalidArgs 50 |
the API was called wrong | a developer |
The first group is safe because those codes are only minted at the two entry points that take a user-supplied location (loon_exttable_explore, loon_exttable_get_file_info) — storage doesn't guess there, the entry point knows. The second group is exactly your case: LOON_INVALID_PROPERTIES fires on loon_properties_create's duplicate-key and bad-index checks, and most of those properties are hard-coded in segcore, so calling it a user error is wrong.
By this PR's own stated rule — one category per consumer action — those two rows need different categories, because "return it to the requester, don't alert" and "alert a developer, this is a bug" are opposite actions. So the fix I'd propose:
- 1, 7, 50 become
Permanent("our bug or the data is gone → alert a developer, never retry"), and map to2001 UnexpectedErrorrather than2042 InvalidParameter. Non-retriable either way, so no retry-behaviour change — but segcore is told "you called us wrong" instead of milvus telling its user "your parameter is bad." Userstays, and shrinks to 13/14/116 — the codes where storage provably knows the input was a user-supplied location.User → 2042then becomes true rather than aspirational, and the invariant test can keep enforcing it.
This costs no new category and removes the guess you're pointing at. It does change the segcore code for LOON_INVALID_ARGS, which is the highest-frequency code at the boundary, so I'd rather have your agreement before pushing it than surprise you with it.
One thing I'd push back on slightly: LOON_NOT_SUPPORT (9) is also currently User, and I think that one is defensible where it is — asking for a format this build lacks is a property of the request, and no developer action fixes it. Happy to be argued out of that too.
Separately, thanks for reading closely enough to hit this — the invariant tests in this PR would never have caught it, since they only check that the tables agree with each other.
…us-io#606) ## Problem `main` currently fails its own error-handling ratchet, and every open PR inherits the failure once CI merges it with main. milvus-io#598 (`352d545`) removed the last `throw` from `cpp/src/packed/column_group.cpp` — `ColumnGroup::Table()` now returns `arrow::Result` — but did not regenerate `cpp/scripts/error_handling_baseline.tsv`. The committed baseline still claims one throw there, and the ratchet's first layer is an exact match: ``` -throw cpp/src/packed/column_group.cpp 1 ``` ## Why it got through milvus-io#598 branched from before milvus-io#596 added the gate, so there was no baseline at its base commit and the workflow took its documented bootstrap path: ``` ::notice::error-handling-ratchet: no baseline at base commit (bootstrap); base-totals layer skipped ``` The exact-match layer had nothing to compare against on that PR, so the divergence only became visible after the merge. milvus-io#597 is in the same position (branched pre-milvus-io#596) — it regenerated its baseline on rebase, so it is fine, but the bootstrap hole is worth knowing about: a PR that predates the gate can merge a stale baseline. ## Change Deletes the one stale line. Content taken from the diff CI printed on milvus-io#604, not from a local `update` run. **Warning for whoever touches this next on macOS:** do not run `cpp/scripts/error_handling_ratchet.sh update` there. The scan uses `gcc -fpreprocessed -dD -E -P`, which Apple clang rejects with `unknown argument`, so every file silently counts zero — `check` reports the entire baseline as removed, and `update` would erase the file. Worth a guard in the script (probe the preprocessor once and fail loudly, and refuse to write an empty baseline); I can follow up with that if wanted. Unblocks: milvus-io#597, milvus-io#603, milvus-io#604, and anything else merged with main after `352d545`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_011HDgMok3nR8ZNugKWWQQK2 Signed-off-by: xiaofanluan <xiaofan.luan@zilliz.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
cb52d8d to
5617489
Compare
|
On The mechanical point is correct: the old symbol was out-of-line, so the
If an out-of-tree consumer ever surfaces, the fix on their side is a one-line rename. Please treat this as settled for this PR; happy to revisit if someone can name a real consumer. On structured Java exceptions (round 7, P2) — agreed on the end state, and it is deliberately a follow-up rather than part of this PR: |
5617489 to
aaa7295
Compare
|
On the The finding is factually right and I want to record it as such rather than argue it away. At the merge base ( Accepting it, for three reasons that are specific to this symbol rather than general hand-waving:
Restoring a non-inline forwarder would mean keeping a permanently dead exported symbol for a function nothing calls — the kind of second surface this PR exists to remove. If a real consumer surfaces the fix on their side is a one-line rename. Marking this waived. |
aaa7295 to
3d509a1
Compare
e85cfc0 to
4a5f36a
Compare
|
Is the current PR ready for review? |
a815b0f to
cb50589
Compare
cb50589 to
1fd5edf
Compare
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: xiaofan-luan The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: xiaofan-luan The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
953ab69 to
24ed518
Compare
24ed518 to
62b489e
Compare
Summary
This PR defines one native storage-error taxonomy and preserves it from the
producer to the Arrow, FFI and segcore boundaries.
User,Retryable,Conflict,DataFormat, orSystem.transactions and Vortex attach typed diagnostic codes where the cause is
known. Unclassified failures remain conservative
Systemerrors.LoonFFIResultremains unchanged. Consumers derive category and retry signalfrom the existing error code and keep the code, name and message for
diagnostics.
at entry points that know the source was supplied by the caller. Unknown file
formats use
LOON_USER_INVALID_ARGUMENT, notLOON_SOURCE_INVALID.Aggregation contract
Independent packed, multipart and batch failures use one bounded aggregator:
Retryable, the retryable aggregate parent is returned;using an explicit stable priority;
parent;
extra_infoare retained with limits on itemcount, field length and total size.
This makes asynchronous S3/Azure callback order unable to change the final
error code. A retryable packed/write failure is intended for a bounded outer
operation retry, which recreates the request/writer rather than reusing a failed
writer object.
Consumer contract
Retryable: an outer operation may retry with bounded backoff.Conflict: do not blindly replay; re-read/rebase through transaction-awarelogic.
DataFormat: persisted data could not be decoded.User: return the caller-owned error without treating it as a system alert.Systemand unknown future codes: do not retry generically; inspect the exactcode and diagnostics.
Milvus companion adapters should preserve
Code,Category,Name, andMessage. In particular,Conflictmust not be globally mapped to a retriablestorage sentinel.
Verification
milvus_testandTest_FFIcpp/scripts/check_error_table.py: 36 codes / 24ExtendStatusCodevalues in syncgit diff --checkThe complete code/category table and consumer examples are in
docs/error-codes.md.