Files
datastore/src/Storages/MergeTree/MergeTreeDataPartWriterOnDisk.cpp
T

731 lines
32 KiB
C++

#include <Storages/MergeTree/MergeTreeDataPartWriterOnDisk.h>
#include <Storages/MergeTree/DataPartStorageOnDiskBase.h>
#include <Storages/MergeTree/MergeTreeData.h>
#include <Storages/MergeTree/MergeTreeIndexGranularity.h>
#include <Storages/MergeTree/MergeTreeIndexText.h>
#include <Storages/MergeTree/MergeTreeIndicesSerialization.h>
#include <Storages/MergeTree/MergeTreeSettings.h>
#include <Storages/MergeTree/ParallelSyncFiles.h>
#include <Interpreters/Context.h>
#include <Interpreters/ProcessList.h>
#include <Common/CurrentThread.h>
#include <Common/ElapsedTimeProfileEventIncrement.h>
#include <Common/MemoryTrackerBlockerInThread.h>
#include <Common/StringUtils.h>
#include <Common/escapeForFileName.h>
#include <Common/logger_useful.h>
#include <Compression/CompressionFactory.h>
#include <IO/HashingWriteBuffer.h>
#include <IO/NullWriteBuffer.h>
#include <IO/PackedFilesWriter.h>
#include <Poco/String.h>
#include <base/find_symbols.h>
namespace ProfileEvents
{
extern const Event MergeTreeDataWriterSkipIndicesCalculationMicroseconds;
}
namespace DB
{
namespace MergeTreeSetting
{
extern const MergeTreeSettingsUInt64 index_granularity;
extern const MergeTreeSettingsUInt64 index_granularity_bytes;
extern const MergeTreeSettingsUInt64 packed_skip_index_max_bytes;
}
namespace ErrorCodes
{
extern const int LOGICAL_ERROR;
}
MergeTreeDataPartWriterOnDisk::MergeTreeDataPartWriterOnDisk(
const String & data_part_name_,
const String & logger_name_,
const SerializationByName & serializations_,
MutableDataPartStoragePtr data_part_storage_,
const MergeTreeIndexGranularityInfo & index_granularity_info_,
const MergeTreeSettingsPtr & storage_settings_,
const NamesAndTypesList & columns_list_,
const StorageMetadataPtr & metadata_snapshot_,
const MergeTreeIndices & indices_to_recalc_,
const String & marks_file_extension_,
const CompressionCodecPtr & default_codec_,
const MergeTreeWriterSettings & settings_,
MergeTreeIndexGranularityPtr index_granularity_,
WrittenOffsetSubstreams * written_offset_substreams_)
: IMergeTreeDataPartWriter(
data_part_name_, serializations_, data_part_storage_, index_granularity_info_,
storage_settings_, columns_list_, metadata_snapshot_, settings_, std::move(index_granularity_))
, skip_indices(indices_to_recalc_)
, marks_file_extension(marks_file_extension_)
, default_codec(default_codec_)
, compute_granularity(index_granularity->empty())
, compress_primary_key(settings.compress_primary_key)
, written_offset_substreams(written_offset_substreams_)
, execution_stats(skip_indices.size())
, log(getLogger(logger_name_ + " (DataPartWriter)"))
{
if (settings.blocks_are_granules_size && !index_granularity->empty())
throw Exception(ErrorCodes::LOGICAL_ERROR,
"Can't take information about index granularity from blocks, when non empty index_granularity array specified");
/// We don't need to check if it exists or not, createDirectories doesn't throw
getDataPartStorage().createDirectories();
if (settings.rewrite_primary_key)
initPrimaryIndex();
initSkipIndices();
}
void MergeTreeDataPartWriterOnDisk::cancel() noexcept
{
if (index_file_stream)
index_file_stream->cancel();
if (index_file_hashing_stream)
index_file_hashing_stream->cancel();
if (index_compressor_stream)
index_compressor_stream->cancel();
if (index_source_hashing_stream)
index_source_hashing_stream->cancel();
for (auto & stream : skip_indices_streams_holders)
stream->cancel();
if (skip_indices_packed_file)
skip_indices_packed_file->cancel();
}
size_t MergeTreeDataPartWriterOnDisk::computeIndexGranularity(const Block & block) const
{
return DB::computeIndexGranularity(
block.rows(),
getBlockSizeForGranularity(block),
(*storage_settings)[MergeTreeSetting::index_granularity_bytes],
(*storage_settings)[MergeTreeSetting::index_granularity],
settings.blocks_are_granules_size,
settings.can_use_adaptive_granularity);
}
void MergeTreeDataPartWriterOnDisk::initPrimaryIndex()
{
if (metadata_snapshot->hasPrimaryKey())
{
String index_name = "primary" + getIndexExtension(compress_primary_key);
index_file_stream = getDataPartStorage().writeFile(index_name, DBMS_DEFAULT_BUFFER_SIZE, settings.query_write_settings);
index_file_hashing_stream = std::make_unique<HashingWriteBuffer>(*index_file_stream);
if (compress_primary_key)
{
CompressionCodecPtr primary_key_compression_codec = CompressionCodecFactory::instance().get(settings.primary_key_compression_codec);
index_compressor_stream = std::make_unique<CompressedWriteBuffer>(*index_file_hashing_stream, primary_key_compression_codec, settings.primary_key_compress_block_size);
index_source_hashing_stream = std::make_unique<HashingWriteBuffer>(*index_compressor_stream);
}
const auto & primary_key_types = metadata_snapshot->getPrimaryKey().data_types;
index_serializations.reserve(primary_key_types.size());
for (const auto & type : primary_key_types)
index_serializations.push_back(type->getDefaultSerialization());
}
}
void MergeTreeDataPartWriterOnDisk::initSkipIndices()
{
const size_t packed_spill_threshold = (*storage_settings)[MergeTreeSetting::packed_skip_index_max_bytes];
/// packed_skip_index_max_bytes = 0 disables packing entirely (always per-file).
const bool packing_enabled = packed_spill_threshold > 0;
/// Secondary writers (vertical-merge per-column `MergedColumnOnlyOutputStream`) borrow
/// the outer writer's `PackedFilesWriter` so the two writers don't both try to write
/// `skp_idx.packed`. The owner of the borrowed writer is responsible for finalizing the
/// archive; see `fillSkipIndicesChecksums`.
if (settings.external_packed_skip_indices_writer)
skip_indices_packed_writer_borrowed = settings.external_packed_skip_indices_writer;
else if (packing_enabled)
skip_indices_packed_writer = std::make_unique<PackedFilesWriter>();
if (skip_indices.empty())
return;
ParserCodec codec_parser;
auto ast = parseQuery(codec_parser, "(" + Poco::toUpper(settings.marks_compression_codec) + ")", 0, DBMS_DEFAULT_MAX_PARSER_DEPTH, DBMS_DEFAULT_MAX_PARSER_BACKTRACKS);
CompressionCodecPtr marks_compression_codec = CompressionCodecFactory::instance().get(ast, nullptr);
PackedFilesWriter * packed_writer_for_streams
= skip_indices_packed_writer ? skip_indices_packed_writer.get() : skip_indices_packed_writer_borrowed;
for (const auto & skip_index : skip_indices)
{
auto index_name = skip_index->getFileName();
auto index_substreams = skip_index->getSubstreams();
/// Full-text indices are not supported and never packed: their merge output goes
/// through MergeTextIndexesTask which writes standalone files.
const bool packs_this_index = packing_enabled
&& dynamic_cast<const MergeTreeIndexText *>(skip_index.get()) == nullptr;
auto & index_streams = skip_indices_streams.emplace_back();
for (const auto & index_substream : index_substreams)
{
if (index_substream.extension == String(PackedFilesIO::ARCHIVE_EXTENSION))
throw Exception(ErrorCodes::LOGICAL_ERROR,
"Skip index '{}' uses reserved substream extension '{}'", skip_index->index.name, index_substream.extension);
/// The "logical" stream name (skp_idx_<name>[.suffix]) is what the in-archive virtual
/// file uses; the on-disk per-file substream may use a hashed form to fit filesystem
/// limits. We pass both into the stream's size-adaptive packing:
/// - logical_stream_name: used as the packed_writer virtual filename when the
/// substream stays in the archive (no FS length limit there).
/// - on_disk_stream_name: used as the standalone-file path when the substream
/// spills past the size threshold.
auto logical_stream_name = index_name + index_substream.suffix;
auto on_disk_stream_name = replaceFileNameToHashIfNeeded(logical_stream_name, *storage_settings, data_part_storage.get());
SizeAdaptivePacking packing;
if (packs_this_index)
packing = {
packed_writer_for_streams,
logical_stream_name + index_substream.extension,
logical_stream_name + marks_file_extension,
packed_spill_threshold};
auto stream = std::make_unique<MergeTreeIndexWriterStream>(
on_disk_stream_name,
data_part_storage,
on_disk_stream_name,
index_substream.extension,
on_disk_stream_name,
marks_file_extension,
default_codec,
settings.max_compress_block_size,
marks_compression_codec,
settings.marks_compress_block_size,
settings.query_write_settings,
packing);
index_streams[index_substream.type] = stream.get();
skip_indices_streams_holders.push_back(std::move(stream));
if (settings.save_marks_in_cache)
cached_index_marks.emplace(on_disk_stream_name, std::make_unique<MarksInCompressedFile::PlainArray>());
}
skip_indices_aggregators.push_back(skip_index->createIndexAggregator());
skip_index_accumulated_marks.push_back(0);
}
}
void MergeTreeDataPartWriterOnDisk::calculateAndSerializePrimaryIndexRow(const Block & index_block, size_t row)
{
chassert(index_block.columns() == index_serializations.size());
auto & index_stream = compress_primary_key ? *index_source_hashing_stream : *index_file_hashing_stream;
for (size_t i = 0; i < index_block.columns(); ++i)
{
const auto & column = index_block.getByPosition(i).column;
index_serializations[i]->serializeBinary(*column, row, index_stream, {});
if (settings.save_primary_index_in_memory)
index_columns[i]->insertFrom(*column, row);
}
}
void MergeTreeDataPartWriterOnDisk::calculateAndSerializePrimaryIndex(const Block & primary_index_block, const Granules & granules_to_write)
{
if (!metadata_snapshot->hasPrimaryKey())
return;
{
/** While filling index (index_columns), disable memory tracker.
* Because memory is allocated here (maybe in context of INSERT query),
* but then freed in completely different place (while merging parts), where query memory_tracker is not available.
* And otherwise it will look like excessively growing memory consumption in context of query.
* (observed in long INSERT SELECTs)
*/
MemoryTrackerBlockerInThread temporarily_disable_memory_tracker;
if (settings.save_primary_index_in_memory && index_columns.empty())
{
index_columns = primary_index_block.cloneEmptyColumns();
}
/// Write index. The index contains Primary Key value for each `index_granularity` row.
for (const auto & granule : granules_to_write)
{
if (granule.mark_on_start)
calculateAndSerializePrimaryIndexRow(primary_index_block, granule.start_row);
}
}
/// Store block with last index row to write final mark at the end of column
if (with_final_mark)
last_index_block = primary_index_block;
}
void MergeTreeDataPartWriterOnDisk::calculateAndSerializeSkipIndices(const Block & skip_indexes_block, const Granules & granules_to_write)
{
/// Building a skip index over many granules (e.g. an unbounded `set(0)` index on a
/// high-cardinality column) can run for minutes. The INSERT pipeline only enforces query limits
/// between blocks, so without a check here a KILLed INSERT keeps building the index to completion.
/// checkTimeLimit throws on a cancelled query and on an exceeded max_execution_time in throw mode;
/// it is cheap (an atomic flag plus an elapsed-timer read).
QueryStatusPtr query_status;
if (auto query_context = CurrentThread::tryGetQueryContext())
query_status = query_context->getProcessListElementSafe();
/// Filling and writing skip indices like in MergeTreeDataPartWriterWide::writeColumn
for (size_t i = 0; i < skip_indices.size(); ++i)
{
const auto index_helper = skip_indices[i];
auto & index_streams = skip_indices_streams[i];
for (const auto & granule : granules_to_write)
{
if (query_status)
query_status->checkTimeLimit();
if (skip_index_accumulated_marks[i] == index_helper->index.granularity)
{
auto index_granule = skip_indices_aggregators[i]->getGranuleAndReset();
index_granule->serializeBinaryWithMultipleStreams(index_streams);
skip_index_accumulated_marks[i] = 0;
}
if (skip_indices_aggregators[i]->empty() && granule.mark_on_start)
{
skip_indices_aggregators[i] = index_helper->createIndexAggregator();
for (const auto & [type, stream] : index_streams)
{
auto & marks_out = stream->compress_marks ? stream->marks_compressed_hashing : stream->marks_hashing;
if (stream->compressed_hashing.offset() >= settings.min_compress_block_size)
stream->compressed_hashing.next();
MarkInCompressedFile mark{stream->plain_hashing.count(), stream->compressed_hashing.offset()};
writeBinaryLittleEndian(mark.offset_in_compressed_file, marks_out);
writeBinaryLittleEndian(mark.offset_in_decompressed_block, marks_out);
/// Actually this numbers is redundant, but we have to store them
/// to be compatible with the normal .mrk2 file format
if (settings.can_use_adaptive_granularity)
writeBinaryLittleEndian(1UL, marks_out);
if (auto it = cached_index_marks.find(stream->escaped_column_name); it != cached_index_marks.end())
it->second->push_back(mark);
}
}
ProfileEventTimeIncrement<Microseconds> watch(ProfileEvents::MergeTreeDataWriterSkipIndicesCalculationMicroseconds);
size_t pos = granule.start_row;
skip_indices_aggregators[i]->update(skip_indexes_block, &pos, granule.rows_to_write);
if (granule.is_complete)
++skip_index_accumulated_marks[i];
execution_stats.skip_indices_build_us[i] += watch.elapsed();
}
}
}
void MergeTreeDataPartWriterOnDisk::fillPrimaryIndexChecksums(MergeTreeData::DataPart::Checksums & checksums)
{
bool write_final_mark = (with_final_mark && data_written);
if (write_final_mark && compute_granularity)
index_granularity->appendMark(0);
if (index_file_hashing_stream)
{
if (write_final_mark && !last_index_block.empty())
{
MemoryTrackerBlockerInThread temporarily_disable_memory_tracker;
calculateAndSerializePrimaryIndexRow(last_index_block, last_index_block.rows() - 1);
}
last_index_block.clear();
if (compress_primary_key)
{
index_source_hashing_stream->finalize();
index_compressor_stream->finalize();
}
index_file_hashing_stream->finalize();
String index_name = "primary" + getIndexExtension(compress_primary_key);
if (compress_primary_key)
{
checksums.files[index_name].is_compressed = true;
checksums.files[index_name].uncompressed_size = index_source_hashing_stream->count();
checksums.files[index_name].uncompressed_hash = index_source_hashing_stream->getHash();
}
checksums.files[index_name].file_size = index_file_hashing_stream->count();
checksums.files[index_name].file_hash = index_file_hashing_stream->getHash();
index_file_stream->preFinalize();
}
}
void MergeTreeDataPartWriterOnDisk::finishPrimaryIndexSerialization(bool sync)
{
if (index_file_hashing_stream)
{
index_file_stream->finalize();
if (sync)
index_file_stream->sync();
if (compress_primary_key)
{
index_source_hashing_stream = nullptr;
index_compressor_stream = nullptr;
}
index_file_hashing_stream = nullptr;
/// Release the primary index (`primary.cidx` / `primary.idx`) file descriptor now that the data
/// has been flushed and synced, so it is not held open across the part-directory rename. See the
/// detailed rationale in MergeTreeDataPartWriterCompact::finishDataSerialization and
/// https://github.com/ClickHouse/ClickHouse/issues/56288. The wrapper streams above were reset
/// first, so no live buffer references index_file_stream.
index_file_stream = nullptr;
}
}
void MergeTreeDataPartWriterOnDisk::fillSkipIndicesChecksums(MergeTreeData::DataPart::Checksums & checksums)
{
for (size_t i = 0; i < skip_indices.size(); ++i)
{
if (!skip_indices_aggregators[i]->empty())
{
auto & index_streams = skip_indices_streams[i];
auto index_granule = skip_indices_aggregators[i]->getGranuleAndReset();
index_granule->serializeBinaryWithMultipleStreams(index_streams);
}
}
for (size_t i = 0; i < skip_indices_streams.size(); ++i)
{
const auto & skip_index = skip_indices[i];
const auto & index_substreams = skip_index->getSubstreams();
const String logical_index_name = skip_index->getFileName();
size_t substream_idx = 0;
for (const auto & [type, stream] : skip_indices_streams[i])
{
/// preFinalize drains the chain above (compressor + hashing) so the
/// size-adaptive packing buffer at the bottom of the stream has seen all bytes and
/// the spilled-vs-packed decision is final.
stream->preFinalize();
if (stream->isPacked())
{
/// Packed substream: the reader looks up its marks under the LOGICAL
/// (unhashed) stream name because checksums.txt has no per-substream entry
/// for it to disambiguate against. cached_index_marks was emplaced under the
/// on-disk (possibly hashed) name -- if those differ (long index name +
/// replace_long_file_name_to_hash) the prewarmed marks never get hit. Re-key
/// the entry under the logical name so prewarm survives.
const String & on_disk_key = stream->escaped_column_name;
const String logical_key = logical_index_name + index_substreams[substream_idx].suffix;
if (on_disk_key != logical_key)
{
auto node = cached_index_marks.extract(on_disk_key);
if (!node.empty())
{
node.key() = logical_key;
cached_index_marks.insert(std::move(node));
}
}
/// Substream fit under the spill threshold: bytes are still buffered in memory;
/// finalize hands them to skip_indices_packed_writer. No per-file checksum
/// entry -- the archive's single checksum covers it.
stream->finalize();
/// Record uncompressed size in the archive index so the size accounting doesn't
/// have to read the payload back. Virtual name matches the one passed above.
if (MergeTreeIndexSubstream::isCompressed(type))
{
PackedFilesWriter * packed_writer =
skip_indices_packed_writer ? skip_indices_packed_writer.get() : skip_indices_packed_writer_borrowed;
packed_writer->setUncompressedSize(logical_key + index_substreams[substream_idx].extension, stream->getDataUncompressedSize());
}
}
else
{
/// Substream spilled to a standalone per-file write on data_part_storage.
/// Emit per-file checksum entries; the underlying file will be finalized in
/// finishSkipIndicesSerialization below.
stream->addToChecksums(checksums, MergeTreeIndexSubstream::isCompressed(type));
}
++substream_idx;
}
}
if (skip_indices_packed_writer && skip_indices_packed_writer->hasModifiedFiles())
{
const String packed_filename{SKIP_INDICES_PACKED_FILENAME};
skip_indices_packed_file = getDataPartStorage().writeFile(packed_filename, DBMS_DEFAULT_BUFFER_SIZE, settings.query_write_settings);
HashingWriteBuffer packed_hashing(*skip_indices_packed_file);
auto [packed_index, _] = skip_indices_packed_writer->finalize(packed_hashing, {}, PackedFilesIO::VERSION_WITH_UNCOMPRESSED_SIZE);
packed_hashing.finalize();
auto & checksum = checksums.files[packed_filename];
checksum.file_size = packed_hashing.count();
checksum.file_hash = packed_hashing.getHash();
skip_indices_packed_file->preFinalize();
/// Seed the storage's PackedFilesReader from the in-memory archive index so the per-part
/// secondary-indices size accounting (which runs between preFinalize and the actual
/// finalize) can answer existsFile / getFileSize without touching disk. On object-storage
/// disks the archive file isn't visible until the underlying multipart upload completes,
/// which only happens at finalize time; without this seed the accounting caches 0 for
/// every packed substream.
if (auto * disk_storage = dynamic_cast<DataPartStorageOnDiskBase *>(&getDataPartStorage()))
disk_storage->seedSkipIndicesPackedReader(packed_index);
}
}
void MergeTreeDataPartWriterOnDisk::preloadPackedSkipIndicesArchive(
const DataPartStorageOnDiskBase & source, const NameSet & files)
{
if (files.empty())
return;
/// Create the packed writer on demand: the current setting may not pack any index in this
/// part (so initSkipIndices left skip_indices_packed_writer null) but we still need a
/// destination archive to carry the preserved entries from source into the new part.
/// fillSkipIndicesChecksums emits skp_idx.packed iff this writer has any modified files,
/// so a writer holding only preserved entries still produces the archive.
if (!skip_indices_packed_writer)
skip_indices_packed_writer = std::make_unique<PackedFilesWriter>();
source.copyPackedSkipIndicesFilesInto(
files, *skip_indices_packed_writer, ReadSettings{}, settings.query_write_settings);
}
void MergeTreeDataPartWriterOnDisk::finishSkipIndicesSerialization(bool sync)
{
/// finalize() is idempotent thanks to MergeTreeWriterStream::is_prefinalized
/// and WriteBuffer's finalized flag, so packed streams (already finalized in
/// fillSkipIndicesChecksums) are safe to revisit here.
for (auto & stream : skip_indices_streams_holders)
stream->finalize();
if (skip_indices_packed_file)
{
skip_indices_packed_file->finalize();
if (sync)
skip_indices_packed_file->sync();
}
if (sync)
{
std::vector<const MergeTreeWriterStream *> streams_to_sync;
streams_to_sync.reserve(skip_indices_streams_holders.size());
for (const auto & index_streams : skip_indices_streams)
{
for (const auto & [_, stream] : index_streams)
{
/// Packed substreams have no on-disk file to sync; the archive is synced
/// separately via skip_indices_packed_file->sync() above. Streams that spilled
/// (mixed-layout part: some packed, some per-file) still need their per-file
/// data and marks synced.
if (!stream->isPacked())
streams_to_sync.push_back(stream);
}
}
if (!streams_to_sync.empty())
parallelSyncFiles(streams_to_sync);
}
if (!skip_indices.empty() && log->is(Poco::Message::PRIO_DEBUG))
{
UInt64 total_us = 0;
std::string indices_str;
/// Create pairs of (index, time_us) for sorting
std::vector<std::pair<size_t, UInt64>> index_times;
index_times.reserve(skip_indices.size());
for (size_t i = 0; i < skip_indices.size(); ++i)
{
index_times.emplace_back(i, execution_stats.skip_indices_build_us[i]);
total_us += execution_stats.skip_indices_build_us[i];
}
/// If there are many indices, show only the slowest ones
constexpr size_t max_indices_to_show = 10;
if (skip_indices.size() > max_indices_to_show)
{
std::partial_sort(
index_times.begin(),
index_times.begin() + max_indices_to_show,
index_times.end(),
[](const auto & a, const auto & b) { return a.second > b.second; });
index_times.resize(max_indices_to_show);
}
for (size_t i = 0; i < index_times.size(); ++i)
{
if (i > 0)
indices_str += ", ";
auto [idx, time_us] = index_times[i];
indices_str += fmt::format("{}: {} ms", skip_indices[idx]->index.name, time_us / 1000);
}
if (skip_indices.size() > max_indices_to_show)
indices_str += fmt::format(" (showing {} slowest out of {})", max_indices_to_show, skip_indices.size());
LOG_DEBUG(
log,
"Spent {} ms calculating {} skip indices for the part {}: [{}]",
total_us / 1000,
skip_indices.size(),
data_part_name,
indices_str);
}
skip_indices_streams.clear();
skip_indices_streams_holders.clear();
skip_indices_aggregators.clear();
skip_index_accumulated_marks.clear();
skip_indices_packed_file.reset();
skip_indices_packed_writer.reset();
}
Names MergeTreeDataPartWriterOnDisk::getSkipIndicesColumns() const
{
std::unordered_set<String> skip_indexes_column_names_set;
for (const auto & index : skip_indices)
std::copy(index->index.column_names.cbegin(), index->index.column_names.cend(),
std::inserter(skip_indexes_column_names_set, skip_indexes_column_names_set.end()));
return Names(skip_indexes_column_names_set.begin(), skip_indexes_column_names_set.end());
}
void MergeTreeDataPartWriterOnDisk::prepareBlockForWriting(Block & block)
{
/// If block sample is empty, initialize it using current block (it will be the first block to write).
if (block_sample.empty())
{
for (size_t i = 0; i != block.columns(); ++i)
{
auto & column = block.getByPosition(i);
ColumnWithTypeAndName sample_column;
sample_column.name = column.name;
sample_column.type = column.type;
auto mutable_column = column.column->cloneEmpty();
/// Set of streams may depend on dynamic structure and statistics.
/// For example: ColumnObject, ColumnDynamic, ColumnMap (with adaptive number of buckets).
/// So we need to save them from the first block and set them later to all next blocks.
if (column.column->hasDynamicStructure())
mutable_column->takeExactDynamicStructureFrom(*column.column);
if (column.column->hasStatistics())
mutable_column->takeOrCalculateStatisticsFrom({column.column});
sample_column.column = std::move(mutable_column);
block_sample.insert(std::move(sample_column));
}
}
/// Otherwise, adjust this block so all columns will be written in the same set of substreams as columns from sample block.
else
{
size_t size = block.columns();
for (size_t i = 0; i != size; ++i)
{
auto & column = block.getByPosition(i);
const auto & sample_column = block_sample.getByPosition(i);
/// Check if the dynamic structure of this column is different from the sample column.
if (column.column->hasDynamicStructure() && !column.column->dynamicStructureEquals(*sample_column.column))
{
/// We need to change the dynamic structure of the column so it matches the sample column.
/// To do it, we create empty column of this type, take dynamic structure from sample column
/// and insert data into it. Resulting column will have required dynamic structure and the content
/// of the column in current block.
auto new_column = sample_column.type->createColumn();
new_column->takeExactDynamicStructureFrom(*sample_column.column);
new_column->insertRangeFrom(*column.column, 0, column.column->size());
column.column = std::move(new_column);
}
/// Take statistics from sample column.
if (column.column->hasStatistics())
{
auto mutable_column = IColumn::mutate(std::move(column.column));
mutable_column->takeOrCalculateStatisticsFrom({sample_column.column});
column.column = std::move(mutable_column);
}
}
}
}
void MergeTreeDataPartWriterOnDisk::initStreamsIfNeeded()
{
if (streams_initialized)
return;
/// Block sample is required. It's initialized in prepareBlockForWriting on first written block.
/// If block sample is empty, it means we didn't write any data, so we need to initialize it
/// with empty columns.
if (block_sample.empty())
{
for (const auto & [name, type] : columns_list)
block_sample.insert(ColumnWithTypeAndName{type->createColumn(), type, name});
}
for (const auto & column : columns_list)
{
auto compression = getCodecDescOrDefault(column.name, default_codec);
addStreams(column, compression);
}
streams_initialized = true;
}
void MergeTreeDataPartWriterOnDisk::initColumnsSubstreamsIfNeeded()
{
if (columns_substreams.getTotalSubstreams() || (index_granularity_info.mark_type.part_type == MergeTreeDataPartType::Compact && !index_granularity_info.mark_type.with_substreams))
return;
/// Block sample is required. It's initialized in prepareBlockForWriting on first written block.
/// If block sample is empty, it means we didn't write any data, so we need to initialize it
/// with empty columns.
if (block_sample.empty())
{
for (const auto & [name, type] : columns_list)
block_sample.insert(ColumnWithTypeAndName{type->createColumn(), type, name});
}
NullWriteBuffer buf;
auto serialize_settings = getSerializationSettings();
for (const auto & name_and_type : columns_list)
{
columns_substreams.addColumn(name_and_type.name);
serialize_settings.getter = [&](const ISerialization::SubstreamPath & substream_path)
{
columns_substreams.addSubstreamToLastColumn(ISerialization::getFileNameForStream(name_and_type, substream_path, ISerialization::StreamFileNameSettings(*storage_settings)));
return &buf;
};
serialize_settings.stream_mark_getter = [&](const ISerialization::SubstreamPath &){ return MarkInCompressedFile(); };
ISerialization::SerializeBinaryBulkStatePtr state;
auto serialization = getSerialization(name_and_type.name);
const auto & column = block_sample.getByName(name_and_type.name);
serialization->serializeBinaryBulkStatePrefix(*column.column, serialize_settings, state);
serialization->serializeBinaryBulkWithMultipleStreams(*column.column, column.column->size(), 0, serialize_settings, state);
serialization->serializeBinaryBulkStateSuffix(serialize_settings, state);
}
}
}