From 6e3f35fc942b332c1506668edc2fc5be1242a88b Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Fri, 20 Oct 2023 16:18:39 +0800 Subject: [PATCH 1/6] fix issue https://github.com/ClickHouse/ClickHouse/issues/55858 --- src/IO/readFloatText.h | 6 +++--- tests/queries/0_stateless/02900_issue_55858.reference | 4 ++++ tests/queries/0_stateless/02900_issue_55858.sql | 10 ++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 tests/queries/0_stateless/02900_issue_55858.reference create mode 100644 tests/queries/0_stateless/02900_issue_55858.sql diff --git a/src/IO/readFloatText.h b/src/IO/readFloatText.h index feab9589c2e..e3d549c8e17 100644 --- a/src/IO/readFloatText.h +++ b/src/IO/readFloatText.h @@ -148,11 +148,11 @@ ReturnType readFloatTextPreciseImpl(T & x, ReadBuffer & buf) static_assert('a' > '.' && 'A' > '.' && '\n' < '.' && '\t' < '.' && '\'' < '.' && '"' < '.', "Layout of char is not like ASCII"); static constexpr bool throw_exception = std::is_same_v; - - /// Fast path (avoid copying) if the buffer have at least MAX_LENGTH bytes. static constexpr int MAX_LENGTH = 316; + ReadBufferFromMemory * buf_from_memory = dynamic_cast(&buf); - if (likely(!buf.eof() && buf.position() + MAX_LENGTH <= buf.buffer().end())) + /// Fast path (avoid copying) if the buffer have at least MAX_LENGTH bytes or buf is ReadBufferFromMemory + if (likely(!buf.eof() && (buf_from_memory || buf.position() + MAX_LENGTH <= buf.buffer().end()))) { auto * initial_position = buf.position(); auto res = fast_float::from_chars(initial_position, buf.buffer().end(), x); diff --git a/tests/queries/0_stateless/02900_issue_55858.reference b/tests/queries/0_stateless/02900_issue_55858.reference new file mode 100644 index 00000000000..1d64c9ea17c --- /dev/null +++ b/tests/queries/0_stateless/02900_issue_55858.reference @@ -0,0 +1,4 @@ +0 +0 +\N +\N diff --git a/tests/queries/0_stateless/02900_issue_55858.sql b/tests/queries/0_stateless/02900_issue_55858.sql new file mode 100644 index 00000000000..b7b6704cdb5 --- /dev/null +++ b/tests/queries/0_stateless/02900_issue_55858.sql @@ -0,0 +1,10 @@ +set precise_float_parsing = 1; + +select cast('2023-01-01' as Float64); -- { serverError 6 } +select cast('2023-01-01' as Float32); -- { serverError 6 } +select toFloat32('2023-01-01'); -- { serverError 6 } +select toFloat64('2023-01-01'); -- { serverError 6 } +select toFloat32OrZero('2023-01-01'); +select toFloat64OrZero('2023-01-01'); +select toFloat32OrNull('2023-01-01'); +select toFloat64OrNull('2023-01-01'); From 2a894335b71df44564e1267f33237307b0dccbde Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Tue, 24 Oct 2023 16:16:05 +0800 Subject: [PATCH 2/6] commit again --- src/IO/readFloatText.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/IO/readFloatText.h b/src/IO/readFloatText.h index e3d549c8e17..ad14563c859 100644 --- a/src/IO/readFloatText.h +++ b/src/IO/readFloatText.h @@ -149,6 +149,7 @@ ReturnType readFloatTextPreciseImpl(T & x, ReadBuffer & buf) static constexpr bool throw_exception = std::is_same_v; static constexpr int MAX_LENGTH = 316; + ReadBufferFromMemory * buf_from_memory = dynamic_cast(&buf); /// Fast path (avoid copying) if the buffer have at least MAX_LENGTH bytes or buf is ReadBufferFromMemory From d5feaa72c1917682fb8955663804c9514c3e496f Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Wed, 25 Oct 2023 20:16:18 +0800 Subject: [PATCH 3/6] improve thrown exception message --- src/IO/readFloatText.h | 10 +++++++--- tests/queries/0_stateless/02900_issue_55858.sql | 8 ++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/IO/readFloatText.h b/src/IO/readFloatText.h index ad14563c859..c0da9129a05 100644 --- a/src/IO/readFloatText.h +++ b/src/IO/readFloatText.h @@ -161,7 +161,10 @@ ReturnType readFloatTextPreciseImpl(T & x, ReadBuffer & buf) if (unlikely(res.ec != std::errc())) { if constexpr (throw_exception) - throw ParsingException(ErrorCodes::CANNOT_PARSE_NUMBER, "Cannot read floating point value"); + throw ParsingException( + ErrorCodes::CANNOT_PARSE_NUMBER, + "Cannot read floating point value here: {}", + String(initial_position, buf.buffer().end() - initial_position)); else return ReturnType(false); } @@ -248,10 +251,11 @@ ReturnType readFloatTextPreciseImpl(T & x, ReadBuffer & buf) res = fast_float::from_chars(tmp_buf, tmp_buf + num_copied_chars, x64); x = static_cast(x64); } - if (unlikely(res.ec != std::errc())) + if (unlikely(res.ec != std::errc() || res.ptr - tmp_buf != num_copied_chars)) { if constexpr (throw_exception) - throw ParsingException(ErrorCodes::CANNOT_PARSE_NUMBER, "Cannot read floating point value"); + throw ParsingException( + ErrorCodes::CANNOT_PARSE_NUMBER, "Cannot read floating point value here: {}", String(tmp_buf, num_copied_chars)); else return ReturnType(false); } diff --git a/tests/queries/0_stateless/02900_issue_55858.sql b/tests/queries/0_stateless/02900_issue_55858.sql index b7b6704cdb5..65fc06d9797 100644 --- a/tests/queries/0_stateless/02900_issue_55858.sql +++ b/tests/queries/0_stateless/02900_issue_55858.sql @@ -1,9 +1,9 @@ set precise_float_parsing = 1; -select cast('2023-01-01' as Float64); -- { serverError 6 } -select cast('2023-01-01' as Float32); -- { serverError 6 } -select toFloat32('2023-01-01'); -- { serverError 6 } -select toFloat64('2023-01-01'); -- { serverError 6 } +select cast('2023-01-01' as Float64); -- { serverError 72 } +select cast('2023-01-01' as Float32); -- { serverError 72 } +select toFloat32('2023-01-01'); -- { serverError 72 } +select toFloat64('2023-01-01'); -- { serverError 72 } select toFloat32OrZero('2023-01-01'); select toFloat64OrZero('2023-01-01'); select toFloat32OrNull('2023-01-01'); From 3009e535063a66d854ed57aa4d8490d8bbdc8f29 Mon Sep 17 00:00:00 2001 From: taiyang-li <654010905@qq.com> Date: Thu, 26 Oct 2023 15:05:21 +0800 Subject: [PATCH 4/6] fix failed ut --- tests/queries/0_stateless/02900_issue_55858.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/queries/0_stateless/02900_issue_55858.sql b/tests/queries/0_stateless/02900_issue_55858.sql index 65fc06d9797..b7b6704cdb5 100644 --- a/tests/queries/0_stateless/02900_issue_55858.sql +++ b/tests/queries/0_stateless/02900_issue_55858.sql @@ -1,9 +1,9 @@ set precise_float_parsing = 1; -select cast('2023-01-01' as Float64); -- { serverError 72 } -select cast('2023-01-01' as Float32); -- { serverError 72 } -select toFloat32('2023-01-01'); -- { serverError 72 } -select toFloat64('2023-01-01'); -- { serverError 72 } +select cast('2023-01-01' as Float64); -- { serverError 6 } +select cast('2023-01-01' as Float32); -- { serverError 6 } +select toFloat32('2023-01-01'); -- { serverError 6 } +select toFloat64('2023-01-01'); -- { serverError 6 } select toFloat32OrZero('2023-01-01'); select toFloat64OrZero('2023-01-01'); select toFloat32OrNull('2023-01-01'); From 308b2942d7a3168139a38f18e2283fa4596addc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=89=AC?= <654010905@qq.com> Date: Fri, 27 Oct 2023 11:42:33 +0800 Subject: [PATCH 5/6] Update readFloatText.h --- src/IO/readFloatText.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/IO/readFloatText.h b/src/IO/readFloatText.h index c0da9129a05..b0682576183 100644 --- a/src/IO/readFloatText.h +++ b/src/IO/readFloatText.h @@ -151,7 +151,6 @@ ReturnType readFloatTextPreciseImpl(T & x, ReadBuffer & buf) static constexpr int MAX_LENGTH = 316; ReadBufferFromMemory * buf_from_memory = dynamic_cast(&buf); - /// Fast path (avoid copying) if the buffer have at least MAX_LENGTH bytes or buf is ReadBufferFromMemory if (likely(!buf.eof() && (buf_from_memory || buf.position() + MAX_LENGTH <= buf.buffer().end()))) { From f560e7ee52cd43717759ead2abf7e40d4b318dea Mon Sep 17 00:00:00 2001 From: AN Date: Fri, 27 Oct 2023 16:36:05 +0300 Subject: [PATCH 6/6] Update architecture.md (#56049) Minor fixes (typo found in comment pull/55985 by den-crane, punctuation style unification) --- docs/ru/development/architecture.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/ru/development/architecture.md b/docs/ru/development/architecture.md index 9c4a503a276..35741570702 100644 --- a/docs/ru/development/architecture.md +++ b/docs/ru/development/architecture.md @@ -43,7 +43,7 @@ ClickHouse — полноценная столбцовая СУБД. Данны ## Блоки (Block) {#block} -`Block` — это контейнер, который представляет фрагмент (chunk) таблицы в памяти. Это набор троек — `(IColumn, IDataType, имя столбца)`. В процессе выполнения запроса, данные обрабатываются блоками (`Block`). Если есть `Block`, значит у нас есть данные (в объекте `IColumn`), информация о типе (в `IDataType`), которая говорит, как работать со столбцов, и имя столбца (оригинальное имя столбца таблицы или служебное имя, присвоенное для получения промежуточных результатов вычислений). +`Block` — это контейнер, который представляет фрагмент (chunk) таблицы в памяти. Это набор троек — `(IColumn, IDataType, имя столбца)`. В процессе выполнения запроса, данные обрабатываются блоками (`Block`). Если есть `Block`, значит у нас есть данные (в объекте `IColumn`), информация о типе (в `IDataType`), которая говорит, как работать со столбцом, и имя столбца (оригинальное имя столбца таблицы или служебное имя, присвоенное для получения промежуточных результатов вычислений). При вычислении некоторой функции на столбцах в блоке добавляется ещё один столбец с результатами в блок, не трогая колонки аргументов функции, потому что операции иммутабельные. Позже ненужные столбцы могут быть удалены из блока, но не модифицированы. Это удобно для устранения общих подвыражений. @@ -67,15 +67,15 @@ ClickHouse — полноценная столбцовая СУБД. Данны ## Форматы {#formats} -Форматы данных реализуются с помощью потоков блоков. Есть форматы представления (presentational), пригодные только для вывода данных клиенту, такие как `Pretty` формат, который предоставляет только `IBlockOutputStream`. И есть форматы ввода/вывода, такие как `TabSeparated` или `JSONEachRow`. +Форматы данных реализуются с помощью потоков блоков. Есть форматы представления (presentational), пригодные только для вывода данных клиенту, такие как `Pretty`-формат, который предоставляет только `IBlockOutputStream`. И есть форматы ввода-вывода, такие как `TabSeparated` или `JSONEachRow`. -Существуют также потоки строк: `IRowInputStream` и `IRowOutputStream`. Они позволяют вытягивать/выталкивать данные отдельными строками, а не блоками. Они нужны только для упрощения реализации ориентированных на строки форматов. Обертка `BlockInputStreamFromRowInputStream` и `BlockOutputStreamFromRowOutputStream` позволяет конвертировать потоки, ориентированные на строки, в обычные потоки, ориентированные на блоки. +Существуют также потоки строк: `IRowInputStream` и `IRowOutputStream`. Они позволяют вытягивать и выталкивать данные отдельными строками, а не блоками. Они нужны только для упрощения реализации ориентированных на строки форматов. Обертка `BlockInputStreamFromRowInputStream` и `BlockOutputStreamFromRowOutputStream` позволяет конвертировать потоки, ориентированные на строки, в обычные потоки, ориентированные на блоки. ## I/O {#io} Для байт-ориентированного ввода-вывода существуют абстрактные классы `ReadBuffer` и `WriteBuffer`. Они используются вместо `iostream`. Не волнуйтесь: каждый зрелый проект C++ использует что-то другое вместо `iostream` по уважительным причинам. -`ReadBuffer` и `WriteBuffer` это просто непрерывный буфер и курсор, указывающий на позицию в этом буфере. Реализации могут как владеть так и не владеть памятью буфера. Существует виртуальный метод заполнения буфера следующими данными (для `ReadBuffer`) или сброса буфера куда-нибудь (например `WriteBuffer`). Виртуальные методы редко вызываются. +`ReadBuffer` и `WriteBuffer` — это просто непрерывный буфер и курсор, указывающий на позицию в этом буфере. Реализации могут как владеть так и не владеть памятью буфера. Существует виртуальный метод заполнения буфера следующими данными (для `ReadBuffer`) или сброса буфера куда-нибудь (например `WriteBuffer`). Виртуальные методы редко вызываются. Реализации `ReadBuffer`/`WriteBuffer` используются для работы с файлами и файловыми дескрипторами, а также сетевыми сокетами, для реализации сжатия (`CompressedWriteBuffer` инициализируется вместе с другим `WriteBuffer` и осуществляет сжатие данных перед записью в него), и для других целей – названия `ConcatReadBuffer`, `LimitReadBuffer`, и `HashingWriteBuffer` говорят сами за себя. @@ -87,7 +87,7 @@ ClickHouse — полноценная столбцовая СУБД. Данны Интерфейс `IStorage` служит для отображения таблицы. Различные движки таблиц являются реализациями этого интерфейса. Примеры `StorageMergeTree`, `StorageMemory` и так далее. Экземпляры этих классов являются просто таблицами. -Ключевые методы `IStorage` это `read` и `write`. Есть и другие варианты - `alter`, `rename`, `drop` и так далее. Метод `read` принимает следующие аргументы: набор столбцов для чтения из таблицы, `AST` запрос и желаемое количество потоков для вывода. Он возвращает один или несколько объектов `IBlockInputStream` и информацию о стадии обработки данных, которая была завершена внутри табличного движка во время выполнения запроса. +Ключевые методы `IStorage` это `read` и `write`. Есть и другие варианты — `alter`, `rename`, `drop` и так далее. Метод `read` принимает следующие аргументы: набор столбцов для чтения из таблицы, `AST` запрос и желаемое количество потоков для вывода. Он возвращает один или несколько объектов `IBlockInputStream` и информацию о стадии обработки данных, которая была завершена внутри табличного движка во время выполнения запроса. В большинстве случаев метод read отвечает только за чтение указанных столбцов из таблицы, а не за дальнейшую обработку данных. Вся дальнейшая обработка данных осуществляется интерпретатором запросов и не входит в сферу ответственности `IStorage`. @@ -112,7 +112,7 @@ ClickHouse — полноценная столбцовая СУБД. Данны ## Интерпретаторы {#interpreters} -Интерпретаторы отвечают за создание конвейера выполнения запроса из `AST`. Есть простые интерпретаторы, такие как `InterpreterExistsQuery` и `InterpreterDropQuery` или более сложный `InterpreterSelectQuery`. Конвейер выполнения запроса представляет собой комбинацию входных и выходных потоков блоков. Например, результатом интерпретации `SELECT` запроса является `IBlockInputStream` для чтения результирующего набора данных; результат интерпретации `INSERT` запроса - это `IBlockOutputStream`, для записи данных, предназначенных для вставки; результат интерпретации `INSERT SELECT` запроса - это `IBlockInputStream`, который возвращает пустой результирующий набор при первом чтении, но копирует данные из `SELECT` к `INSERT`. +Интерпретаторы отвечают за создание конвейера выполнения запроса из `AST`. Есть простые интерпретаторы, такие как `InterpreterExistsQuery` и `InterpreterDropQuery` или более сложный `InterpreterSelectQuery`. Конвейер выполнения запроса представляет собой комбинацию входных и выходных потоков блоков. Например, результатом интерпретации `SELECT` запроса является `IBlockInputStream` для чтения результирующего набора данных; результат интерпретации `INSERT` запроса — это `IBlockOutputStream`, для записи данных, предназначенных для вставки; результат интерпретации `INSERT SELECT` запроса — это `IBlockInputStream`, который возвращает пустой результирующий набор при первом чтении, но копирует данные из `SELECT` к `INSERT`. `InterpreterSelectQuery` использует `ExpressionAnalyzer` и `ExpressionActions` механизмы для анализа запросов и преобразований. Именно здесь выполняется большинство оптимизаций запросов на основе правил. `ExpressionAnalyzer` написан довольно грязно и должен быть переписан: различные преобразования запросов и оптимизации должны быть извлечены в отдельные классы, чтобы позволить модульные преобразования или запросы. @@ -120,9 +120,9 @@ ClickHouse — полноценная столбцовая СУБД. Данны Существуют обычные функции и агрегатные функции. Агрегатные функции смотрите в следующем разделе. -Обычные функции не изменяют число строк и работают так, как если бы обрабатывали каждую строку независимо. В действительности же, функции вызываются не к отдельным строкам, а блокам данных для реализации векторизованного выполнения запросов. +Обычные функции не изменяют число строк и работают так, как если бы обрабатывали каждую строку независимо. В действительности же функции вызываются не к отдельным строкам, а блокам данных для реализации векторизованного выполнения запросов. -Некоторые функции, такие как [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), и [runningAccumulate](../sql-reference/functions/other-functions.md#runningaccumulate), эксплуатируют блочную обработку и нарушают независимость строк. +Некоторые функции, такие как [blockSize](../sql-reference/functions/other-functions.md#function-blocksize), [rowNumberInBlock](../sql-reference/functions/other-functions.md#function-rownumberinblock), и [runningAccumulate](../sql-reference/functions/other-functions.md#runningaccumulate), используют блочную обработку и нарушают независимость строк. ClickHouse имеет сильную типизацию, поэтому нет никакого неявного преобразования типов. Если функция не поддерживает определенную комбинацию типов, она создает исключение. Но функции могут работать (перегружаться) для многих различных комбинаций типов. Например, функция `plus` (для реализации `+` оператор) работает для любой комбинации числовых типов: `UInt8` + `Float32`, `UInt16` + `Int8` и так далее. Кроме того, некоторые вариадические функции, такие как `concat`, могут принимать любое количество аргументов. @@ -161,23 +161,23 @@ ClickHouse имеет сильную типизацию, поэтому нет ::: ## Выполнение распределенных запросов (Distributed Query Execution) {#distributed-query-execution} -Сервера в кластере в основном независимы. Вы можете создать `Распределенную` (`Distributed`) таблицу на одном или всех серверах в кластере. Такая таблица сама по себе не хранит данные - она только предоставляет возможность "просмотра" всех локальных таблиц на нескольких узлах кластера. При выполнении `SELECT` распределенная таблица переписывает запрос, выбирает удаленные узлы в соответствии с настройками балансировки нагрузки и отправляет им запрос. Распределенная таблица просит удаленные сервера обработать запрос до той стадии, когда промежуточные результаты с разных серверов могут быть объединены. Затем он получает промежуточные результаты и объединяет их. Распределенная таблица пытается возложить как можно больше работы на удаленные серверы и сократить объем промежуточных данных, передаваемых по сети. +Сервера в кластере в основном независимы. Вы можете создать `распределённую` (`Distributed`) таблицу на одном или всех серверах в кластере. Такая таблица сама по себе не хранит данные — она только предоставляет возможность “просмотра” всех локальных таблиц на нескольких узлах кластера. При выполнении `SELECT` распределенная таблица переписывает запрос, выбирает удаленные узлы в соответствии с настройками балансировки нагрузки и отправляет им запрос. Распределенная таблица просит удаленные сервера обработать запрос до той стадии, когда промежуточные результаты с разных серверов могут быть объединены. Затем он получает промежуточные результаты и объединяет их. Распределенная таблица пытается возложить как можно больше работы на удаленные серверы и сократить объем промежуточных данных, передаваемых по сети. Ситуация усложняется при использовании подзапросов в случае `IN` или `JOIN`, когда каждый из них использует таблицу `Distributed`. Есть разные стратегии для выполнения таких запросов. -Глобального плана выполнения распределенных запросов не существует. Каждый узел имеет собственный локальный план для своей части работы. У нас есть простое однонаправленное выполнение распределенных запросов: мы отправляем запросы на удаленные узлы и затем объединяем результаты. Но это невозможно для сложных запросов `GROUP BY` высокой кардинальности или запросов с большим числом временных данных в `JOIN`: в таких случаях нам необходимо перераспределить («reshuffle») данные между серверами, что требует дополнительной координации. ClickHouse не поддерживает выполнение запросов такого рода, и нам нужно работать над этим. +Глобального плана выполнения распределённых запросов не существует. Каждый узел имеет собственный локальный план для своей части работы. У нас есть простое однонаправленное выполнение распределенных запросов: мы отправляем запросы на удаленные узлы и затем объединяем результаты. Но это невозможно для сложных запросов `GROUP BY` высокой кардинальности или запросов с большим числом временных данных в `JOIN`: в таких случаях нам необходимо перераспределить (“reshuffle”) данные между узлами, что требует дополнительной координации. ClickHouse не поддерживает выполнение запросов такого рода, и нам нужно работать над этим. ## Merge Tree {#merge-tree} -`MergeTree` — это семейство движков хранения, поддерживающих индексацию по первичному ключу. Первичный ключ может быть произвольным набором (кортежем) столбцов или выражений. Данные в таблице `MergeTree` хранятся "частями" (“parts”). Каждая часть хранит данные отсортированные по первичному ключу (данные упорядочены лексикографически). Все столбцы таблицы хранятся в отдельных файлах `column.bin` в этих частях. Файлы состоят из сжатых блоков. Каждый блок обычно содержит от 64 КБ до 1 МБ несжатых данных, в зависимости от среднего значения размера данных. Блоки состоят из значений столбцов, расположенных последовательно один за другим. Значения столбцов находятся в одинаковом порядке для каждого столбца (порядок определяется первичным ключом), поэтому, когда вы выполняете итерацию по многим столбцам, вы получаете значения для соответствующих строк. +`MergeTree` — это семейство движков хранения, поддерживающих индексацию по первичному ключу. Первичный ключ может быть произвольным набором (кортежем) столбцов или выражений. Данные в таблице `MergeTree` хранятся “частями” (“parts”). Каждая часть хранит данные отсортированные по первичному ключу (данные упорядочены лексикографически). Все столбцы таблицы хранятся в отдельных файлах `column.bin` в этих частях. Файлы состоят из сжатых блоков. Каждый блок обычно содержит от 64 КБ до 1 МБ несжатых данных, в зависимости от среднего значения размера данных. Блоки состоят из значений столбцов, расположенных последовательно один за другим. Значения столбцов находятся в одинаковом порядке для каждого столбца (порядок определяется первичным ключом), поэтому, когда вы выполняете итерацию по многим столбцам, вы получаете значения для соответствующих строк. -Сам первичный ключ является “разреженным” (sparse). Он не относится к каждой отдельной строке, а только к некоторым диапазонам данных. Отдельный файл «primary.idx» имеет значение первичного ключа для каждой N-й строки, где N называется гранулярностью индекса (index_granularity, обычно N = 8192). Также для каждого столбца у нас есть файлы `column.mrk` с "метками" ("marks"), которые обозначают смещение для каждой N-й строки в файле данных. Каждая метка представляет собой пару: смещение начала сжатого блока от начала файла и смещение к началу данных в распакованном блоке. Обычно сжатые блоки выравниваются по меткам, а смещение в распакованном блоке равно нулю. Данные для `primary.idx` всегда находятся в памяти, а данные для файлов `column.mrk` кэшируются. +Сам первичный ключ является “разреженным” (sparse). Он не относится к каждой отдельной строке, а только к некоторым диапазонам данных. Отдельный файл «primary.idx» имеет значение первичного ключа для каждой N-й строки, где N называется гранулярностью индекса (index_granularity, обычно N = 8192). Также для каждого столбца у нас есть файлы `column.mrk` с “метками” (“marks”), которые обозначают смещение для каждой N-й строки в файле данных. Каждая метка представляет собой пару: смещение начала сжатого блока от начала файла и смещение к началу данных в распакованном блоке. Обычно сжатые блоки выравниваются по меткам, а смещение в распакованном блоке равно нулю. Данные для `primary.idx` всегда находятся в памяти, а данные для файлов `column.mrk` кэшируются. Когда мы собираемся читать что-то из части данных `MergeTree`, мы смотрим содержимое `primary.idx` и определяем диапазоны, которые могут содержать запрошенные данные, затем просматриваем содержимое `column.mrk` и вычисляем смещение, чтобы начать чтение этих диапазонов. Из-за разреженности могут быть прочитаны лишние данные. ClickHouse не подходит для простых точечных запросов высокой интенсивности, потому что весь диапазон строк размером `index_granularity` должен быть прочитан для каждого ключа, а сжатый блок должен быть полностью распакован для каждого столбца. Мы сделали индекс разреженным, потому что мы должны иметь возможность поддерживать триллионы строк на один сервер без существенных расходов памяти на индексацию. Кроме того, поскольку первичный ключ является разреженным, он не уникален: он не может проверить наличие ключа в таблице во время INSERT. Вы можете иметь множество строк с одним и тем же ключом в таблице. При выполнении `INSERT` для группы данных в `MergeTree`, элементы группы сортируются по первичному ключу и образует новую “часть”. Фоновые потоки периодически выбирают некоторые части и объединяют их в одну отсортированную часть, чтобы сохранить относительно небольшое количество частей. Вот почему он называется `MergeTree`. Конечно, объединение приводит к повышению интенсивности записи. Все части иммутабельные: они только создаются и удаляются, но не изменяются. Когда выполняется `SELECT`, он содержит снимок таблицы (набор частей). После объединения старые части также сохраняются в течение некоторого времени, чтобы упростить восстановление после сбоя, поэтому, если мы видим, что какая-то объединенная часть, вероятно, повреждена, мы можем заменить ее исходными частями. -`MergeTree` не является LSM (Log-structured merge-tree — журнально-структурированным деревом со слиянием), потому что оно не содержит «memtable» и «log»: вставленные данные записываются непосредственно в файловую систему. Это делает его пригодным только для вставки данных в пакетах, а не по отдельным строкам и не очень часто — примерно раз в секунду это нормально, а тысячу раз в секунду - нет. Мы сделали это для простоты и потому, что мы уже вставляем данные в пакеты в наших приложениях. +`MergeTree` не является LSM (Log-structured merge-tree — журнально-структурированным деревом со слиянием), потому что оно не содержит «memtable» и «log»: вставленные данные записываются непосредственно в файловую систему. Это делает его пригодным только для вставки данных в пакетах, а не по отдельным строкам и не очень часто — примерно раз в секунду это нормально, а тысячу раз в секунду — нет. Мы сделали это для простоты и потому, что мы уже вставляем данные в пакеты в наших приложениях. > Таблицы `MergeTree` могут иметь только один (первичный) индекс: вторичных индексов нет. Было бы неплохо разрешить несколько физических представлениям в одной логической таблице, например, хранить данные в более чем одном физическом порядке или даже разрешить представления с предварительно агрегированными данными вместе с исходными данными. @@ -191,7 +191,7 @@ ClickHouse имеет сильную типизацию, поэтому нет Репликация использует асинхронную multi-master-схему. Вы можете вставить данные в любую реплику, которая имеет открытую сессию в `ZooKeeper`, и данные реплицируются на все другие реплики асинхронно. Поскольку ClickHouse не поддерживает UPDATE, репликация исключает конфликты (conflict-free replication). Поскольку подтверждение вставок кворумом не реализовано, только что вставленные данные могут быть потеряны в случае сбоя одного узла. -Метаданные для репликации хранятся в `ZooKeeper`. Существует журнал репликации, в котором перечислены действия, которые необходимо выполнить. Среди этих действий: получить часть (get the part); объединить части (merge parts); удалить партицию (drop a partition) и так далее. Каждая реплика копирует журнал репликации в свою очередь, а затем выполняет действия из очереди. Например, при вставке в журнале создается действие «получить часть» (get the part), и каждая реплика загружает эту часть. Слияния координируются между репликами, чтобы получить идентичные до байта результаты. Все части объединяются одинаково на всех репликах. Одна из реплик-лидеров инициирует новое слияние кусков первой и записывает действия «слияния частей» в журнал. Несколько реплик (или все) могут быть лидерами одновременно. Реплике можно запретить быть лидером с помощью `merge_tree` настройки `replicated_can_become_leader`. +Метаданные для репликации хранятся в `ZooKeeper`. Существует журнал репликации, в котором перечислены действия, которые необходимо выполнить. Среди этих действий: получить часть (get the part); объединить части (merge parts); удалить партицию (drop a partition) и так далее. Каждая реплика копирует журнал репликации в свою очередь, а затем выполняет действия из очереди. Например, при вставке в журнале создается действие “получить часть” (get the part), и каждая реплика загружает эту часть. Слияния координируются между репликами, чтобы получить идентичные до байта результаты. Все части объединяются одинаково на всех репликах. Одна из реплик-лидеров инициирует новое слияние кусков первой и записывает действия “слияния частей” в журнал. Несколько реплик (или все) могут быть лидерами одновременно. Реплике можно запретить быть лидером с помощью `merge_tree` настройки `replicated_can_become_leader`. Репликация является физической: между узлами передаются только сжатые части, а не запросы. Слияния обрабатываются на каждой реплике независимо, в большинстве случаев, чтобы снизить затраты на сеть, во избежание усиления роли сети. Крупные объединенные части отправляются по сети только в случае значительной задержки репликации.