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/row_ttl.h" |
19 | | |
20 | | #include <utility> |
21 | | |
22 | | #include "common/check.h" |
23 | | #include "core/assert_cast.h" |
24 | | #include "core/column/column_nullable.h" |
25 | | #include "core/column/column_vector.h" |
26 | | #include "core/data_type/data_type_factory.hpp" |
27 | | #include "core/data_type/primitive_type.h" |
28 | | #include "core/data_type_serde/data_type_serde.h" |
29 | | #include "core/value/timestamptz_value.h" |
30 | | #include "core/value/vdatetime_value.h" |
31 | | #include "storage/tablet/tablet_schema.h" |
32 | | #include "storage/utils.h" |
33 | | #include "util/timezone_utils.h" |
34 | | |
35 | | namespace doris { |
36 | | namespace { |
37 | | |
38 | 24 | Result<int64_t> checked_multiply(int64_t value, int64_t multiplier) { |
39 | 24 | int64_t result = 0; |
40 | 24 | if (__builtin_mul_overflow(value, multiplier, &result)) { |
41 | 1 | return ResultError(Status::InvalidArgument("row ttl value overflows microseconds")); |
42 | 1 | } |
43 | 23 | return result; |
44 | 24 | } |
45 | | |
46 | 34 | Result<int64_t> checked_add(int64_t left, int64_t right) { |
47 | 34 | int64_t result = 0; |
48 | 34 | if (__builtin_add_overflow(left, right, &result)) { |
49 | 2 | return ResultError(Status::InvalidArgument("row ttl expiration time overflows int64")); |
50 | 2 | } |
51 | 32 | return result; |
52 | 34 | } |
53 | | |
54 | | template <typename ColumnType, typename ValueType> |
55 | | void extract_epoch_time(const IColumn& source, size_t row, const cctz::time_zone& time_zone, |
56 | 12 | int64_t* epoch_seconds, int64_t* microsecond) { |
57 | 12 | const auto& value = assert_cast<const ColumnType&>(source).get_data()[row]; |
58 | 12 | const auto& date_time = reinterpret_cast<const ValueType&>(value); |
59 | 12 | date_time.unix_timestamp(epoch_seconds, time_zone); |
60 | 12 | *microsecond = date_time.microsecond(); |
61 | 12 | } row_ttl.cpp:_ZN5doris12_GLOBAL__N_118extract_epoch_timeINS_12ColumnVectorILNS_13PrimitiveTypeE25EEENS_11DateV2ValueINS_15DateV2ValueTypeEEEEEvRKNS_7IColumnEmRKN4cctz9time_zoneEPlSF_ Line | Count | Source | 56 | 1 | int64_t* epoch_seconds, int64_t* microsecond) { | 57 | 1 | const auto& value = assert_cast<const ColumnType&>(source).get_data()[row]; | 58 | 1 | const auto& date_time = reinterpret_cast<const ValueType&>(value); | 59 | 1 | date_time.unix_timestamp(epoch_seconds, time_zone); | 60 | 1 | *microsecond = date_time.microsecond(); | 61 | 1 | } |
row_ttl.cpp:_ZN5doris12_GLOBAL__N_118extract_epoch_timeINS_12ColumnVectorILNS_13PrimitiveTypeE26EEENS_11DateV2ValueINS_19DateTimeV2ValueTypeEEEEEvRKNS_7IColumnEmRKN4cctz9time_zoneEPlSF_ Line | Count | Source | 56 | 10 | int64_t* epoch_seconds, int64_t* microsecond) { | 57 | 10 | const auto& value = assert_cast<const ColumnType&>(source).get_data()[row]; | 58 | 10 | const auto& date_time = reinterpret_cast<const ValueType&>(value); | 59 | 10 | date_time.unix_timestamp(epoch_seconds, time_zone); | 60 | 10 | *microsecond = date_time.microsecond(); | 61 | 10 | } |
row_ttl.cpp:_ZN5doris12_GLOBAL__N_118extract_epoch_timeINS_12ColumnVectorILNS_13PrimitiveTypeE42EEENS_16TimestampTzValueEEEvRKNS_7IColumnEmRKN4cctz9time_zoneEPlSD_ Line | Count | Source | 56 | 1 | int64_t* epoch_seconds, int64_t* microsecond) { | 57 | 1 | const auto& value = assert_cast<const ColumnType&>(source).get_data()[row]; | 58 | 1 | const auto& date_time = reinterpret_cast<const ValueType&>(value); | 59 | 1 | date_time.unix_timestamp(epoch_seconds, time_zone); | 60 | 1 | *microsecond = date_time.microsecond(); | 61 | 1 | } |
|
62 | | |
63 | | } // namespace |
64 | | |
65 | 11 | bool row_ttl_uses_source_time(const TabletSchema& tablet_schema) { |
66 | 11 | DORIS_CHECK(tablet_schema.has_ttl_col()); |
67 | 11 | return tablet_schema.column(tablet_schema.ttl_col_idx()).type() != |
68 | 11 | FieldType::OLAP_FIELD_TYPE_BIGINT; |
69 | 11 | } |
70 | | |
71 | | Status calculate_row_ttl_expiration_us(const IColumn& source, FieldType source_type, size_t row, |
72 | | const cctz::time_zone& time_zone, int64_t duration_us, |
73 | 15 | int64_t* expiration_us) { |
74 | 15 | int64_t epoch_seconds = 0; |
75 | 15 | int64_t microsecond = 0; |
76 | 15 | switch (source_type) { |
77 | 1 | case FieldType::OLAP_FIELD_TYPE_DATE: { |
78 | 1 | const auto& date_time = assert_cast<const ColumnDate&>(source).get_data()[row]; |
79 | 1 | date_time.unix_timestamp(&epoch_seconds, time_zone); |
80 | 1 | break; |
81 | 0 | } |
82 | 1 | case FieldType::OLAP_FIELD_TYPE_DATETIME: { |
83 | 1 | const auto& date_time = assert_cast<const ColumnDateTime&>(source).get_data()[row]; |
84 | 1 | date_time.unix_timestamp(&epoch_seconds, time_zone); |
85 | 1 | break; |
86 | 0 | } |
87 | 1 | case FieldType::OLAP_FIELD_TYPE_DATEV2: |
88 | 1 | extract_epoch_time<ColumnDateV2, DateV2Value<DateV2ValueType>>( |
89 | 1 | source, row, time_zone, &epoch_seconds, µsecond); |
90 | 1 | break; |
91 | 10 | case FieldType::OLAP_FIELD_TYPE_DATETIMEV2: |
92 | 10 | extract_epoch_time<ColumnDateTimeV2, DateV2Value<DateTimeV2ValueType>>( |
93 | 10 | source, row, time_zone, &epoch_seconds, µsecond); |
94 | 10 | break; |
95 | 1 | case FieldType::OLAP_FIELD_TYPE_TIMESTAMPTZ: |
96 | 1 | extract_epoch_time<ColumnTimeStampTz, TimestampTzValue>(source, row, cctz::utc_time_zone(), |
97 | 1 | &epoch_seconds, µsecond); |
98 | 1 | break; |
99 | 1 | default: |
100 | 1 | return Status::InvalidArgument("row ttl source column must be DATE or DATETIME"); |
101 | 15 | } |
102 | | |
103 | 14 | auto epoch_base = checked_multiply(epoch_seconds, 1'000'000); |
104 | 14 | if (!epoch_base) { |
105 | 0 | return epoch_base.error(); |
106 | 0 | } |
107 | 14 | auto epoch_micros = checked_add(*epoch_base, microsecond); |
108 | 14 | if (!epoch_micros) { |
109 | 0 | return epoch_micros.error(); |
110 | 0 | } |
111 | 14 | auto expiration = checked_add(*epoch_micros, duration_us); |
112 | 14 | if (!expiration) { |
113 | 1 | return expiration.error(); |
114 | 1 | } |
115 | 13 | *expiration_us = *expiration; |
116 | 13 | return Status::OK(); |
117 | 14 | } |
118 | | |
119 | | Result<std::optional<int64_t>> convert_row_ttl_time_to_epoch_us( |
120 | | const TabletColumn& source_column, const std::string& source_value, |
121 | 8 | const std::string& timezone) { |
122 | 8 | cctz::time_zone time_zone; |
123 | 8 | if (!TimezoneUtils::find_cctz_time_zone(timezone, time_zone)) { |
124 | 1 | return ResultError(Status::InvalidArgument("invalid time zone for row ttl: {}", timezone)); |
125 | 1 | } |
126 | | |
127 | 7 | DataTypePtr data_type = DataTypeFactory::instance().create_data_type(source_column); |
128 | 7 | MutableColumnPtr source = data_type->create_column(); |
129 | 7 | StringRef value(source_value); |
130 | 7 | Slice slice = value.to_slice(); |
131 | 7 | DataTypeSerDe::FormatOptions options; |
132 | 7 | options.converted_from_string = true; |
133 | 7 | options.timezone = &time_zone; |
134 | 7 | Status status = data_type->get_serde()->deserialize_one_cell_from_json(*source, slice, options); |
135 | 7 | if (!status.ok()) { |
136 | 0 | return ResultError(status); |
137 | 0 | } |
138 | | |
139 | 7 | const IColumn* source_data = source.get(); |
140 | 7 | if (const auto* nullable = check_and_get_column<ColumnNullable>(source_data)) { |
141 | 7 | if (nullable->is_null_at(0)) { |
142 | 1 | return std::optional<int64_t> {}; |
143 | 1 | } |
144 | 6 | source_data = &nullable->get_nested_column(); |
145 | 6 | } |
146 | 6 | int64_t expiration_us = 0; |
147 | 6 | status = calculate_row_ttl_expiration_us(*source_data, source_column.type(), 0, time_zone, 0, |
148 | 6 | &expiration_us); |
149 | 6 | if (!status.ok()) { |
150 | 1 | return ResultError(status); |
151 | 1 | } |
152 | 5 | return std::optional<int64_t> {expiration_us}; |
153 | 6 | } |
154 | | |
155 | | Result<std::optional<int64_t>> calculate_row_ttl_expiration_us(RowTtlOperation operation, |
156 | | std::optional<int64_t> value, |
157 | 13 | int64_t now_us) { |
158 | 13 | if (operation == RowTtlOperation::PERSIST) { |
159 | 2 | return std::optional<int64_t> {}; |
160 | 2 | } |
161 | 11 | if (!value.has_value()) { |
162 | 1 | return ResultError(Status::InvalidArgument("row ttl operation requires a value")); |
163 | 1 | } |
164 | | |
165 | 10 | int64_t multiplier = 0; |
166 | 10 | bool relative = false; |
167 | 10 | switch (operation) { |
168 | 2 | case RowTtlOperation::EXPIRE: |
169 | 2 | multiplier = 1'000'000; |
170 | 2 | relative = true; |
171 | 2 | break; |
172 | 4 | case RowTtlOperation::PEXPIRE: |
173 | 4 | multiplier = 1'000; |
174 | 4 | relative = true; |
175 | 4 | break; |
176 | 3 | case RowTtlOperation::EXPIREAT: |
177 | 3 | multiplier = 1'000'000; |
178 | 3 | break; |
179 | 1 | case RowTtlOperation::PEXPIREAT: |
180 | 1 | multiplier = 1'000; |
181 | 1 | break; |
182 | 0 | case RowTtlOperation::PERSIST: |
183 | 0 | __builtin_unreachable(); |
184 | 10 | } |
185 | | |
186 | 10 | auto converted = checked_multiply(*value, multiplier); |
187 | 10 | if (!converted) { |
188 | 1 | return ResultError(converted.error()); |
189 | 1 | } |
190 | 9 | if (!relative) { |
191 | 3 | return std::optional<int64_t> {*converted}; |
192 | 3 | } |
193 | 6 | auto expiration = checked_add(now_us, *converted); |
194 | 6 | if (!expiration) { |
195 | 1 | return ResultError(expiration.error()); |
196 | 1 | } |
197 | 5 | return std::optional<int64_t> {*expiration}; |
198 | 6 | } |
199 | | |
200 | | Status apply_row_ttl_update(MutableColumnPtr& ttl_column, size_t row, RowTtlOperation operation, |
201 | 5 | std::optional<int64_t> value, int64_t now_us) { |
202 | 5 | if (row >= ttl_column->size()) { |
203 | 1 | return Status::InvalidArgument("row ttl target row {} is out of range {}", row, |
204 | 1 | ttl_column->size()); |
205 | 1 | } |
206 | 4 | auto* nullable = typeid_cast<ColumnNullable*>(ttl_column.get()); |
207 | 4 | if (nullable == nullptr) { |
208 | 1 | return Status::InvalidArgument("row ttl column must be nullable"); |
209 | 1 | } |
210 | 3 | auto* values = typeid_cast<ColumnInt64*>(&nullable->get_nested_column()); |
211 | 3 | if (values == nullptr) { |
212 | 1 | return Status::InvalidArgument("row ttl column must have Int64 nested values"); |
213 | 1 | } |
214 | | |
215 | 2 | auto expiration = calculate_row_ttl_expiration_us(operation, value, now_us); |
216 | 2 | if (!expiration) { |
217 | 0 | return expiration.error(); |
218 | 0 | } |
219 | 2 | auto& null_map = nullable->get_null_map_data(); |
220 | 2 | if (!expiration->has_value()) { |
221 | 1 | null_map[row] = 1; |
222 | 1 | values->get_data()[row] = 0; |
223 | 1 | return Status::OK(); |
224 | 1 | } |
225 | 1 | null_map[row] = 0; |
226 | 1 | values->get_data()[row] = **expiration; |
227 | 1 | return Status::OK(); |
228 | 2 | } |
229 | | |
230 | | Status apply_row_ttl_update(const RowLocation& location, RowTtlOperation operation, |
231 | | std::optional<int64_t> value, int64_t now_us, |
232 | 1 | const RowTtlLocationWriter& writer) { |
233 | 1 | auto expiration = calculate_row_ttl_expiration_us(operation, value, now_us); |
234 | 1 | if (!expiration) { |
235 | 0 | return expiration.error(); |
236 | 0 | } |
237 | 1 | return writer(location, *expiration); |
238 | 1 | } |
239 | | |
240 | | Status build_row_visibility_filter(const Block& block, const TabletSchema& tablet_schema, |
241 | | bool apply_delete_sign, bool apply_row_ttl, int64_t now_us, |
242 | 508 | RowVisibilityFilter* filter) { |
243 | 508 | filter->selection.resize_fill(block.rows(), 1); |
244 | 508 | filter->rows_deleted = 0; |
245 | | |
246 | 508 | if (apply_delete_sign) { |
247 | 503 | const int delete_sign_position = block.get_position_by_name(DELETE_SIGN); |
248 | 503 | DORIS_CHECK_GE(delete_sign_position, 0); |
249 | 503 | const auto* delete_sign = check_and_get_column<ColumnInt8>( |
250 | 503 | block.get_by_position(delete_sign_position).column.get()); |
251 | 503 | DORIS_CHECK(delete_sign != nullptr); |
252 | 503 | const auto& delete_sign_data = delete_sign->get_data(); |
253 | 981k | for (size_t row = 0; row < block.rows(); ++row) { |
254 | 980k | if (delete_sign_data[row] != 0) { |
255 | 143 | filter->selection[row] = 0; |
256 | 143 | ++filter->rows_deleted; |
257 | 143 | } |
258 | 980k | } |
259 | 503 | } |
260 | | |
261 | 508 | if (!apply_row_ttl) { |
262 | 503 | return Status::OK(); |
263 | 503 | } |
264 | | |
265 | 5 | const int ttl_position = block.get_position_by_name(TTL_COL); |
266 | 5 | DORIS_CHECK_GE(ttl_position, 0); |
267 | 5 | const auto* nullable = |
268 | 5 | check_and_get_column<ColumnNullable>(block.get_by_position(ttl_position).column.get()); |
269 | 5 | DORIS_CHECK(nullable != nullptr); |
270 | 5 | const auto& null_map = nullable->get_null_map_data(); |
271 | 5 | const bool source_time = row_ttl_uses_source_time(tablet_schema); |
272 | 5 | const int64_t duration_us = tablet_schema.row_ttl_duration_us(); |
273 | 5 | if (source_time && duration_us < 0) { |
274 | 1 | return Status::InvalidArgument("row ttl duration is missing from temporal tablet schema"); |
275 | 1 | } |
276 | 4 | const auto* direct_expiration = source_time |
277 | 4 | ? nullptr |
278 | 4 | : check_and_get_column<ColumnInt64>( |
279 | 3 | &nullable->get_nested_column()); |
280 | 4 | DORIS_CHECK(source_time || direct_expiration != nullptr); |
281 | 4 | const cctz::time_zone local_time_zone = cctz::local_time_zone(); |
282 | 18 | for (size_t row = 0; row < block.rows(); ++row) { |
283 | 14 | if (!filter->selection[row] || null_map[row]) { |
284 | 5 | continue; |
285 | 5 | } |
286 | 9 | int64_t expiration_us = 0; |
287 | 9 | if (source_time) { |
288 | 2 | RETURN_IF_ERROR(calculate_row_ttl_expiration_us( |
289 | 2 | nullable->get_nested_column(), |
290 | 2 | tablet_schema.column(tablet_schema.ttl_col_idx()).type(), row, |
291 | 2 | local_time_zone, duration_us, &expiration_us)); |
292 | 7 | } else { |
293 | 7 | expiration_us = direct_expiration->get_data()[row]; |
294 | 7 | } |
295 | 9 | if (expiration_us <= now_us) { |
296 | 5 | filter->selection[row] = 0; |
297 | 5 | ++filter->rows_deleted; |
298 | 5 | } |
299 | 9 | } |
300 | 4 | return Status::OK(); |
301 | 4 | } |
302 | | |
303 | 504 | Status filter_block_by_row_visibility(Block* block, const IColumn::Filter& filter) { |
304 | 504 | if (filter.size() != block->rows()) { |
305 | 1 | return Status::InvalidArgument("row visibility filter size {} does not match block rows {}", |
306 | 1 | filter.size(), block->rows()); |
307 | 1 | } |
308 | 503 | RETURN_IF_CATCH_EXCEPTION(Block::filter_block_internal(block, filter)); |
309 | 503 | return Status::OK(); |
310 | 503 | } |
311 | | |
312 | | Status copy_row_ttl_source(Block* block, const TabletSchema& tablet_schema, int32_t source_cid, |
313 | 1 | const std::vector<bool>& rows_to_copy, size_t row_pos) { |
314 | 1 | DORIS_CHECK(tablet_schema.has_ttl_col()); |
315 | 1 | DORIS_CHECK(source_cid >= 0); |
316 | 1 | DORIS_CHECK(row_pos + rows_to_copy.size() <= block->rows()); |
317 | | |
318 | 1 | const ColumnWithTypeAndName& source_entry = block->get_by_position(source_cid); |
319 | 1 | const auto* nullable_source = check_and_get_column<ColumnNullable>(source_entry.column.get()); |
320 | 1 | const NullMap* source_null_map = |
321 | 1 | nullable_source == nullptr ? nullptr : &nullable_source->get_null_map_data(); |
322 | 1 | const ColumnPtr source = nullable_source == nullptr ? source_entry.column |
323 | 1 | : nullable_source->get_nested_column_ptr(); |
324 | | |
325 | 1 | const int32_t ttl_cid = tablet_schema.ttl_col_idx(); |
326 | 1 | MutableColumnPtr mutable_ttl = IColumn::mutate(block->get_by_position(ttl_cid).column); |
327 | 1 | auto& ttl = assert_cast<ColumnNullable&>(*mutable_ttl); |
328 | 1 | auto& ttl_data = ttl.get_nested_column(); |
329 | 1 | auto& ttl_null_map = ttl.get_null_map_data(); |
330 | | |
331 | 3 | for (size_t mask_row = 0; mask_row < rows_to_copy.size(); ++mask_row) { |
332 | 2 | if (!rows_to_copy[mask_row]) { |
333 | 0 | continue; |
334 | 0 | } |
335 | 2 | const size_t row = row_pos + mask_row; |
336 | 2 | if (source_null_map != nullptr && (*source_null_map)[row]) { |
337 | 1 | ttl_null_map[row] = 1; |
338 | 1 | ttl_data.replace_column_data(*source, row, row); |
339 | 1 | continue; |
340 | 1 | } |
341 | 1 | ttl_data.replace_column_data(*source, row, row); |
342 | 1 | ttl_null_map[row] = 0; |
343 | 1 | } |
344 | 1 | block->replace_by_position(ttl_cid, std::move(mutable_ttl)); |
345 | 1 | return Status::OK(); |
346 | 1 | } |
347 | | |
348 | | bool should_gc_row_ttl(const TabletSchema& tablet_schema, bool enable_unique_key_merge_on_write, |
349 | 1.30M | ReaderType reader_type, const Version& version) { |
350 | 1.30M | if (!tablet_schema.has_ttl_col() || tablet_schema.keys_type() == KeysType::AGG_KEYS) { |
351 | 1.30M | return false; |
352 | 1.30M | } |
353 | 18.4E | if (reader_type == ReaderType::READER_BINLOG_COMPACTION || |
354 | 18.4E | reader_type == ReaderType::READER_COLD_DATA_COMPACTION) { |
355 | 2 | return false; |
356 | 2 | } |
357 | | |
358 | 18.4E | const bool full_coverage = |
359 | 18.4E | reader_type == ReaderType::READER_FULL_COMPACTION || |
360 | 18.4E | (reader_type == ReaderType::READER_BASE_COMPACTION && version.first == 0); |
361 | 18.4E | if (tablet_schema.keys_type() == KeysType::UNIQUE_KEYS && !enable_unique_key_merge_on_write) { |
362 | 5 | return full_coverage; |
363 | 5 | } |
364 | 18.4E | return reader_type == ReaderType::READER_CUMULATIVE_COMPACTION || |
365 | 18.4E | reader_type == ReaderType::READER_BASE_COMPACTION || |
366 | 18.4E | reader_type == ReaderType::READER_FULL_COMPACTION || |
367 | 18.4E | reader_type == ReaderType::READER_SEGMENT_COMPACTION; |
368 | 18.4E | } |
369 | | |
370 | | } // namespace doris |