Search before asking
Version
master, 846d9b2.
What's Wrong?
__DORIS_COMMIT_TSO_COL__ stores a 0 placeholder on disk in a single-version segment; the real value is the rowset's commit_tso, supplied at read time by building a ConstantColumnReader instead of the on-disk reader. That substitution goes through a per-segment reader cache that ignores the constant:
Status ColumnReaderCache::get_column_reader(int32_t col_uid, ..., std::optional<Field> const_value) {
// Attempt to find in cache
if (auto cached = _lookup({col_uid, {}})) {
*column_reader = cached;
return Status::OK();
}
be/src/storage/segment/column_reader_cache.cpp:96-105. On a hit the cached reader is returned and const_value is dropped, so whichever caller populates the entry first decides what every later caller gets. The cache is owned by the Segment and outlives a query.
The expression zone-map context builder is a caller that never passes a constant:
Status st = segment->get_column_reader(*tablet_column, &reader, read_options.stats,
&read_options.io_ctx);
be/src/storage/segment/segment.cpp:129-130, and the page-level builder does the same at be/src/storage/segment/segment_iterator.cpp:3414-3415. The segment-level builder runs at be/src/storage/segment/segment.cpp:485-488, inside Segment::new_iterator, so it executes before the SegmentIterator exists and therefore before any Segment::new_column_iterator call, which is where the constant would otherwise be installed (be/src/storage/segment/segment.cpp:947-966).
The consequence is not limited to pruning. ConstantColumnReader::new_iterator returns a ConstantColumnIterator over the real value (be/src/storage/segment/column_reader.h:1093-1096); the on-disk reader returns the placeholder. Once the builder has cached the on-disk reader for that column, every subsequent read of that column on that segment yields 0 as row data, for as long as the segment stays cached, including for other queries.
Reaching it needs a pushed common expression that references the column and is zone-map evaluable, with no ColumnPredicate on the same column in the same query (a ColumnPredicate makes Segment::new_iterator install the constant first, at be/src/storage/segment/segment.cpp:422-437). IS NULL and IS NOT NULL already qualify (be/src/exprs/function/is_null.h:67, be/src/exprs/function/is_not_null.h:69), and a column-vs-column comparison would qualify once expression zone maps support that shape, which is proposed in #67774; such a comparison can never fall back to a ColumnPredicate, since those are column-vs-literal.
What You Expected?
A caller that asks for a constant-backed reader gets one, whatever another caller cached earlier, and a column whose on-disk value is a placeholder never returns that placeholder as row data.
How to Reproduce?
Not reproduced end to end; this is a static reading of the cache lookup, the two builder call sites, and their order inside Segment::new_iterator. A unit test on ColumnReaderCache is the direct check: request a column reader without const_value, then request the same column with one, and assert the second call returns a ConstantColumnReader.
Anything Else?
Correcting the two builder call sites is not sufficient on its own. Other callers build a bare StorageReadOptions without a tablet schema and so cannot pass a constant either, which the comment at be/src/storage/segment/segment.cpp:947-952 already notes for the MOW partial-update row fetch. The lookup itself is the place where the constant is dropped, so that is where the fix belongs.
Are you willing to submit PR?
Code of Conduct
Search before asking
Version
master, 846d9b2.
What's Wrong?
__DORIS_COMMIT_TSO_COL__stores a0placeholder on disk in a single-version segment; the real value is the rowset'scommit_tso, supplied at read time by building aConstantColumnReaderinstead of the on-disk reader. That substitution goes through a per-segment reader cache that ignores the constant:be/src/storage/segment/column_reader_cache.cpp:96-105. On a hit the cached reader is returned andconst_valueis dropped, so whichever caller populates the entry first decides what every later caller gets. The cache is owned by theSegmentand outlives a query.The expression zone-map context builder is a caller that never passes a constant:
Status st = segment->get_column_reader(*tablet_column, &reader, read_options.stats, &read_options.io_ctx);be/src/storage/segment/segment.cpp:129-130, and the page-level builder does the same atbe/src/storage/segment/segment_iterator.cpp:3414-3415. The segment-level builder runs atbe/src/storage/segment/segment.cpp:485-488, insideSegment::new_iterator, so it executes before theSegmentIteratorexists and therefore before anySegment::new_column_iteratorcall, which is where the constant would otherwise be installed (be/src/storage/segment/segment.cpp:947-966).The consequence is not limited to pruning.
ConstantColumnReader::new_iteratorreturns aConstantColumnIteratorover the real value (be/src/storage/segment/column_reader.h:1093-1096); the on-disk reader returns the placeholder. Once the builder has cached the on-disk reader for that column, every subsequent read of that column on that segment yields0as row data, for as long as the segment stays cached, including for other queries.Reaching it needs a pushed common expression that references the column and is zone-map evaluable, with no
ColumnPredicateon the same column in the same query (aColumnPredicatemakesSegment::new_iteratorinstall the constant first, atbe/src/storage/segment/segment.cpp:422-437).IS NULLandIS NOT NULLalready qualify (be/src/exprs/function/is_null.h:67,be/src/exprs/function/is_not_null.h:69), and a column-vs-column comparison would qualify once expression zone maps support that shape, which is proposed in #67774; such a comparison can never fall back to aColumnPredicate, since those are column-vs-literal.What You Expected?
A caller that asks for a constant-backed reader gets one, whatever another caller cached earlier, and a column whose on-disk value is a placeholder never returns that placeholder as row data.
How to Reproduce?
Not reproduced end to end; this is a static reading of the cache lookup, the two builder call sites, and their order inside
Segment::new_iterator. A unit test onColumnReaderCacheis the direct check: request a column reader withoutconst_value, then request the same column with one, and assert the second call returns aConstantColumnReader.Anything Else?
Correcting the two builder call sites is not sufficient on its own. Other callers build a bare
StorageReadOptionswithout a tablet schema and so cannot pass a constant either, which the comment atbe/src/storage/segment/segment.cpp:947-952already notes for the MOW partial-update row fetch. The lookup itself is the place where the constant is dropped, so that is where the fix belongs.Are you willing to submit PR?
Code of Conduct