{"record":{"id":"184248b4ee3f1921","repo":"risingwavelabs/risingwave","slug":"hyperloglog-deletion-in-append-only-bucket","errorCode":null,"errorMessage":"HyperLogLog: Deletion in append-only bucket","messagePattern":"HyperLogLog: Deletion in append-only bucket","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/expr/impl/src/aggregate/approx_count_distinct/append_only.rs","lineNumber":30,"sourceCode":"// See the License for the specific language governing permissions and\n// limitations under the License.\n\nuse risingwave_common::bail;\nuse risingwave_common_estimate_size::EstimateSize;\nuse risingwave_expr::Result;\n\nuse super::Bucket;\n\n#[derive(Clone, Copy, Default, Debug, EstimateSize, PartialEq, Eq)]\npub struct AppendOnlyBucket(pub u8);\n\nimpl Bucket for AppendOnlyBucket {\n    fn update(&mut self, index: u8, retract: bool) -> Result<()> {\n        if index > 64 || index == 0 {\n            bail!(\"HyperLogLog: Invalid bucket index\");\n        }\n        if retract {\n            bail!(\"HyperLogLog: Deletion in append-only bucket\");\n        }\n        if index > self.0 {\n            self.0 = index;\n        }\n        Ok(())\n    }\n\n    fn max(&self) -> u8 {\n        self.0\n    }\n}\n","sourceCodeStart":12,"sourceCodeEnd":42,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/expr/impl/src/aggregate/approx_count_distinct/append_only.rs#L12-L42","documentation":"Append-only HyperLogLog buckets only keep a monotonic maximum rank per bucket, which makes retraction (deleting a previously aggregated row) impossible — the correct maximum cannot be recovered after removing a contribution. Any `update` with `retract = true` is rejected.","triggerScenarios":"Calling `AppendOnlyBucket::update(index, true)`, i.e. feeding a delete/retract record into an aggregate state built with the append-only HLL implementation.","commonSituations":"Running aggregations over streams/sources that emit DELETE or UPDATE-with-delete records (e.g. upsert source, backfill retraction) while the plan selected the append-only HLL variant.","solutions":["Use the updatable HLL implementation (`UpdatableBucket`/sparse+dense counts) when the input can retract","Configure the source as append-only, or ensure the optimizer proves the stream is append-only before choosing the append-only aggregate","If retracts are spurious, filter them before the aggregate"],"exampleFix":"// before: append-only HLL on upsert stream\n// agg: approx_count_distinct (append_only)\n// after: select updatable variant\n// agg: approx_count_distinct (updatable) // supports retract","handlingStrategy":"fallback","validationCode":"fn stream_supports_retract(upsert_input: bool) -> Result<()> {\n    if upsert_input { bail!(\"need updatable HLL for retractable input\") }\n    Ok(())\n}","typeGuard":null,"tryCatchPattern":"match agg.update(value, retract) {\n    Err(e) if e.to_string().contains(\"Deletion in append-only\") => switch_to_updatable_aggregator(),\n    other => other,\n}","preventionTips":["Choose the updatable HLL whenever the source may emit deletes/updates","Only pick append-only aggregates when the optimizer proved append-only input","Filter retract records before the aggregate if they're unexpected"],"tags":["rust","hyperloglog","aggregate","retraction"],"backgroundTag":"unsupported-operation","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}