asdf2014 commented on code in PR #65482:
URL: https://github.com/apache/doris/pull/65482#discussion_r3586910510


##########
be/src/exec/pipeline/pipeline_fragment_context.cpp:
##########
@@ -1519,9 +1519,24 @@ Status 
PipelineFragmentContext::_create_operator(ObjectPool* pool, const TPlanNo
     bool fe_with_old_version = false;
     switch (tnode.node_type) {
     case TPlanNodeType::OLAP_SCAN_NODE: {
+        std::shared_ptr<QueryCacheRuntime> query_cache_runtime;
+        if (enable_query_cache) {
+            if (_query_cache_runtime == nullptr) {

Review Comment:
   Fixed in ba09543cf71. The plan tree is built in pre-order and the cache 
source sits above the scan, so the runtime created with it must already exist 
when the scan node is reached; a missing one means FE sent a malformed plan 
shape. Silently creating a runtime here could even drop data: with a HIT 
decision the scan skips scanning while no cache source emits the entry. So the 
lazy creation is replaced with an `InternalError`. The same reasoning applies 
to the cache source side, whose old degrade-to-pass-through branch is now an 
error too, and both paths are covered by unit tests 
(`QueryCacheFragmentContextTest` on the scan side, 
`QueryCacheOperatorTest.test_missing_runtime_fails_init` on the cache source 
side).



##########
be/src/exec/pipeline/pipeline_fragment_context.cpp:
##########
@@ -1519,9 +1519,24 @@ Status 
PipelineFragmentContext::_create_operator(ObjectPool* pool, const TPlanNo
     bool fe_with_old_version = false;
     switch (tnode.node_type) {
     case TPlanNodeType::OLAP_SCAN_NODE: {
+        std::shared_ptr<QueryCacheRuntime> query_cache_runtime;
+        if (enable_query_cache) {
+            if (_query_cache_runtime == nullptr) {
+                _query_cache_runtime =
+                        
std::make_shared<QueryCacheRuntime>(_params.fragment.query_cache_param);
+            }
+            if (tnode.olap_scan_node.__isset.read_row_binlog &&
+                tnode.olap_scan_node.read_row_binlog) {
+                // Row-binlog scans read a different data stream: they must
+                // neither serve nor fill the query cache.
+                _query_cache_runtime->disable_for_binlog_scan();
+            }
+            query_cache_runtime = _query_cache_runtime;
+        }
         op = std::make_shared<OlapScanOperatorX>(
                 pool, tnode, next_operator_id(), descs, _num_instances,
-                enable_query_cache ? _params.fragment.query_cache_param : 
TQueryCacheParam {});
+                enable_query_cache ? _params.fragment.query_cache_param : 
TQueryCacheParam {},
+                std::move(query_cache_runtime));

Review Comment:
   The scan does need the shared runtime: the runtime is the single decision 
point for the instance. Whichever operator initializes first triggers 
`get_or_make_decision()`, and both consume the same decision object, so the 
pair always agrees: the scan skips scanning if and only if the cache source 
emits just the cached blocks (HIT), and scans only the delta if and only if the 
cache source merges it (INCREMENTAL). Two independent lookups are exactly the 
pre-existing race this PR removes: they could interleave with an eviction and 
write back an empty poisoned entry. The local `shared_ptr` copy was indeed 
unnecessary; ba09543cf71 passes the member directly.



##########
be/src/exec/operator/olap_scan_operator.cpp:
##########
@@ -698,14 +698,24 @@ Status 
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
     bool read_row_binlog =
             p._olap_scan_node.__isset.read_row_binlog && 
p._olap_scan_node.read_row_binlog;
     bool has_tso_predicate = _scan_ranges[0]->__isset.start_tso || 
_scan_ranges[0]->__isset.end_tso;
+    // Query cache incremental merge: the read sources only cover the delta
+    // versions (cached_version, current_version], see prepare().
+    const bool cache_incremental =
+            _query_cache_decision != nullptr &&
+            _query_cache_decision->mode == 
QueryCacheInstanceDecision::Mode::INCREMENTAL;
+    const int64_t cache_start_version =
+            cache_incremental ? _query_cache_decision->cached_version + 1 : 0;
 
     // The flag of preagg's meaning is whether return pre agg data(or partial 
agg data)
     // PreAgg ON: The storage layer returns partially aggregated data without 
additional processing. (Fast data reading)
     // for example, if a table is select userid,count(*) from base table.
     // And the user send a query like select userid,count(*) from base table 
group by userid.
     // then the storage layer do not need do aggregation, it could just return 
the partial agg data, because the compute layer will do aggregation.
     // PreAgg OFF: The storage layer must complete pre-aggregation and return 
fully aggregated data. (Slow data reading)
-    if (enable_parallel_scan && !p._should_run_serial &&
+    // Incremental merge deltas are small by construction, so the parallel

Review Comment:
   You are right. Removed in ba09543cf71. I traced every consumer of the 
reader's `version.first`: the delete predicates and the merge-on-write bitmap 
only use `version.second`, the segment-level single-version checks use the 
rowset's own version, and the scanner's self-capture path only runs when 
`rs_splits` is empty, which the pre-captured delta read source makes 
unreachable. So `Params.start_version` and its plumbing are gone and the reader 
keeps the plain `{0, version}` range. The `cache_incremental` flag itself 
stays: it still gates `take_delta_read_source()` in `prepare()` and keeps the 
parallel scanner builder off for delta scans. The delta is typically a couple 
of small rowsets landed since the cached version, and the plain path still 
gives every tablet its own scanner, so size-based redistribution has nothing to 
split there and only adds the builder's segment loading cost.



##########
be/src/exec/operator/olap_scan_operator.cpp:
##########
@@ -698,14 +698,24 @@ Status 
OlapScanLocalState::_init_scanners(std::list<ScannerSPtr>* scanners) {
     bool read_row_binlog =
             p._olap_scan_node.__isset.read_row_binlog && 
p._olap_scan_node.read_row_binlog;
     bool has_tso_predicate = _scan_ranges[0]->__isset.start_tso || 
_scan_ranges[0]->__isset.end_tso;
+    // Query cache incremental merge: the read sources only cover the delta
+    // versions (cached_version, current_version], see prepare().
+    const bool cache_incremental =
+            _query_cache_decision != nullptr &&
+            _query_cache_decision->mode == 
QueryCacheInstanceDecision::Mode::INCREMENTAL;
+    const int64_t cache_start_version =

Review Comment:
   Removed in ba09543cf71 together with the plumbing below. The 
`cache_incremental` flag itself stays: it still gates 
`take_delta_read_source()` and keeps the parallel scanner builder off for delta 
scans (details in the sibling thread).



-- 
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