Continue support for sparse serialization of Nullable columns#88999
Continue support for sparse serialization of Nullable columns#88999CurtizJ merged 26 commits intoClickHouse:masterfrom
Conversation
|
Workflow [PR], commit [5ec8a84] Summary: ❌
|
|
|
There was a problem hiding this comment.
Thank you! Looks good in general.
We also need to bump the version of the protocol and remove Sparse from the Nullable columns here for old clients. Otherwise, we break compatibility somehow like this:
SELECT s
FROM test_sparse
┌────s─┐
1. │ ᴺᵁᴸᴸ │
2. │ ᴺᵁᴸᴸ │
3. │ ᴺᵁᴸᴸ │
4. │ ᴺᵁᴸᴸ │
5. │ ᴺᵁᴸᴸ │
└──────┘
Error on processing query: Code: 100. DB::Exception: Unknown packet 111 from server localhost:9000. (UNKNOWN_PACKET_FROM_SERVER) (version 25.12.1.1)
Please also add a compatibility test.
| struct DeserializeStateSparse : public ISerialization::DeserializeBinaryBulkState | ||
| { | ||
| /// Column offsets from previous read. | ||
| ColumnPtr column_offsets; |
There was a problem hiding this comment.
I missed something. Why do we need to save offsets from the previous read now?
There was a problem hiding this comment.
This ensures that SerializationSparse and SerializationSparseNullMap can share a common sparse offset.
8cbd360
| } | ||
|
|
||
| settings.path.pop_back(); | ||
| return state_sparse.column_offsets->size() - old_size; |
There was a problem hiding this comment.
Seeing a crash here in HEAD:
2025.12.09 10:40:28.775269 [ 125 ] {} <Fatal> BaseDaemon: 2. ./ci/tmp/build/./src/DataTypes/Serializations/SerializationSparse.cpp:216: DB::(anonymous namespace)::readOrGetCachedSparseOffsets(DB::ISerialization::DeserializeBinaryBulkSettings&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*, DB::(anonymous namespace)::DeserializeStateSparse&, unsigned long, unsigned long, unsigned long, unsigned long&, unsigned long&) @ 0x000000001a773898
2025.12.09 10:40:28.786233 [ 125 ] {} <Fatal> BaseDaemon: 3. ./ci/tmp/build/./src/DataTypes/Serializations/SerializationSparse.cpp:400: DB::SerializationSparse::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const @ 0x000000001a773017
2025.12.09 10:40:28.847084 [ 125 ] {} <Fatal> BaseDaemon: 4.0. inlined from ./src/DataTypes/Serializations/SerializationNamed.cpp:86: DB::SerializationNamed::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const
2025.12.09 10:40:28.847165 [ 125 ] {} <Fatal> BaseDaemon: 4. ./ci/tmp/build/./src/DataTypes/Serializations/SerializationTuple.cpp:811: DB::SerializationTuple::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const @ 0x000000001a79a77a
There was a problem hiding this comment.
More crashes:
2025.12.09 19:46:14.844169 [ 125 ] {} <Fatal> BaseDaemon: Stack trace: 0x000000001a797a58 0x000000001a7971d7 0x000000001a7be93a 0x000000001d0c4f0b 0x000000001d081299 0x000000001d08b064 0x000000001d0c97c4 0x000000001d094185 0x000000001d0d470c 0x000000001d0d8483 0x000000001d0da2b1 0x000000001e0bca92 0x000000001da878c1 0x000000001daa4ddd 0x000000001da96f1b 0x000000001da9b083 0x00000000168501cb 0x0000000016857366 0x000000001684d31f 0x0000000016854b5a 0x00007cbb66081ac3 0x00007cbb661138c0
2025.12.09 19:46:14.854508 [ 125 ] {} <Fatal> BaseDaemon: 2. ./ci/tmp/build/./src/DataTypes/Serializations/SerializationSparse.cpp:216: DB::(anonymous namespace)::readOrGetCachedSparseOffsets(DB::ISerialization::DeserializeBinaryBulkSettings&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*, DB::(anonymous namespace)::DeserializeStateSparse&, unsigned long, unsigned long, unsigned long, unsigned long&, unsigned long&) @ 0x000000001a797a58
2025.12.09 19:46:14.863077 [ 125 ] {} <Fatal> BaseDaemon: 3. ./ci/tmp/build/./src/DataTypes/Serializations/SerializationSparse.cpp:400: DB::SerializationSparse::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const @ 0x000000001a7971d7
2025.12.09 19:46:14.874088 [ 125 ] {} <Fatal> BaseDaemon: 4.0. inlined from ./src/DataTypes/Serializations/SerializationNamed.cpp:86: DB::SerializationNamed::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const
2025.12.09 19:46:14.874088 [ 125 ] {} <Fatal> BaseDaemon: 4.0. inlined from ./src/DataTypes/Serializations/SerializationNamed.cpp:86: DB::SerializationNamed::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const
2025.12.09 19:46:14.884197 [ 125 ] {} <Fatal> BaseDaemon: 5.0. inlined from ./ci/tmp/build/./src/Storages/MergeTree/MergeTreeReaderWide.cpp:573: DB::MergeTreeReaderWide::readData(DB::NameAndTypePair const&, std::shared_ptr<DB::ISerialization const> const&, COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, bool, unsigned long, unsigned long, unsigned long, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>&, std::unordered_map<String, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>>>>&)
2025.12.09 19:46:14.884264 [ 125 ] {} <Fatal> BaseDaemon: 5. ./ci/tmp/build/./src/Storages/MergeTree/MergeTreeReaderWide.cpp:184: DB::MergeTreeReaderWide::readRows(unsigned long, unsigned long, bool, unsigned long, unsigned long, std::vector<COW<DB::IColumn>::immutable_ptr<DB::IColumn>, std::allocator<COW<DB::IColumn>::immutable_ptr<DB::IColumn>>>&) @ 0x000000001d0c4f0b
2025.12.09 19:46:14.898525 [ 125 ] {} <Fatal> BaseDaemon: 6.0. inlined from ./ci/tmp/build/./src/Storages/MergeTree/MergeTreeRangeReader.cpp:130: DB::MergeTreeRangeReader::DelayedStream::readRows(std::vector<COW<DB::IColumn>::immutable_ptr<DB::IColumn>, std::allocator<COW<DB::IColumn>::immutable_ptr<DB::IColumn>>>&, unsigned long)
2025.12.09 19:46:14.898592 [ 125 ] {} <Fatal> BaseDaemon: 6. ./ci/tmp/build/./src/Storages/MergeTree/MergeTreeRangeReader.cpp:202: DB::MergeTreeRangeReader::DelayedStream::finalize(std::vector<COW<DB::IColumn>::immutable_ptr<DB::IColumn>, std::allocator<COW<DB::IColumn>::immutable_ptr<DB::IColumn>>>&) @ 0x000000001d081299
...
There was a problem hiding this comment.
Thanks for reporting this. Where can I find the full log that includes the crash?
There was a problem hiding this comment.
Let me try to get a sample without private information of both the stacktrace and query logs
There was a problem hiding this comment.
Cool. BTW, is there any crash report in the public repo for this issue? It seems this patch was merged 10 days ago. Did the first crash only appear yesterday?
There was a problem hiding this comment.
I've been able to extract enough information to investigate this issue. One thing that I see in the servers after the crash are failures to merge:
2025.12.09 19:46:45.329702 [ 943 ] {} <Error> default.enriched_identities (824ad30d-b455-47a7-a508-16cd3a13df66): Part data/824ad30d-b455-47a7-a508-16cd3a13df66/all_0_0_1/ is broken and needs manual correction. Reason: Code: 226. DB::Exception: No stream (BaseIdentity%2EdeletedAt.
sparse.idx.bin) file checksum for column BaseIdentity in part data/824ad30d-b455-47a7-a508-16cd3a13df66/all_0_0_1/: name: all_0_0_1, parent_part_name: , getMarkSizeInBytes: 24, getMarksCount: 60, index_granularity_info: [mark_type: [adaptive: true, compressed: true, with_substream
s: false, part_type: Wide], index_granularity_bytes: 10485760, fixed_index_granularity: 8192], index_granularity: [Adaptive(marks_rows_partial_sums: [5651, 11921, 18112, 24142, 29452, 34420, 39278, 44051, 48667, 51768, 56989, 63417, 70956, 78489, 86020, 93429, 100550, 107805, 1159
97, 124189, 132381, 140573, 148765, 155021, 160516, 166697, 172337, 176661, 181261, 185961, 191301, 196037, 199925, 203410, 206096, 211602, 216275, 221730, 227526, 233764, 238714, 243204, 247557, 251911, 256368, 260970, 265350, 270115, 276227, 282439, 288643, 294849, 301059, 30726
5, 313478, 319696, 326751, 333827, 338641, 338641])], part_state: [type: Wide, state: Temporary, is_unexpected_local_part: false, is_frozen: false, is_duplicate: false, is_temp: false]. (NO_FILE_IN_DATA_PART), Stack trace (when copying this message, always include the lines below)
DeletedAt is declared as deletedAt Nullable(String) and it seems to be using the sparse type.
Also, shouldn't this serialization be off by default / based on compatiblity? I guess it's not compatible with previous releases, so the moment it's used all servers must be able to read it.
I've asked for help from the QA team to find a repro (and something without internal schemas or data, which I'm yet trying to figure out what can be shared or not).
There was a problem hiding this comment.
Also, shouldn't this serialization be off by default / based on compatiblity? I guess it's not compatible with previous releases, so the moment it's used all servers must be able to read it.
It's likely because we enable randomization on this setting.
ClickHouse/tests/clickhouse-test
Line 1262 in 7d0b74a
There was a problem hiding this comment.
AFAICT no, nullable_serialization_version is not randomized or used on those test services, but it appears like it's being used anyway.
I'm not sure if it's related, but it seems they might comer from using tuples:
2025.12.09 19:46:10.292075 [ 125 ] {} <Fatal> BaseDaemon: 2. ./ci/tmp/build/./src/DataTypes/Serializations/SerializationSparse.cpp:216: DB::(anonymous namespace)::readOrGetCachedSparseOffsets(DB::ISerialization::DeserializeBinaryBulkSettings&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*, DB::(anonymous namespace)::DeserializeStateSparse&, unsigned long, unsigned long, unsigned long, unsigned long&, unsigned long&) @ 0x000000001a797a58
2025.12.09 19:46:10.301328 [ 125 ] {} <Fatal> BaseDaemon: 3. ./ci/tmp/build/./src/DataTypes/Serializations/SerializationSparse.cpp:400: DB::SerializationSparse::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const @ 0x000000001a7971d7
2025.12.09 19:46:10.325054 [ 125 ] {} <Fatal> BaseDaemon: 4.0. inlined from ./src/DataTypes/Serializations/SerializationNamed.cpp:86: DB::SerializationNamed::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const
2025.12.09 19:46:10.325118 [ 125 ] {} <Fatal> BaseDaemon: 4. ./ci/tmp/build/./src/DataTypes/Serializations/SerializationTuple.cpp:811: DB::SerializationTuple::deserializeBinaryBulkWithMultipleStreams(COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, unsigned long, DB::ISerialization::DeserializeBinaryBulkSettings&, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>&, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>*) const @ 0x000000001a7be93a
2025.12.09 19:46:10.345102 [ 125 ] {} <Fatal> BaseDaemon: 5.0. inlined from ./ci/tmp/build/./src/Storages/MergeTree/MergeTreeReaderWide.cpp:573: DB::MergeTreeReaderWide::readData(DB::NameAndTypePair const&, std::shared_ptr<DB::ISerialization const> const&, COW<DB::IColumn>::immutable_ptr<DB::IColumn>&, unsigned long, bool, unsigned long, unsigned long, unsigned long, std::unordered_map<String, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::unique_ptr<DB::ISerialization::ISubstreamsCacheElement, std::default_delete<DB::ISerialization::ISubstreamsCacheElement>>>>>&, std::unordered_map<String, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>, std::hash<String>, std::equal_to<String>, std::allocator<std::pair<String const, std::shared_ptr<DB::ISerialization::DeserializeBinaryBulkState>>>>&)
I wonder if some default is wrong somewhere
There was a problem hiding this comment.
Interesting. I'll investigate.
There was a problem hiding this comment.
I’ve successfully reproduced the tuple sparse/nullable inconsistency. I’ll prepare and submit a fix later today.
CREATE TABLE t (id UInt64, n Nullable(UInt64), s Nullable(String), t Tuple(a Nullable(String), b Nullable(UInt64)))
ENGINE = MergeTree ORDER BY ()
SETTINGS index_granularity = 10, min_bytes_for_wide_part = 0, ratio_of_defaults_for_sparse_serialization = 0.1, serialization_info_version = 'with_types', nullable_serialization_version = 'basic';
insert into t(id) values (1), (2), (3);
SELECT
column,
substreams
FROM system.parts_columns
WHERE (database = currentDatabase()) AND (`table` = 't') AND active;
┌─column─┬─substreams────────────────────────────────────────────────────────────────────────┐
1. │ id │ ['id'] │
2. │ n │ ['n.null','n'] │
3. │ s │ ['s.null','s'] │
4. │ t │ ['t%2Ea.sparse.idx','t%2Ea.null','t%2Ea','t%2Eb.sparse.idx','t%2Eb.null','t%2Eb'] │
└────────┴───────────────────────────────────────────────────────────────────────────────────┘
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Added support of sparse serialization for columns of Nullable type. This continues #44539.
Details
This PR continues the work from #44539 to extend sparse serialization support for Nullable columns.