eldenmoon opened a new pull request, #70424:
URL: https://github.com/apache/doris/pull/70424

   ### What problem does this PR solve?
   
   A short-circuit point query (`enable_short_circuit_query=true` with the BE 
row cache enabled, i.e.
   `disable_storage_row_cache=false`) on a merge-on-write unique table with a 
**partial** row store returns
   stale rows, and can crash the BE.
   
   `row_store_columns` (for example `"row_store_columns" = "id,v"`) makes the 
FE take the short-circuit plan,
   but the hidden `__DORIS_ROW_STORE_COL__` JSONB only holds the configured 
columns
   (`JsonbSerializeUtil::block_to_jsonb` filters on the row-store column ids). 
The point-query executor
   nevertheless filled and read `RowCache` exactly like for a full row store:
   
   1. `MowKeyProbe::maybe_invalidate_row_cache` invalidates the cache only when
      `TabletSchema::has_row_store_for_all_columns()`, so an `UPDATE`/`INSERT` 
left the previous entry in place.
      Short-circuit point queries kept returning the pre-update row until the 
300 s
      `point_query_row_cache_stale_sweep_time_sec` sweep.
   2. A cache hit skips `lookup_row_key` and therefore has no row location, so 
`_lookup_row_data` filled only the
      row-store columns. A query that reads a column outside the row store, or 
`__DORIS_DELETE_SIGN__` (which the
      FE adds as a point-query predicate), then crashed the BE in 
`VExpr::filter_column_with_selector` with
      `Check failed: origin_column->size() == count (0 vs. 1)`.
   
   Repro (reported on 4.1.3, also reproduces on master):
   
   ```sql
   CREATE TABLE t (id BIGINT, v INT, c VARCHAR(64))
   UNIQUE KEY(id) DISTRIBUTED BY HASH(id) BUCKETS 1
   PROPERTIES ("enable_unique_key_merge_on_write"="true", "replication_num"="1",
               "row_store_columns"="id,v");
   INSERT INTO t VALUES (1, 100, 'old');
   -- warm twice through the short-circuit plan -> 1, 100
   UPDATE t SET v = 200, c = 'new' WHERE id = 1;
   SELECT /*+ SET_VAR(enable_short_circuit_query=false) */ id, v, c FROM t 
WHERE id = 1; -- 1, 200, new
   SELECT /*+ SET_VAR(enable_short_circuit_query=true)  */ id, v    FROM t 
WHERE id = 1; -- 1, 100 (stale)
   SELECT /*+ SET_VAR(enable_short_circuit_query=true)  */ id, v, c FROM t 
WHERE id = 1; -- BE crash
   ```
   
   With `disable_storage_row_cache=true` both short-circuit statements return 
`1, 200, new`, which is the
   workaround the reporter used.
   
   ### What exactly does this PR change?
   
   - `PointQueryExecutor` now consults and fills the row cache only when the 
tablet's row store holds every column
     (`has_row_store_for_all_columns()`) — the same predicate the write path 
already uses to invalidate it. Full
     row store point queries are unchanged; partial row store queries read the 
column store as before but no longer
     use a cache entry that can neither be invalidated nor completed.
   - A BE unit test drives the real executor over a partial-row-store tablet 
and asserts the correct rows and zero
     cache hits on both a cold and a warm run (before the fix the warm run hits 
the cache and dies on the same
     `origin_column->size() == count` check).
   - A regression suite covers the `UPDATE` repro (stale value and the 
three-column read) plus a full-row-store
     control that must keep using the cache.
   
   No FE change, no change to write-path invalidation, the cache key, the LRU 
policy or
   `has_row_store_for_all_columns()`. Making the row cache serve partial row 
stores would require a row-location
   lookup on every hit (and delete-sign handling), which defeats the purpose of 
the cache; that is out of scope
   here.
   
   ### Test
   
   - `./build.sh --be` (ASAN).
   - `./run-be-ut.sh --run --filter='PointQuery*:*PointQuery*'` — 7 tests 
passed, including
     
`SegmentLayouts/PointQuerySegmentReuseTest.PartialRowStoreDoesNotUseRowCache/{0,1}`
 and the existing
     `LookupUpAndRowCacheHit/{0,1}` (local toolchain needed 
`GLIBC_COMPATIBILITY=OFF` for the UT link).
   - New suite `point_query_p0/test_row_cache_partial_row_store` and the 
neighbouring
     `test_point_query`, `test_update`, `test_rowstore`, 
`test_short_circuit_rowstore_nested_complex` suites pass.
   - Manual repro on the fixed BE with the row cache enabled: warm/warm `1, 
100`; after `UPDATE` the
     short-circuit query returns `1, 200`, `id, v, c` returns `1, 200, new` 
twice in a row, BE stays healthy and
     `be.out` gains no `Check failed`.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to