Skip to content

[Bug] The column reader cache drops a requested constant value on a cache hit, so a placeholder column can be read as its on-disk placeholder #67994

Description

@LuciferYang

Search before asking

  • I had searched in the issues and found no similar issues.

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?

  • Yes I am willing to submit a PR!

Code of Conduct

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions