Coverage Report

Created: 2026-08-06 12:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/storage/mow/key_probe.cpp
Line
Count
Source
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
1.72k
        : _tablet(tablet),
38
1.72k
          _lookup_schema(lookup_schema),
39
1.72k
          _has_sequence_col(has_sequence_col),
40
1.72k
          _mow_context(std::move(mow_context)),
41
1.72k
          _writing_rowset_id(writing_rowset_id),
42
1.72k
          _writing_segment_id(writing_segment_id),
43
1.72k
          _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
26.6k
        PartialUpdateStats& stats) const {
50
26.6k
    RowLocation loc;
51
    // save rowset shared ptr so this rowset wouldn't delete
52
26.6k
    RowsetSharedPtr rowset;
53
26.6k
    auto st = _tablet->lookup_row_key(key, _lookup_schema, key_has_seq_suffix, specified_rowsets,
54
26.6k
                                      &loc, _mow_context->max_version, segment_caches, &rowset,
55
26.6k
                                      /*with_rowid=*/false);
56
26.6k
    if (st.is<KEY_NOT_FOUND>()) {
57
638
        ++stats.num_rows_new_added;
58
638
        return ProbeOutcome {KeyProbeResult::NOT_FOUND, {}, nullptr, /*use_default_or_null=*/true};
59
638
    }
60
25.9k
    if (!st.ok() && !st.is<KEY_ALREADY_EXISTS>()) {
61
0
        LOG(WARNING) << "failed to lookup row key, tablet_id=" << _tablet->tablet_id()
62
0
                     << ", txn_id=" << _mow_context->txn_id << ", error: " << st;
63
0
        return ResultError(std::move(st));
64
0
    }
65
66
    // Stored row's seq is larger, so the incoming row loses.
67
25.9k
    bool seq_loses = st.is<KEY_ALREADY_EXISTS>();
68
    // A delete-signed row's value columns are never read back, so there is nothing to carry
69
    // forward -- except when the table has a sequence column, whose value must still be read or the
70
    // merge-on-read compaction policy produces wrong results.
71
    // TODO(bobhan1): only read seq col rather than all columns in this situation for partial update
72
    // and flexible partial update
73
25.9k
    bool delete_sign_skip =
74
25.9k
            have_delete_sign && !_has_sequence_col && _policy.use_defaults_for_delete_signed;
75
    // Flexible PU insert-after-delete: an earlier row of this same load already deleted the old
76
    // row, so the insert counts as a brand-new row. Its sequence value, if the input does not carry
77
    // one, is filled by BlockAggregator::aggregate_for_insert_after_delete().
78
    // Evaluated last so the two cheap rules above short-circuit the delete bitmap lookup.
79
25.9k
    auto in_load_deleted = [&] {
80
24.4k
        return _policy.use_defaults_for_in_load_deleted &&
81
24.4k
               _mow_context->delete_bitmap->contains(
82
1.84k
                       {loc.rowset_id, loc.segment_id, DeleteBitmap::TEMP_VERSION_COMMON},
83
1.84k
                       loc.row_id);
84
24.4k
    };
85
    // Skip reading the old row (fill defaults) in any of these cases.
86
25.9k
    bool use_default = (seq_loses && _policy.use_defaults_for_seq_loser) || delete_sign_skip ||
87
25.9k
                       in_load_deleted();
88
25.9k
    ProbeOutcome outcome {seq_loses ? KeyProbeResult::FOUND_NEWER : KeyProbeResult::FOUND, loc,
89
25.9k
                          std::move(rowset), use_default};
90
91
    // Apply the delete-bitmap marks right away -- see class comment (segcompaction).
92
25.9k
    if (seq_loses) {
93
56
        if (_policy.mark_deleted == MarkDeleted::OLD_AND_LOSING_ROW) {
94
            // although we need to mark delete current row, we still need to read missing columns
95
            // for this row, we need to ensure that each column is aligned
96
49
            _mow_context->delete_bitmap->add(
97
49
                    {_writing_rowset_id, _writing_segment_id, DeleteBitmap::TEMP_VERSION_COMMON},
98
49
                    cast_set<uint32_t>(segment_pos));
99
49
            ++stats.num_rows_deleted;
100
49
        }
101
25.9k
    } else if (_policy.mark_deleted != MarkDeleted::NONE) {
102
25.9k
        _mow_context->delete_bitmap->add(
103
25.9k
                {loc.rowset_id, loc.segment_id, DeleteBitmap::TEMP_VERSION_COMMON}, loc.row_id);
104
25.9k
        ++stats.num_rows_updated;
105
25.9k
    }
106
25.9k
    return outcome;
107
25.9k
}
108
109
Result<PrevSeqProbe> MowKeyProbe::probe_previous_seq_value(
110
        const std::string& key, const std::vector<RowsetSharedPtr>& specified_rowsets,
111
2
        std::vector<std::unique_ptr<SegmentCacheHandle>>& segment_caches) const {
112
2
    RowLocation loc;
113
2
    RowsetSharedPtr rowset;
114
2
    PrevSeqProbe result;
115
    // Unlike probe() above, this lookup keeps with_rowid: its callers encode the key from the
116
    // sort-key view, which for a cluster-key table carries the trailing rowid that has to be
117
    // stripped again. Such a table can not reach this path today -- partial update on a cluster-key
118
    // table is rejected up front -- but the encoding, not the probe, decides the flag.
119
2
    auto st =
120
2
            _tablet->lookup_row_key(key, _lookup_schema, /*with_seq_col=*/false, specified_rowsets,
121
2
                                    &loc, _mow_context->max_version, segment_caches, &rowset,
122
2
                                    /*with_rowid=*/true, &result.encoded_seq_value);
123
2
    if (st.is<KEY_NOT_FOUND>()) {
124
        // lookup_row_key writes the sequence value before it checks the delete bitmap, so a key
125
        // whose only row is already marked deleted lands here with that row's value attached.
126
1
        result.encoded_seq_value.clear();
127
1
        result.outcome = ProbeOutcome {KeyProbeResult::NOT_FOUND,
128
1
                                       {},
129
1
                                       nullptr,
130
1
                                       /*use_default_or_null=*/true};
131
1
        return result;
132
1
    }
133
1
    if (!st.ok()) {
134
0
        return ResultError(std::move(st));
135
0
    }
136
1
    result.outcome.result = KeyProbeResult::FOUND;
137
1
    result.outcome.loc = loc;
138
1
    result.outcome.rowset = std::move(rowset);
139
1
    result.outcome.use_default_or_null = false;
140
1
    return result;
141
1
}
142
143
void MowKeyProbe::maybe_invalidate_row_cache(int64_t tablet_id, const TabletSchema& schema,
144
9.67M
                                             DataWriteType write_type, const std::string& key) {
145
    // Just invalid row cache for simplicity, since the rowset is not visible at present. If we
146
    // update/insert cache, if load failed rowset will not be visible but cached data will be
147
    // visible, and lead to inconsistency.
148
9.67M
    if (!config::disable_storage_row_cache && schema.has_row_store_for_all_columns() &&
149
9.67M
        write_type == DataWriteType::TYPE_DIRECT) {
150
        // invalidate cache
151
2
        RowCache::instance()->erase({tablet_id, key});
152
2
    }
153
9.67M
}
154
155
std::string encode_mow_key_invalidate_cache(
156
        const RowKeyEncoder& key_encoder, const std::vector<IOlapColumnDataAccessor*>& key_columns,
157
        const IOlapColumnDataAccessor* seq_column, size_t pos, bool row_has_seq, int64_t tablet_id,
158
8.58M
        const TabletSchema& schema, DataWriteType write_type) {
159
8.58M
    std::string key = key_encoder.full_encode_primary_keys(key_columns, pos);
160
    // the row cache uses the key without the seq as its key, so invalidate before the suffix
161
8.58M
    MowKeyProbe::maybe_invalidate_row_cache(tablet_id, schema, write_type, key);
162
8.58M
    if (row_has_seq) {
163
31.6k
        key_encoder.append_seq_suffix(&key, seq_column, pos);
164
31.6k
    }
165
8.58M
    return key;
166
8.58M
}
167
168
} // namespace doris::segment_v2