Skip to content

Commit d5d0148

Browse files
csun5285claude
andcommitted
[refactor](storage) extract MowKeyProbe and HistoricalRowFetcher; segment writers and row-binlog retriever delegate probe, row-cache invalidation and old-value reads
Part 2/6 of splitting #64674 (block transform chain) into independently reviewable PRs. Part 1 was #65492 (RowKeyEncoder). The merge-on-write primary key probe - look the key up in the load's rowset snapshot, mark the delete bitmap, decide whether the old row has to be read for the missing columns - existed in three near-verbatim copies: SegmentWriter::probe_key_for_mow, VerticalSegmentWriter::_probe_key_for_mow and PrimaryKeyModelRowRetriever. The key encoding around it and the row-cache invalidation were duplicated the same way. The three copies differ in exactly three rules, all of which were expressed by copy-editing the code. Extract them into storage/mow/: - MowKeyProbe: one probe behind a Policy (delete_bitmap_mode READ_ONLY / OLD_ROW / OLD_AND_NEW_ROW, skip_delete_sign, skip_seq_loses, skip_in_load_deleted), plus probe_previous_seq_value(), maybe_invalidate_row_cache() and encode_mow_key_invalidate_cache(), which encodes a key, invalidates the row cache and appends the sequence suffix in the one order that is correct. - HistoricalRowFetcher: owns the rowset pins and the read plans behind FixedReadPlan / FlexibleReadPlan. All three call sites delegate. Both writers keep a thin wrapper that turns a ProbeOutcome back into the out-parameters their fill loops use and run their fixed partial-update fill on a per-call HistoricalRowFetcher; the wrappers are bridge code, deleted when the fills move into chain stages. PrimaryKeyModelRowRetriever drops its own encode / probe / row-cache copies and holds a RowKeyEncoder plus a HistoricalRowFetcher, still driven by the existing RowBinlogSegmentWriter so the row-binlog regressions cover the rework. RowKeyEncoder gains a second constructor for the probe side: the key the primary key index is built on is always the schema key columns, while the writer's constructor builds whatever the segment sorts by (cluster keys when it has them) plus the separate primary-key view and the rowid suffix. The retriever is the first caller that has to encode primary keys for any mow table, with or without cluster keys. Equivalence notes: the Policy reproduces all three old copies term for term (delete-bitmap keys, marked rows and stat counters unchanged); SegmentWriter's cast_set<uint32_t>(max_version) truncation is dropped in favour of the int64 behavior the other two callers already had; lookup_row_key is called with an explicit with_rowid=false, which fixes the retriever on cluster-key mow tables (the default strips ROW_ID_LENGTH bytes off a probe key that carries no rowid suffix) and is inert for partial update, which FE forbids on those tables; num_rows_new_added is bumped inside probe(), before the writers' handle_new_key runs at the call site; fixed-path rowset pins move from writer-lifetime maps to the per-call fetcher, so SegmentWriter::_rsid_to_rowset is gone while the vertical writer keeps its map for the flexible fill and BlockAggregator; the retriever's clear() now also drops the fetcher that holds the read plan. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 973f93f commit d5d0148

17 files changed

Lines changed: 2083 additions & 321 deletions

be/src/storage/key/row_key_encoder.cpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,23 @@ RowKeyEncoder::RowKeyEncoder(const TabletSchema& schema, bool mow)
3838
}
3939
}
4040

41-
void RowKeyEncoder::_init_mow(const TabletSchema& schema) {
41+
RowKeyEncoder::RowKeyEncoder(const TabletSchema& schema)
42+
: _num_short_key_columns(schema.num_short_key_columns()) {
43+
_init_seq_coder(schema);
44+
_add_default_sort_key_columns(schema);
45+
}
46+
47+
void RowKeyEncoder::_init_seq_coder(const TabletSchema& schema) {
4248
// encode the sequence id into the primary key index
4349
if (schema.has_sequence_col()) {
4450
const auto& column = schema.column(schema.sequence_col_idx());
4551
_seq_coder = get_key_coder(column.type());
4652
_seq_col_length = column.length();
4753
}
54+
}
55+
56+
void RowKeyEncoder::_init_mow(const TabletSchema& schema) {
57+
_init_seq_coder(schema);
4858

4959
if (schema.cluster_key_uids().empty()) {
5060
_add_default_sort_key_columns(schema);

be/src/storage/key/row_key_encoder.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,24 @@ class TabletSchema;
3535
// is encoded as KEY_NULL_FIRST_MARKER without value bytes.
3636
class RowKeyEncoder {
3737
public:
38+
// The segment writer's encoder: sort-key view +, for a mow table with
39+
// cluster keys, the separate primary-key view and the rowid suffix.
3840
RowKeyEncoder(const TabletSchema& schema, bool mow);
3941

42+
// The probe side's encoder: the key the primary key index is built on and
43+
// probed with, i.e. the schema key columns plus the sequence suffix. Its
44+
// sort-key view IS that key, so full_encode() returns it for every mow
45+
// table, with or without cluster keys.
46+
explicit RowKeyEncoder(const TabletSchema& schema);
47+
4048
// Encode the sort key columns at `pos` with full length.
4149
std::string full_encode(const std::vector<IOlapColumnDataAccessor*>& key_columns,
4250
size_t pos) const;
4351

4452
// For a mow table with cluster keys, encode the primary key columns at
4553
// `pos` with full length, producing the key stored in and probed against
46-
// the primary key index.
54+
// the primary key index. Without cluster keys the segment sorts by those
55+
// same columns, so the single-argument constructor above covers that case.
4756
std::string full_encode_primary_keys(const std::vector<IOlapColumnDataAccessor*>& key_columns,
4857
size_t pos) const;
4958

@@ -69,6 +78,7 @@ class RowKeyEncoder {
6978
const std::vector<IOlapColumnDataAccessor*>& key_columns,
7079
size_t pos);
7180

81+
void _init_seq_coder(const TabletSchema& schema);
7282
void _init_mow(const TabletSchema& schema);
7383
void _init_non_mow(const TabletSchema& schema);
7484
void _add_default_sort_key_columns(const TabletSchema& schema);
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#include "storage/mow/historical_row_fetcher.h"
19+
20+
#include "storage/rowset/rowset.h"
21+
#include "storage/tablet/tablet_schema.h"
22+
23+
namespace doris {
24+
25+
HistoricalRowFetcher::HistoricalRowFetcher(segment_v2::HistoricalRowRetrieverContext context)
26+
: _context(std::move(context)),
27+
_flexible_plan(_context.tablet_schema->has_row_store_for_all_columns()) {}
28+
29+
void HistoricalRowFetcher::pin_rowset(const RowsetSharedPtr& rowset) {
30+
_rsid_to_rowset.emplace(rowset->rowset_id(), rowset);
31+
}
32+
33+
void HistoricalRowFetcher::plan_fixed_read(const RowLocation& loc, size_t dst_pos) {
34+
_fixed_plan.prepare_to_read(loc, dst_pos);
35+
}
36+
37+
void HistoricalRowFetcher::plan_flexible_read(const RowLocation& loc, size_t dst_pos,
38+
const BitmapValue& skip_bitmap) {
39+
_flexible_plan.prepare_to_read(loc, dst_pos, skip_bitmap);
40+
}
41+
42+
Status HistoricalRowFetcher::fill_missing_columns(const TabletSchema& tablet_schema,
43+
Block& full_block,
44+
const std::vector<bool>& use_default_or_null_flag,
45+
bool has_default_or_nullable,
46+
uint32_t segment_start_pos,
47+
const Block* block) const {
48+
return _fixed_plan.fill_missing_columns(_context, _rsid_to_rowset, tablet_schema, full_block,
49+
use_default_or_null_flag, has_default_or_nullable,
50+
segment_start_pos, block);
51+
}
52+
53+
Status HistoricalRowFetcher::fill_non_primary_key_columns(
54+
const TabletSchema& tablet_schema, Block& full_block,
55+
const std::vector<bool>& use_default_or_null_flag, bool has_default_or_nullable,
56+
uint32_t segment_start_pos, uint32_t block_start_pos, const Block* block,
57+
std::vector<BitmapValue>* skip_bitmaps) const {
58+
return _flexible_plan.fill_non_primary_key_columns(
59+
_context, _rsid_to_rowset, tablet_schema, full_block, use_default_or_null_flag,
60+
has_default_or_nullable, segment_start_pos, block_start_pos, block, skip_bitmaps);
61+
}
62+
63+
Status HistoricalRowFetcher::read_columns(const TabletSchema& tablet_schema,
64+
std::vector<uint32_t> cids_to_read, Block& dst_block,
65+
std::map<uint32_t, uint32_t>* read_index,
66+
bool force_read_old_delete_signs,
67+
const signed char* __restrict cur_delete_signs) const {
68+
return _fixed_plan.read_columns_by_plan(tablet_schema, std::move(cids_to_read), _rsid_to_rowset,
69+
dst_block, read_index, force_read_old_delete_signs,
70+
cur_delete_signs);
71+
}
72+
73+
} // namespace doris
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#pragma once
19+
20+
#include <cstdint>
21+
#include <map>
22+
#include <memory>
23+
#include <vector>
24+
25+
#include "common/status.h"
26+
#include "storage/partial_update_info.h"
27+
#include "storage/rowset/rowset_fwd.h"
28+
#include "storage/segment/historical_row_retriever.h"
29+
30+
namespace doris {
31+
class Block;
32+
class TabletSchema;
33+
34+
// Reads old-row column values for merge-on-write loads. One instance for each
35+
// segment-writer flush; it owns the rowset pins and the read plans that the
36+
// MowKeyProbe outcomes feed.
37+
class HistoricalRowFetcher {
38+
public:
39+
explicit HistoricalRowFetcher(segment_v2::HistoricalRowRetrieverContext context);
40+
41+
// Keep `rowset` alive until fill/read is done.
42+
void pin_rowset(const RowsetSharedPtr& rowset);
43+
44+
// ---- plan building; the destination position (segment_pos / delta_pos /
45+
// block_pos) is not interpreted by the fetcher ----
46+
void plan_fixed_read(const RowLocation& loc, size_t dst_pos);
47+
void plan_flexible_read(const RowLocation& loc, size_t dst_pos, const BitmapValue& skip_bitmap);
48+
49+
// ---- fixed partial update / binlog AFTER fill ----
50+
// Same behavior as FixedReadPlan::fill_missing_columns: forces reading
51+
// old delete signs, fills in this order: default -> null -> auto-inc ->
52+
// any value, keeps the sequence column non-decreasing over delete-signed
53+
// old rows.
54+
Status fill_missing_columns(const TabletSchema& tablet_schema, Block& full_block,
55+
const std::vector<bool>& use_default_or_null_flag,
56+
bool has_default_or_nullable, uint32_t segment_start_pos,
57+
const Block* block) const;
58+
59+
// ---- flexible partial update fill ----
60+
// Same behavior as FlexibleReadPlan::fill_non_primary_key_columns:
61+
// driven by the skip bitmap, re-checks the delete sign with the seq map
62+
// column in mind, on_update_current_timestamp forces default, auto-inc
63+
// copied in place from the current block.
64+
Status fill_non_primary_key_columns(const TabletSchema& tablet_schema, Block& full_block,
65+
const std::vector<bool>& use_default_or_null_flag,
66+
bool has_default_or_nullable, uint32_t segment_start_pos,
67+
uint32_t block_start_pos, const Block* block,
68+
std::vector<BitmapValue>* skip_bitmaps) const;
69+
70+
// ---- raw column read on the fixed plan ----
71+
// No default/null fill steps, no auto-inc, no seq handling. Used by the
72+
// binlog BEFORE image (cids = visible value columns; rows not in
73+
// *read_index are left as NULL) and BlockAggregator::fill_sequence_column.
74+
Status read_columns(const TabletSchema& tablet_schema, std::vector<uint32_t> cids_to_read,
75+
Block& dst_block, std::map<uint32_t, uint32_t>* read_index,
76+
bool force_read_old_delete_signs,
77+
const signed char* __restrict cur_delete_signs = nullptr) const;
78+
79+
const std::map<RowsetId, RowsetSharedPtr>& pinned_rowsets() const { return _rsid_to_rowset; }
80+
81+
private:
82+
// NOTE: _context must stay declared before _flexible_plan; the constructor
83+
// init list reads _context.tablet_schema (FlexibleReadPlan has no default ctor).
84+
segment_v2::HistoricalRowRetrieverContext _context;
85+
FixedReadPlan _fixed_plan;
86+
FlexibleReadPlan _flexible_plan;
87+
std::map<RowsetId, RowsetSharedPtr> _rsid_to_rowset;
88+
};
89+
90+
} // namespace doris

be/src/storage/mow/key_probe.cpp

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
#include "storage/mow/key_probe.h"
19+
20+
#include "common/cast_set.h"
21+
#include "common/config.h"
22+
#include "common/logging.h"
23+
#include "service/point_query_executor.h"
24+
#include "storage/key/row_key_encoder.h"
25+
#include "storage/partial_update_info.h"
26+
#include "storage/tablet/base_tablet.h"
27+
#include "storage/tablet/tablet_meta.h"
28+
#include "storage/tablet/tablet_schema.h"
29+
30+
namespace doris::segment_v2 {
31+
32+
using namespace ErrorCode;
33+
34+
MowKeyProbe::MowKeyProbe(BaseTablet* tablet, TabletSchema* lookup_schema, bool has_sequence_col,
35+
std::shared_ptr<MowContext> mow_context, const RowsetId& writing_rowset_id,
36+
uint32_t writing_segment_id, Policy policy)
37+
: _tablet(tablet),
38+
_lookup_schema(lookup_schema),
39+
_has_sequence_col(has_sequence_col),
40+
_mow_context(std::move(mow_context)),
41+
_writing_rowset_id(writing_rowset_id),
42+
_writing_segment_id(writing_segment_id),
43+
_policy(policy) {}
44+
45+
Result<ProbeOutcome> MowKeyProbe::probe(
46+
const std::string& key, size_t segment_pos, bool key_has_seq_suffix, bool have_delete_sign,
47+
const std::vector<RowsetSharedPtr>& specified_rowsets,
48+
std::vector<std::unique_ptr<SegmentCacheHandle>>& segment_caches,
49+
PartialUpdateStats& stats) const {
50+
RowLocation loc;
51+
// save rowset shared ptr so this rowset wouldn't delete
52+
RowsetSharedPtr rowset;
53+
auto st = _tablet->lookup_row_key(key, _lookup_schema, key_has_seq_suffix, specified_rowsets,
54+
&loc, _mow_context->max_version, segment_caches, &rowset,
55+
/*with_rowid=*/false);
56+
if (st.is<KEY_NOT_FOUND>()) {
57+
++stats.num_rows_new_added;
58+
return ProbeOutcome {KeyProbeResult::NOT_FOUND, {}, nullptr, /*use_default_or_null=*/true};
59+
}
60+
if (!st.ok() && !st.is<KEY_ALREADY_EXISTS>()) {
61+
LOG(WARNING) << "failed to lookup row key, error: " << st;
62+
return ResultError(std::move(st));
63+
}
64+
65+
// Stored row's seq is larger, so the incoming row loses.
66+
bool seq_loses = st.is<KEY_ALREADY_EXISTS>();
67+
// A delete-signed row needs no old values -- but only without a seq col,
68+
// since the seq must still be read for merge-on-read compaction.
69+
bool delete_sign_skip = have_delete_sign && !_has_sequence_col && _policy.skip_delete_sign;
70+
// Flexible PU insert-after-delete: the old row was already deleted in this
71+
// load, so the insert counts as a brand-new row.
72+
bool in_load_deleted =
73+
_policy.skip_in_load_deleted &&
74+
_mow_context->delete_bitmap->contains(
75+
{loc.rowset_id, loc.segment_id, DeleteBitmap::TEMP_VERSION_COMMON}, loc.row_id);
76+
// Skip reading the old row (fill defaults) in any of these cases.
77+
bool use_default = (seq_loses && _policy.skip_seq_loses) || delete_sign_skip || in_load_deleted;
78+
ProbeOutcome outcome {seq_loses ? KeyProbeResult::FOUND_NEWER : KeyProbeResult::FOUND, loc,
79+
std::move(rowset), use_default};
80+
81+
// Apply the delete-bitmap marks right away -- see class comment (segcompaction).
82+
if (seq_loses) {
83+
if (_policy.delete_bitmap_mode == DeleteBitmapMode::OLD_AND_NEW_ROW) {
84+
// although we need to mark delete current row, we still need to read missing
85+
// columns for this row, we need to ensure that each column is aligned
86+
_mow_context->delete_bitmap->add(
87+
{_writing_rowset_id, _writing_segment_id, DeleteBitmap::TEMP_VERSION_COMMON},
88+
cast_set<uint32_t>(segment_pos));
89+
++stats.num_rows_deleted;
90+
}
91+
} else if (_policy.delete_bitmap_mode != DeleteBitmapMode::READ_ONLY) {
92+
_mow_context->delete_bitmap->add(
93+
{loc.rowset_id, loc.segment_id, DeleteBitmap::TEMP_VERSION_COMMON}, loc.row_id);
94+
++stats.num_rows_updated;
95+
}
96+
return outcome;
97+
}
98+
99+
Result<PrevSeqProbe> MowKeyProbe::probe_previous_seq_value(
100+
const std::string& key, const std::vector<RowsetSharedPtr>& specified_rowsets,
101+
std::vector<std::unique_ptr<SegmentCacheHandle>>& segment_caches) const {
102+
RowLocation loc;
103+
RowsetSharedPtr rowset;
104+
PrevSeqProbe result;
105+
auto st =
106+
_tablet->lookup_row_key(key, _lookup_schema, /*with_seq_col=*/false, specified_rowsets,
107+
&loc, _mow_context->max_version, segment_caches, &rowset,
108+
/*with_rowid=*/true, &result.encoded_seq_value);
109+
if (st.is<KEY_NOT_FOUND>()) {
110+
result.outcome = ProbeOutcome {KeyProbeResult::NOT_FOUND,
111+
{},
112+
nullptr,
113+
/*use_default_or_null=*/true};
114+
return result;
115+
}
116+
if (!st.ok()) {
117+
return ResultError(std::move(st));
118+
}
119+
result.outcome.result = KeyProbeResult::FOUND;
120+
result.outcome.loc = loc;
121+
result.outcome.rowset = std::move(rowset);
122+
result.outcome.use_default_or_null = false;
123+
return result;
124+
}
125+
126+
void MowKeyProbe::maybe_invalidate_row_cache(int64_t tablet_id, const TabletSchema& schema,
127+
DataWriteType write_type, const std::string& key) {
128+
// Just invalid row cache for simplicity, since the rowset is not visible at present.
129+
// If we update/insert cache, if load failed rowset will not be visible but cached data
130+
// will be visible, and lead to inconsistency.
131+
if (!config::disable_storage_row_cache && schema.has_row_store_for_all_columns() &&
132+
write_type == DataWriteType::TYPE_DIRECT) {
133+
// invalidate cache
134+
RowCache::instance()->erase({tablet_id, key});
135+
}
136+
}
137+
138+
std::string encode_mow_key_invalidate_cache(
139+
const RowKeyEncoder& key_encoder, const std::vector<IOlapColumnDataAccessor*>& key_columns,
140+
const IOlapColumnDataAccessor* seq_column, size_t pos, bool row_has_seq, int64_t tablet_id,
141+
const TabletSchema& schema, DataWriteType write_type) {
142+
std::string key = key_encoder.full_encode(key_columns, pos);
143+
// the row cache uses the key without the seq as its key, so invalidate before the suffix
144+
MowKeyProbe::maybe_invalidate_row_cache(tablet_id, schema, write_type, key);
145+
if (row_has_seq) {
146+
key_encoder.append_seq_suffix(&key, seq_column, pos);
147+
}
148+
return key;
149+
}
150+
151+
} // namespace doris::segment_v2

0 commit comments

Comments
 (0)