Repository navigation
Conversation
Add CqlBackend, which implements HighVolumeBackend on top of the scylla driver and works with both Apache Cassandra 5.0+ and ScyllaDB 2025.4+. It is available as a standalone 'cql' storage type and as the high-volume tier of tiered storage. CQL conditions cannot express the disjunctions that tombstone-aware operations need, so every conditional operation reads the row, checks its precondition locally, and writes with 'IF rev = ?' on the observed revision. All writes are lightweight transactions so they are never reordered against plain writes. Rows carry an authoritative expires_at column plus a TTL for reclamation; deadlines beyond Cassandra's 2038 limit are stored without a TTL. Also adds a Cassandra devservice, runs the CQL tests against Cassandra and ScyllaDB in CI, and moves apply_range into common.rs so both high-volume backends share it.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #675 +/- ##
==========================================
+ Coverage 91.38% 91.48% +0.10%
==========================================
Files 117 118 +1
Lines 24056 25697 +1641
==========================================
+ Hits 21983 23510 +1527
- Misses 2073 2187 +114
☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
bugbot run |
| /// CQL backend for [Apache Cassandra] or [ScyllaDB]. | ||
| /// | ||
| /// [Apache Cassandra]: https://cassandra.apache.org/ | ||
| /// [ScyllaDB]: https://www.scylladb.com/ | ||
| Cql(cql::CqlConfig), | ||
| } | ||
|
|
||
| /// Constructs a type-erased [`common::HighVolumeBackend`] from the given config. |
There was a problem hiding this comment.
Bug: The compare_and_update method for the CQL backend lacks a retry loop for its CAS operation, unlike other conditional write methods in the same file.
Severity: HIGH
Suggested Fix
Wrap the logic inside compare_and_update in the CqlBackend with a retry loop, similar to the pattern used in other conditional write methods like put_non_tombstone. On a failed CAS attempt (when write_if returns false), the loop should retry the read-modify-write cycle instead of immediately returning SetExpiryResponse::Rejected.
Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.
Location: objectstore-service/src/backend/mod.rs#L119-L126
Potential issue: The `compare_and_update` method in `CqlBackend` performs a client-side
read-then-write check for its Compare-And-Set (CAS) operation but lacks a retry loop to
handle race conditions. If a concurrent write occurs between the read and the
conditional write, `write_if()` will fail, and the method will incorrectly return
`SetExpiryResponse::Rejected`. This signals a permanent failure instead of a transient
one that could be retried. As a result, under concurrent load, updates to object
expiration times can be silently lost, leading to objects having stale expiration data.
Did we get this right? 👍 / 👎 to inform future reviews.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f0fa574. Configure here.
| )); | ||
| } | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
Upload marker create is not idempotent
Medium Severity
create_upload_marker writes only when the row is absent (IF rev = null). A retry after a successful create, or a leftover expired marker at the same upload key, loses the LWT and returns an internal error. The Bigtable backend overwrites unconditionally, so the same retry succeeds there.
Reviewed by Cursor Bugbot for commit f0fa574. Configure here.
| ) | ||
| .await?; | ||
| applied(result) | ||
| } |
There was a problem hiding this comment.
Marker delete misses timeout recovery
Medium Severity
delete_upload_marker returns the driver error when the LWT times out and never re-reads the row. write_if and delete_if treat a matching post-error state as success. A delete that applied before the timeout then looks like a missing marker on retry, so upload completion can fail after the marker was already consumed.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit f0fa574. Configure here.


Adds
CqlBackend, a high-volume backend for Apache Cassandra (5.0+) and ScyllaDB (2025.4+) built on thescylladriver. It can be configured standalone (type: cql) or as thehigh_volumetier of tiered storage. Nothing changes unless it is configured.Draft for discussion and sandbox benchmarking — not production-ready. It has only been tested against single-node clusters, and the write path has not been load-tested.
The keyspace and table must exist up front; the schema is in
devservices/cql-schema.cqland in theCqlConfigdocs. Optional settings cover username/password, rustls TLS (incl. mTLS), request timeout, and consistency levels (defaults:local_quorum/local_serial).Design
Each object is one partition keyed by its storage path, with
kind(object / tombstone / upload marker),metadata,payload,redirect,expires_at, and arevuuid that changes on every write.CQL
IFconditions only support AND, so "absent, inline, or expired tombstone" can't be checked server-side the way Bigtable'sCheckAndMutateRowdoes. Every conditional operation instead reads the row, checks the precondition locally, and writes withIF rev = <observed>(a missing row matchesIF rev = null), retrying a few times if it loses a race. Every write, including standalone puts and deletes, is an LWT. Mixing LWT and plain writes on the same row can reorder them under clock skew.What to look at when benchmarking
LOCAL_QUORUMselect; metadata-only reads skip the payload column.Compatibility notes
storage_compatibility_mode, Cassandra 5.0 rejects TTLs that expire after 2038-01-19. Rows with such deadlines, or deadlines more than 20 years out, are written without a TTL. They still expire on read but are only reclaimed when overwritten or deleted. ScyllaDB accepts these TTLs.tablets = {'enabled': false}.[applied]is read.Known gaps
verify_hostname: false.cql.failures; driver-level retries aren't counted.Testing
The Bigtable backend tests are ported (except legacy-format ones), plus CQL-specific tests for TTLs, upload markers, and concurrent CAS. CI runs them against Cassandra in
test-alland against ScyllaDB in a newtest-cql-scyllajob. A Cassandra devservice (127.0.0.1:8088) is added tofullmode.apply_rangemoves frombigtable.rstocommon.rsso both HV backends share it.