From 6a12990a4fce3ae0cdc7102b72c0ec1cd16aa2e2 Mon Sep 17 00:00:00 2001 From: pilotlao Date: Wed, 30 Sep 2026 15:33:04 +0800 Subject: [PATCH 1/4] Remove table engine documentation tool from ByteHouse READMEs --- server/mcp_server_bytehouse/README.md | 4 ---- server/mcp_server_bytehouse/README_zh.md | 4 ---- 2 files changed, 8 deletions(-) diff --git a/server/mcp_server_bytehouse/README.md b/server/mcp_server_bytehouse/README.md index ce223370..b60c63d9 100644 --- a/server/mcp_server_bytehouse/README.md +++ b/server/mcp_server_bytehouse/README.md @@ -22,10 +22,6 @@ An MCP server for ByteHouse. ByteHouse MCP Server serves as a communication brid - Execute DML or DDL queries on your ByteHouse cluster. - Input: `sql` (string): The SQL query to execute. -* `get_bytehouse_table_engine_doc` - - Get ByteHouse Engine Manual. - - Input: `doc_name` (string): The name of the doc. - ## Configuration ### Running the Server diff --git a/server/mcp_server_bytehouse/README_zh.md b/server/mcp_server_bytehouse/README_zh.md index e8569cdb..f26ad307 100644 --- a/server/mcp_server_bytehouse/README_zh.md +++ b/server/mcp_server_bytehouse/README_zh.md @@ -23,8 +23,6 @@ ByteHouse MCP Server有着不可替代的作用。它可以实现大语言模型 通过交互式执行 SELECT 查询语句来进行数据分析工作。在面对各种复杂的数据环境时,该助手能够精准地依据用户所提供的 SELECT 查询语句,在相应的数据库中进行数据检索操作,最终返回精确的查询结果。而且,更为重要的是,它不会仅仅止步于返回查询结果,还会对这些查询结果进一步进行全面且深入的分析说明。它会从多个维度去剖析这些数据,例如数据的分布规律、数据之间的关联关系、数据所反映出的潜在趋势等等,从而为用户提供更为详尽且有价值的信息,帮助用户更好地理解和利用这些数据。 ### run_dml_ddl_query 能够实现可交互地执行数据操作语言(DML)以及数据定义语言(DDL)的 SQL 语句。在使用者输入相关的 DML/DDL SQL 语句后,该助手会迅速对语句进行处理并执行,之后及时反馈执行结果,同时还会对执行结果给出详细的解释,帮助使用者更好地理解操作的过程和最终产生的影响。 -### get_bytehouse_table_engine_doc -通过交互式咨询,能够方便快捷地获取关于ByteHouse表引擎使用方面的详细说明。无论是初次接触ByteHouse表引擎的新手想要了解基础的操作流程,还是有一定使用经验的用户希望深入探究其高级特性,都可以为你精准提供你所需要的ByteHouse表引擎使用说明内容。 ## 最容易被唤起的 Prompt示例 ### list_database 我想要查询当前所使用的集群环境中,都存在哪些数据库。具体来说,希望获取该集群内所有已创建数据库的名称等相关信息,以便进一步对这些数据库进行管理、分析或者使用等操作。 @@ -32,8 +30,6 @@ ByteHouse MCP Server有着不可替代的作用。它可以实现大语言模型 我想要查询数据库名为 {database name} 的这个数据库中具体包含了哪些数据表。 ### run_select_query 帮忙查询一下集群存储目前的水位情况,包括各个存储节点当前的使用容量、剩余容量以及占总容量的百分比等详细信息。 -### get_bytehouse_table_engine_doc -帮我详细查询一下Bytehouse表引擎的使用文档。为了能更直观地了解其操作方式,希望能给出一些具体的执行实例,包括示例代码、操作步骤、输入输出的详细描述等内容,以便我可以更好地学习和实践Bytehouse表引擎的相关应用。 ## 可适配平台 火山方舟,Cursor,Visual Studio Code,Trae From 0c1b0959321f7b27fc4fe03ea7b771c2cbc63659 Mon Sep 17 00:00:00 2001 From: pilotlao Date: Wed, 30 Sep 2026 15:36:03 +0800 Subject: [PATCH 2/4] Remove ByteHouse table engine documentation tool --- .../mcp_bytehouse/__init__.py | 2 -- .../mcp_bytehouse/mcp_server.py | 17 ----------------- 2 files changed, 19 deletions(-) diff --git a/server/mcp_server_bytehouse/mcp_bytehouse/__init__.py b/server/mcp_server_bytehouse/mcp_bytehouse/__init__.py index 32f01949..f6c368d0 100644 --- a/server/mcp_server_bytehouse/mcp_bytehouse/__init__.py +++ b/server/mcp_server_bytehouse/mcp_bytehouse/__init__.py @@ -4,7 +4,6 @@ list_tables, run_select_query, run_dml_ddl_query, - get_bytehouse_table_engine_doc ) __all__ = [ @@ -13,5 +12,4 @@ "run_select_query", "create_clickhouse_client", "run_dml_ddl_query", - "get_bytehouse_table_engine_doc" ] diff --git a/server/mcp_server_bytehouse/mcp_bytehouse/mcp_server.py b/server/mcp_server_bytehouse/mcp_bytehouse/mcp_server.py index 53a96818..ea529243 100644 --- a/server/mcp_server_bytehouse/mcp_bytehouse/mcp_server.py +++ b/server/mcp_server_bytehouse/mcp_bytehouse/mcp_server.py @@ -1,5 +1,4 @@ import os -import importlib import logging from typing import Sequence import concurrent.futures @@ -168,7 +167,6 @@ def run_select_query(query: str): def run_dml_ddl_query(query: str): """Run a DML/DDL query in a ByteHouse database Note: 建表前先询问用户使用的是ByteHouse云数仓还是ByteHouse企业版,推荐优先使用ByteHouse自研的表引擎 - Note: 表引擎优先使用ByteHouse自研的表引擎,可通过get_bytehouse_table_engine_doc获取表引擎的文档,其中doc_name:ha_unique_merge_tree, ha_merge_tree, distributed是企业版的, cnch_merge_tree, cnch_unique_merge_tree是云数仓的 Note: 调用前转义SQL包含的双引号,确保符合JSON格式 Note: 企业版默认是用ON CLUSTER使得DDL在全部节点上执行,使用ON CLUSTER子句前,请确认集群名正常,可以查询system.clusters获得 """ @@ -182,21 +180,6 @@ def run_dml_ddl_query(query: str): future.cancel() return f"Queries taking longer than {SELECT_QUERY_TIMEOUT_SECS} seconds are currently not supported." -@mcp.tool() -def get_bytehouse_table_engine_doc(doc_name: str): - """Get the documentation for a ByteHouse table engine - Node: 有以下几种文档名doc_name ha_unique_merge_tree, ha_merge_tree, distributed, cnch_merge_tree, cnch_unique_merge_tree - 输入对应的表引擎名,返回对应的文档,请参考文档描述,按照用户需求选择对应的表引擎 - """ - try: - with importlib.resources.open_text('mcp_bytehouse.knowledge', f'{doc_name}.md') as f: - content = f.read() - return content - except FileNotFoundError: - return f"No documentation found for table engine {doc_name}" - except Exception as e: - return f"An error occurred while reading the documentation: {str(e)}" - def create_clickhouse_client(): client_config = config.get_client_config() logger.info( From 7f9cedb910f52864ec2113c0297c92d47ef6559e Mon Sep 17 00:00:00 2001 From: pilotlao Date: Wed, 30 Sep 2026 16:05:20 +0800 Subject: [PATCH 3/4] Remove unused ByteHouse engine documentation resources --- .../knowledge/cnch_merge_tree.md | 207 ------ .../knowledge/cnch_unique_merge_tree.md | 166 ----- .../mcp_bytehouse/knowledge/distributed.md | 65 -- .../mcp_bytehouse/knowledge/ha_merge_tree.md | 58 -- .../knowledge/ha_unique_merge_tree.md | 605 ------------------ 5 files changed, 1101 deletions(-) delete mode 100644 server/mcp_server_bytehouse/mcp_bytehouse/knowledge/cnch_merge_tree.md delete mode 100644 server/mcp_server_bytehouse/mcp_bytehouse/knowledge/cnch_unique_merge_tree.md delete mode 100644 server/mcp_server_bytehouse/mcp_bytehouse/knowledge/distributed.md delete mode 100644 server/mcp_server_bytehouse/mcp_bytehouse/knowledge/ha_merge_tree.md delete mode 100644 server/mcp_server_bytehouse/mcp_bytehouse/knowledge/ha_unique_merge_tree.md diff --git a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/cnch_merge_tree.md b/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/cnch_merge_tree.md deleted file mode 100644 index c1ddbf09..00000000 --- a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/cnch_merge_tree.md +++ /dev/null @@ -1,207 +0,0 @@ -/ByteHouse云数仓版/用户指南/数据建模/表引擎/CnchMergeTree -## 表引擎介绍 - -表引擎即表的类型,决定了: - -* 数据的组织和存储方式 -* 索引的方式以及索引类型 -* 支持哪些查询以及如何支持 -* 一些其他特定的功能和配置 - -ByteHouse 云数仓版最常用的表引擎是 CnchMergeTree,除此之外也有其他特殊类型的表引擎包括 Hive外表、Kafka表等。本文重点分享 CnchMergeTree 表引擎的原理。 - -## CnchMergeTree 表引擎 - -CNCHMergeTree 是最常用的表引擎,核心思想和LSM-Tree类似,数据按分区键(partition by)进行分区,然后排序键(order by)进行有序存储。主要有如下特点: -**1. 逻辑分区** - -如果指定了分区键的话,数据会按分区键划分成了不同的逻辑数据集(逻辑分区,Partition)。 - -每一个逻辑分区可以存在零到多个数据片段(DataPart)。如果查询条件可以裁剪分区,通常可以加速查询。如果没有指定分区键,全部数据都在一个逻辑分区里。 -**2. 数据片段** - -数据片段里的数据按排序键排序。每个数据片段还会存在一个min/max索引,来加速分区选择。 -**3. 数据颗粒(Granule)** - -每个数据片段被逻辑的分割成颗粒(granule),默认的Granule为8192行(由表的index\_granularity配置决定)。颗粒是 ByteHouse 中进行数据查询时的最小不可分割数据集。每个颗粒的第一行通过该行的主键值进行标记, ByteHouse 会为每个数据片段创建一个索引文件来存储这些标记。对于每列,无论它是否包含在主键当中,ByteHouse 都会存储类似标记。这些标记让您可以在列文件中直接找到数据。Granule作为ByteHouse 稀疏索引的索引目标,也是在内存中进行数据扫描的单位。 -**4. 后台 Merge** - -后台任务会定时对同一个分区的DataPart进行合并,并保持按排序键有序。后台的合并减少了 Part 的数目,以便更高效存储,并提升了查询性能。 - -## CnchMergeTree 建表语句和相关配置 - -CnchMergeTree 表引擎支持的建表语义如下: - -``` -CREATE TABLE [IF NOT EXISTS] [db.]table_name -( - name1 [type1] [DEFAULT|ALIAS expr1] [compression_codec] [TTL expr1], - name2 [type2] [DEFAULT|ALIAS expr2] [compression_codec] [TTL expr2], - ... - INDEX index_name1 expr1 TYPE type1(...) GRANULARITY value1, - INDEX index_name2 expr2 TYPE type2(...) GRANULARITY value2, -) ENGINE = CnchMergeTree() -ORDER BY expr -[PARTITION BY expr] -[CLUSTER BY (column, expression, ...) INTO value1 BUCKETS SPLIT_NUMBER value2 WITH_RANGE] -[PRIMARY KEY expr] -[UNIQUE KEY expr] -[SAMPLE BY expr] -[TTL expr] -[SETTINGS name=value, ...] - -``` -## 配置参数说明 - -### 设计分区键(PARTITION BY) - -分区键定义分区,分区是在一个表中通过指定的规则划分而成的逻辑数据集。可以按任意标准进行分区,如按日期。为了减少需要操作的数据,每个分区都是分开存储的。查询时,ByteHouse 尽量使用这些分区的最小子集。建表时候通过 `PARTITION BY expr` 子句指定。分区键可以是表中列的任意表达式。例如,指定按月分区,表达式为 `toYYYYMM(date)`;或者按表达元组,如`(toMonday(date), EventType)`等。 - -需要注意,表中分区表达式计算出的取值范围不能太大(推荐不超过一万),太多分区会占用比较大的内存以及带来比较多的 IO 和计算开销。 - -合理的设计分区键可以极大减少查询时需要扫描的数据量,一般考虑将查询中最常用的条件同时取值范围不超过一万的列设计为分区键(如日期等) - -### 设计排序键(ORDER BY) - -可以是一组列的元组或任意的表达式。 例如: `ORDER BY (OrderID, Date)`。 - -如果不需要排序,可以使用 `ORDER BY tuple()`,DataPart将按照数据插入的顺序存储。 - -### 主键(PRIMARY KEY) - -默认情况不需要显式指定,ByteHouse 将使用排序键作为主键。当有特殊场景主键和排序键不一致时,主键必须为排序键的最左前缀。如排序键为(OrderID, Date),主键必须为OrderID,不能为Date。 - -ByteHouse 会在主键上建立以 Granule 为单位的稀疏索引,(与之对比,所谓稠密索引则是每一行都会建立索引信息)。 - -如果查询条件能匹配主键索引的最左前缀,通过主键索引可以快速过滤出可能需要读取的数据颗粒,相比扫描整个 DataPart,通常要高效很多。 -**另外需要注意,PRIMARY KEY不能保证唯一性,所以可以插入主键重复的数据行。** - -分区(PARTITION BY)和主键(PRIMARY KEY)是两种不同的加速数据查询的方式,定义的时候应当尽量错开使用不同的列来定义两者,来覆盖更多的查询场景。例如order by的第一个列一定不要重复放到partition by里。下面是如何选择主键的一些考虑: - -* 是否是查询条件里常用的列 -* 不是非分区键的第一个列 -* 这个列的选择性,例如性别、是/否这种可选值太少的列不建议放入主键中 -* 假如现在的主键是(a,b),如果在大多数情况下给定(a,b)对应的数据范围很大(包含多个Granule),可以考虑把一个新的查询常用列附加到主键中,这样可以过滤更多的数据。 -* 过长的主键会对插入性能和内存消耗有负面影响,但对查询性能没有影响。 - -### 唯一键(UNIQUE KEY) - -ByteHouse的主键(PRIMARY KEY)不能保证唯一性,如果有唯一键去重的需求,需要在建表时设置唯一键索引。设置唯一键之后,ByteHouse 提供 upsert 更新写语义,可以根据唯一键高效更新数据行,或者在upsert的时候通过设置虚拟列 `_delete_flag_=1` ,可以用来删除指定的 key。查询自动返回每个唯一键的最新值。 - -唯一键可以是一组列的元组或任意的表达式,如`UNIQUE KEY (product_id, sipHash64(city))`。 - -通过唯一键查询时会用上唯一键索引过滤数据加速查询,所以通常排序主键可以设置和唯一键不一样列,覆盖更多的查询条件。 - -更多唯一键索引和唯一键表的介绍详情可参考[唯一键表](/docs/6517/1451290)、 [ByteHouse Unique 表最佳实践](/docs/6517/145505)。 - -注意 - -**Primary key 和 Unique key 的区别?** - -Primary key会自带稀疏索性加速查询,Unique key会保证数据唯一(同一个分区中,如果unique key相同的情况,后写进去的数据会覆盖前面的数据),建议选择这两种key的时候从需求考虑,查询会作为过滤条件的字段可以考虑做为Primary key。 - -### 分桶 Bucketing (Cluster By) - -分桶常用于以下场景,具体请参考 [应用案例](/docs/6517/166175#%E5%BA%94%E7%94%A8%E6%A1%88%E4%BE%8B)。 - -1. **通用场景:** 数据分布不均匀 - * **定义及原理**:当分区无法实现数据的均匀分布时,可以利用分桶字段。 分桶字段保证一列数据均匀分布在集群的每个节点下。 这可以最大限度地提高查询的集群性能。 分区字段的合理设置也有助于解决数据倾斜问题,保证数据分布更加均匀。 - * **字段限制**:不支持 Nullable。 - * **配置建议**:选择分组依据中经常出现的字段。 - * 表创建成功后,该字段不允许修改列类型。 -2. **特定场景**:重复数据删除速度慢 - * **定义和原理**:当设置了Unique Key并且单个分区中的数据过多(例如超过1亿行)时,数据摄取的速度将会受到影响。 这是因为需要获取锁才能进行重复数据删除。 在这种情况下,您可以将分区划分为存储桶以提高数据摄取速度。 - * **字段限制**:不支持 Nullable。 - * **配置建议**:Bucket Key需要与Unique Key相同。 (每个桶应小于1000万行) - -注意 - -更改现有表以添加存储桶只会影响新分区,但不会影响现有分区。 - -### 采样 - -用于抽样的表达式,该配置为可选项。 - -如果要用抽样表达式,主键中必须包含这个表达式。例如: `SAMPLE BY intHash32(UserID) ORDER BY (CounterID, EventDate, intHash32(UserID))`。 - -### 列和表的 TTL - -指定行存储的持续时间并定义数据片段在硬盘和卷上的移动逻辑的规则列表,可选项。 - -表达式中必须存在至少一个 `Date` 或 `DateTime` 类型的列,比如: -`TTL date + INTERVAl 1 DAY`。 - -### 压缩 - -compression\_codec字段可以用于配置编解码器,该配置为可选项,默认值为 LZ4。 - -ByteHouse支持通用目的编码和特定编码,通用编解码器更像默认编解码器(LZ4, ZTSD)及其修改版本。特定编解码器是为了利用数据的特定特征使压缩更有效而设计的。 - -1. **通用编码** - -* NONE : 无压缩。 -* LZ4 : 默认值,无损[极速压缩算法](https://github.com/lz4/lz4)。 -* LZ4HC[(level)] : 具有可配置级别的LZ4HC高压缩率算法。level默认值为9,支持值[1 ~ 12],推荐选用[4 ~ 9]。 -* ZSTD[(level)] : 具有可配置级别的ZSTD压缩算法。level默认值为1,支持[1 ~ 22]。 - -2. **特定编码算法** - -* Delta(delta\_bytes) : 增量编码,即保留第一位并存储后续每两个值之间差值的算法。默认值为 sizeof(type), 可选值为1、2、4或8,若为其他值则视为1。 - -3. **多编解码器** - - 使用上述多个编解码器。压缩将根据编解码器声明的顺序进行,解压则按相反的顺序进行。 - -举例参考: - -``` -CREATE TABLE codec_example -( - date Date CODEC(Delta, ZSTD), - ts DateTime CODEC(LZ4HC), - float_value Float32 CODEC(NONE), - double_value Float64 CODEC(LZ4HC(9)) -) -ENGINE = CnchMergeTree -PARTITION BY tuple() -ORDER BY date - -``` -### 更多配置 - -更多建表相关配置,例如 Unique 表,分桶表等,可以参考[最佳实践](/docs/6517/145504)中的对应文档。 - -## 架构优劣势说明 - -CnchMergeTree 合并的核心价值在于零存整取:数据分不同批次导入表中,但可以通过合并减少文件数,并让数据顺序存储。使得 ByteHouse 能最大限度运用磁盘强大的顺序读能力,带来极优的查询性能。 - -但合并的问题也显而易见:如果后台的写入太过零碎(如每次只插入几百行,几十行),则带来非常多的 Part,Merge 任务会导致 CPU 开销、内存占用提升,带来查询任务的性能下降升值出错。此外,如果过多的小文件导致合并变慢,也会导致查询最新的数据时,Part 还没来得及合并,也会导致查询性能降低。 - -## 常见问题 - -### 为什么不建议使用 `Select *`? - -由于 ByteHouse 为列式存储数据库,数据存放在不同的列存文件(.bin)中,这一设计是为了查询指定列时只需要读取有限的文件数,加速查询。 - -如果`select *`,后台需要读取所有的`.bin`列存文件,相当于放弃了列存带来的优势。 - -### 为什么不建议 `Insert Into`插入数据? - -一次`Insert Into`会新建一个 part 文件夹,而不断调用`Insert Into`则会带来很多 part,且每个 part 的数据量很小,后台需要长时间的合并才能减少 part 数量。带来的问题: - -* 在此期间,查询极慢,因为一个范围查询可能跨若干个 part 中的列存文件。 -* 长时间 Merge 不完,占用系统资源。 - -### 为什么建议查询时加限制分区字段的条件? - -限制分区后,查询只会扫描有限的 part 目录,减少扫描数据量,可以大大加速查询。 - -### 为什么分区字段建议设置为日期字段? - -目前 ByteHouse 仅支持可以转为日期的字段(int,string,data,datatime)来配置分区键。因为从业务视角上看,每天的数据量 / 每小时的数据量接近,日期字段分区可以带来每个分区的大小比较均衡,不会造成单个查询的延迟剧烈波动; - -### 为什么排序索引建议设置为查询中最常用的字段? - -前文中可以看到在每个 block 内会按照排序索引进行排序,并且基于该字段建立了稀疏索引。查询条件中只要带有排序索引,MergeTree 引擎会通过索引中标记的行与数据的对应关系裁剪不必要读取的 granule,扫描行数降低,查询性能提升。 - -如果查询不带排序索引,则只能进行全文件的扫描,效率很低。 \ No newline at end of file diff --git a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/cnch_unique_merge_tree.md b/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/cnch_unique_merge_tree.md deleted file mode 100644 index 4ff97f24..00000000 --- a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/cnch_unique_merge_tree.md +++ /dev/null @@ -1,166 +0,0 @@ -* 文档首页 -/ByteHouse云数仓版/用户指南/数据建模/表引擎/唯一键表 - -ByteHouse云数仓版的CnchMergeTree表引擎支持在建表时设置唯一键(UNIQUE KEY),创建一个唯一键表,本文为您介绍唯一键表的能力细节和使用示例。 - -能力概述 - -唯一键表即指定唯一键(UNIQUE KEY)的 CnchMergeTree 表,具有以下特点: - -* 用户通过 UNIQUE KEY 配置唯一键,支持 upsert 更新写语义,查询时自动返回每个唯一键的最新值。 -* 在保证实时更新能力的情况下,依然保持较高的查询性能。 -* 唯一键(UNIQUE KEY)支持多字段和表达式。 -* 唯一键表支持多种去重粒度(如分区级去重、bucket 去重等)。 -* 支持自定义版本字段,写入低版本数据时自动忽略。 -* 支持根据 UNIQUE KEY 实时删除数据。 -* 支持根据 UNIQUE KEY 进行部分列更新操作。 - -## 唯一键表与非唯一键表能力对比 - -| 对比项 | 唯一键表 | 非唯一键表 | -| --- | --- | --- | -| 唯一键约束 | 保证 | 不保证 | -| [更新语句 (UPDATE)](/docs/6517/1359471) | 支持 | 不支持同步更新 可以通过 alter 语句实现异步更新 | -| [删除语句 (DELETE)](/docs/6517/1359473) | 支持 | 不支持同步删除 可以通过 alter 语句实现异步删除 | -| [部分列更新](/docs/6517/1331034) | 支持 | 不支持 | -| UPSERT / [INSERT THROW](/docs/6517/1359469#16a2fc23)/ [INSERT IGNORE](/docs/6517/1359469#2c3c55f2) | 支持 说明 当使用唯一键表进行 insert 时,默认会实现 upsert 语义,即保留每个唯一键的最新值。 | 不支持 | - -## 唯一键支持的类型 - -| 常用 Unique Key 字段类型 | 其他 Unique key 的字段类型 | -| --- | --- | -| String, UInt8, UInt16, UInt32, UInt64, Int8, Int16, Int32, Int64 | Decimal32, Decimal64, Date, Date32, DateTime, DateTime64, Time | - -如果希望使用不支持的字段类型用作 Unique key 字段,可以尝试使用 `sipHash64()` 将对应字段类型转化为 UInt64。 - -说明 - -`sipHash64` 是一种快速且低冲突率的哈希函数,实践中未遇到 hash 冲突的场景。 - -去重粒度:分区级唯一&Bucket唯一 - -Bytehouse 唯一键表默认提供分区级唯一作为去重粒度,当 cluster by 所需列为 unique key 字段所需列子集时,去重粒度可以优化到 bucket 唯一。不同的去重粒度能够在元数据级别限制待去重的数据量,进而满足用户不同的写入诉求: - -注意 - -唯一键表在写入时,需要通过唯一键,找到每条记录原来所在的位置,因此待去重的存量数据行数越多,写入 rps 越低;通常,在存量数据为 50,000,000 时,写入 rps 为 20,000。 - -| 对比项 | 分区级唯一+非bucket去重优化 | 分区级唯一+bucket去重优化 | -| --- | --- | --- | -| 去重说明 | 分区级别去重,新写入的数据会跟对应分区中所有的存量数据进行去重。 | 分区级别的 bucket 去重,新写入的数据会跟表中对应分区,相同 bucket 的存量数据去重。 | -| 去重存量数据示例 | Image * 去重粒度为分区级 * 新写入数据的 partition = 2020-10-29 | Image**cluster by 所需列为 unique key 字段所需列的子集** 图例中: * 去重粒度为Bucket级 * Bucket 数量为 5 * 新写入数据的 partition = 2020-10-29 * 新写入数据的 bucket = 0 | - -数据删除/更新原理 - -唯一键表使用了 Delete-and-Insert 策略,当更新数据到来时,通过唯一键,先找到每条记录原来所在的位置,将该条记录标记为删除,然后将最新的数据作为新记录写入到新的数据文件中。读取时,根据删除标记,将已删除的旧版本数据过滤掉,从而查询到唯一键的最新值。 - -例如,以下示例中表为分区级唯一,PK 为 partition key,UK 为 unique key,delete bitmap 为标记删除列。 -![Image](https://p9-arcosite.byteimg.com/tos-cn-i-goo7wpa0wc/68f983433f944d84b3e1c4bc4aab0054~tplv-goo7wpa0wc-image.image) - -Delete bitmap 列为 1 的数据被标记删除,在增量数据写入后,最新数据为: - -``` -2020-10-29,1,m -2020-10-29,2,n -2020-10-30,1,z -2020-10-30,2,k - -``` -使用限制 - -唯一键表由于去重特性,为了进行高效的数据写入,对于数据量和 Unique Key 数量具有如下使用限制: - -* 建议 Unique Key 字段设置不超过 5 个;如果多个字段组合构成唯一键,可以使用它们的 sipHash64 哈希作为唯一键。 -* 建议去重粒度的数据量控制在 1 亿以下。 - -使用示例 - -以下为您提供两个典型使用示例,更多示例可参见[ByteHouse Unique 表最佳实践](/docs/6517/145505)。 - -## 分区级唯一示例 - -``` -CREATE TABLE t1 -( - `event_time` DateTime, - `product_id` UInt64, - `city` String, - `category` String, - `amount` UInt32, - `revenue` UInt64 -) -ENGINE = CnchMergeTree -PARTITION BY toDate(event_time) -ORDER BY (city, category) -UNIQUE KEY product_id; - -INSERT INTO t1 VALUES -('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), -('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200), -('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100); - --- 写入相同 key 的数据可以实现更新(upsert语义) -INSERT INTO t1 VALUES -('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 4, 400), -('2020-10-29 23:50:00', 10003, 'Beijing', '男装', 2, 200), -('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100), -('2020-10-30 00:00:05', 10001, 'Beijing', '男装', 1, 100), -('2020-10-30 00:00:05', 10002, 'Beijing', '男装', 2, 200); - --- 查询自动返回每个key最新的数据 -select * from t1 order by toDate(event_time), product_id; -┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ -│ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 5 │ 500 │ -│ 2020-10-29 23:50:00 │ 10002 │ Beijing │ 男装 │ 4 │ 400 │ -│ 2020-10-29 23:50:00 │ 10003 │ Beijing │ 男装 │ 2 │ 200 │ -│ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-30 00:00:05 │ 10001 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-30 00:00:05 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ -└─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘ - -``` -## 分区级唯一+bucket 唯一示例 - -当唯一键表单分区数据量达到一定量级,会影响写入效率。此时可以使用 cluster by 将分区级的数据量打散到不同的分桶,当 cluster by 所需的列全部包含在 unique key 中时,可以达到分区级唯一的效果,同时享受 bucket 唯一优化。 - -``` -CREATE TABLE t2 -( - `event_time` DateTime, - `product_id` UInt64, - `city` String, - `category` String, - `amount` UInt32, - `revenue` UInt64 -) -ENGINE = CnchMergeTree -PARTITION BY toDate(event_time) -ORDER BY (city, category) -CLUSTER BY product_id INTO 10 BUCKETS -UNIQUE KEY product_id; - -INSERT INTO t2 VALUES -('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), -('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200), -('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100); - --- 写入相同 key 的数据可以实现更新(upsert语义) -INSERT INTO t2 VALUES -('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 4, 400), -('2020-10-29 23:50:00', 10003, 'Beijing', '男装', 2, 200), -('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100), -('2020-10-30 00:00:05', 10001, 'Beijing', '男装', 1, 100), -('2020-10-30 00:00:05', 10002, 'Beijing', '男装', 2, 200); - --- 查询自动返回每个key最新的数据 -select * from t2 order by toDate(event_time), product_id; -┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ -│ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 5 │ 500 │ -│ 2020-10-29 23:50:00 │ 10002 │ Beijing │ 男装 │ 4 │ 400 │ -│ 2020-10-29 23:50:00 │ 10003 │ Beijing │ 男装 │ 2 │ 200 │ -│ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-30 00:00:05 │ 10001 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-30 00:00:05 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ -└─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘ - -``` diff --git a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/distributed.md b/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/distributed.md deleted file mode 100644 index 0d0ec1bc..00000000 --- a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/distributed.md +++ /dev/null @@ -1,65 +0,0 @@ -之前说到,HaMergeTree。ByteHouse 的数据分片需要结合分布式表引擎(Distributed)一起使用。Distributed 表引擎本身不存储任何数据,它能够作为分布式表的一层代理,在集群内部自动展开数据写入、分发、查询、路由等工作。 - -架构与原理 - -![alt](https://portal.volccdn.com/obj/volcfe/cloud-universal-doc/upload_9bd086868b72d41c7c8449923afa6271.png) - -从上图可以看出一张表分成了两部分: - -* 本地表:通常以 `_local` 后缀进行命名。本地表是承接数据的载体,可以使用 非 Distributed 的任意表引擎,我们建议使用 HaMergeTree,或 HaUniqueMergeTree -* 分布式表:通常以业务表直接命名,分布式表只能使用 Distributed 表引擎,他们与本地表形成一对多的映射关系,以后通过分布式表代理操作多张本地表。 - -而为了让整个集群的每一个节点都可查,也需要将分布式表建到每个节点上。因此,对于一张业务表,需要在每个节点上都分别创建一张分布式表,和一张本地表。架构如下图: - -![alt](https://portal.volccdn.com/obj/volcfe/cloud-universal-doc/upload_ec1700c306040f7f71cfa94d56353d7c.png) - -## 分布式表的查询 - -当 Distributed 表执行查询操作的时候,会依次查询每个分片的数据,然后再汇总返回。 - -## 分布式表的写入 - -当对 Distributed 表的执行写入时,会先写入本地,再根据分片键(Shard Key)计算数据应该写入哪些节点,建立远程链接,再分发到这些节点。 - -但这样做有一些缺点: - -1. Part 同步问题:分布式表接收到数据后会将数据拆分成多个 parts,并转发数据到其它服务器,会引起服务器间网络流量增加、服务器merge的工作量增加,导致写入速度变慢,并且增加了 Too many parts 的可能性。 -2. 数据的一致性问题:先在分布式表所在的机器进行落盘, 然后异步的发送到本地表所在机器进行存储,中间没有一致性的校验,而且在分布式表所在机器时如果机器出现宕机, 会存在数据丢失风险。 -3. 数据写入默认是异步的,短时间内可能造成不一致。 - -因此,ByteHouse 的读写建议为:插入 local 表,读 Distributed 表。不直接插入 Distributed 表。当然,这样就需要业务在写入前针对 Shard Key 提前拆好数据,分别写入不同的 Local 表。 - -建表 -## 通过 SQL 建表 - -分布式表的建表语句示例如下: - -``` -CREATE TABLE [IF NOT EXISTS] [db_name.]table_name ON CLUSTER clustern( - name1 [type] [DEFAULT|MATERIALIZED|ALIAS expr], - name2 [type] [DEFAULT|MATERIALIZED|ALIAS expr],... -) ENGINE = Distributed(cluster,database,table,[sharding_key]) -[PARTITION BY expr] -[ORDER BY expr] -[PRIMARY KEY expr] -[SAMPLE BY expr] -[SETTINGS name=value, ...] - -``` - -* cluster:集群名称,与集群配置中的自定义名称相对应,在对分布式表执行写入和查询过程中,它会使用集群的配置信息来找对应的节点。 -* database:对应数据库名称 -* table:对应数据表名称, -* sharding\_key:分片键,选填参数,在写入数据的过程中,分布式表会依据分片键的规则,将数据分布到各个本地表所在的节点中。关于分片的规则这里进一步说明,分片键要求返回一个整型类型的取值,包括 Int 和 UInt 类型的系列。 - -分片键使用示例: - -``` --- 分片键可以是一个具体的整型字段 --- 按照用户 ID 划分 -Distributed(cluster,database,table,userid)-- 分片键也可以是返回整型的表达式 --- 按照随机数划分 -Distributed(cluster,database,table,rand())-- 按照用户 ID 的散列值划分 -Distributed(cluster,database,table,intHash64(userid)) - -``` \ No newline at end of file diff --git a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/ha_merge_tree.md b/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/ha_merge_tree.md deleted file mode 100644 index 61382197..00000000 --- a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/ha_merge_tree.md +++ /dev/null @@ -1,58 +0,0 @@ -HaMergeTree 是 ByteHouse 自研的引擎,是 ClickHouse 社区的 MergeTree 引擎的**高可用版**,支持主备数据同步。**ByteHouse 默认使用HaMergeTree引擎。** - -相比起社区的 ReplicatedMergeTree,HaMergeTree 在实现多副本的同时,减少了 ZooKeeper 的依赖,单集群可支持的总数表比社区版更多(1W以上)。 - -## 架构与原理 -每个分片 的 HaMergeTree 数据会相互同步,保持数据一致,因此查询同一分片任一副本的 HaMergeTree 得到结果都是一致的。当其中任一节点发生故障时,只要该分片下仍有存活的节点,数据仍然保持可查。节点故障后进行替换,新节点上的数据也会被仍存活的节点的 HaMergeTree 表同步。 - -若当前集群是单副本模式,也可以创建 HaMergeTree 表,但根据定义,此时分片内只有一个副本,因此不存在数据同步,这张 HaMergeTree 表的表现行为和普通的 MergeTree 表一致。 - -需要注意的是,不同 Shard 中 HaMergeTree 的数据是不同的。此时需要 Distributed 表汇集不同节点的数据,统一返回。关于 Distributed 表的详情请见 [Distributed](/docs/6464/163838)。 - -![Image](https://portal.volccdn.com/obj/volcfe/cloud-universal-doc/upload_17ea3cd69c471733abae16953f910925.png) - -## 建表 -### SQL 建表 -以下示仅描述了如何建一张 HaMergeTree 表,若你直接使用 SQL 建表,作为最佳实践,你仍需新建一张 Distributed 表,详情请见 [Distributed](/docs/6464/163838)。 - -```sql -CREATE TABLE [db.]table_name [ON CLUSTER cluster] -( - name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1], - name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2], - ... -) ENGINE = HaMergeTree(shard, replica) -- 默认为 '/clickhouse/bytehouse/库名.表名/{shard}','{replica}' -PARTITION BY toYYYYMM(EventDate) -ORDER BY (CounterID, EventDate, intHash32(UserID)) -[PRIMARY KEY expr] -[SAMPLE BY expr] -[TTL expr] -[SETTINGS name=value, ...] -``` - -#### 关键参数 -- **排序键(ORDER BY)**: - - ByteHouse 为了提高查询性能, 存储数据时会根据排序索引顺序存储。 - - 排序键可以不唯一。但是不能为 Nullable。 - - 建议选择 1 - 3 个经常作为过滤条件的字段作为排序键,使用频率越高,对应顺序越优先。当优先级近似时,选择基数较小的排序索引位于更优先的顺序。 - - 分区字段不必为排序索引。 -- **主键(PRIMARY KEY)**: - - 在索引文件(.idx)记录的就是行与主键的对应关系。它默认和排序索引是一致的,通常也不需要额外设置。 - - 主键不能包含 Nullable 值。一个数据表只能选择一个主键。 -- **抽样字段(SAMPLE BY)**: - - 默认取第一个主键字段,用于在查询抽样时使用。请参考社区 [Sample by](https://clickhouse.com/docs/en/sql-reference/statements/select/sample/) 语法。 - - 必须是排序索引 / 主键之一。 -- **分区字段(PARTITION BY)**: - - **推荐的类型**:时间类型(Date/DateTime),选择时间类型为最佳实践,且建议将 DateTime 类型通过 toDate() 转为 Date 类型作为分区键。 - - 如果无需进行分区时,可以不选分区键。 - - 分区字段不可以为 Nullable -- **TTL**: - - 设置数据生命周期,生效粒度为表级别。 - - 数据保留时间是以“分区字段”作为基础进行计算,因此对于非时间分区的表将无法设置 TTL,系统将强制修改为永久保留。 - -#### Settings -此部分与 [社区 MergeTree 引擎](https://clickhouse.com/docs/en/engines/table-engines/mergetree-family/mergetree#settings) 的 Settings 一致。若采用控制面建表已使用默认值,无需进一步配置。 - -:::warning -使用 HaMergeTree 时,请将 remote 配置文件中的 internal_replication 设置为 True,否则会查询到2份数据。 -::: diff --git a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/ha_unique_merge_tree.md b/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/ha_unique_merge_tree.md deleted file mode 100644 index e497dbc5..00000000 --- a/server/mcp_server_bytehouse/mcp_bytehouse/knowledge/ha_unique_merge_tree.md +++ /dev/null @@ -1,605 +0,0 @@ -唯一键引擎(HaUniqueMergeTree) 是 ByteHouse 自研的一款既保留了 ClickHouse 高效的查询性能、又支持主键更新的表引擎。它解决了社区版 ClickHouse 不能支持高效更新操作的痛点,帮助业务更简单地开发实时分析应用。 - -引擎优势 - -1. 用户通过 UNIQUE KEY 配置唯一键,提供 upsert 更新写语义,查询自动返回每个唯一键的最新值。(和社区的 ReplacingMergeTree 相比,ReplacingMergeTree 在数据导入后需要等待 Merge 完成,才可以查到去重后的数据,而 HaUniqueMergeTree 则是即导入后立即去重)。 -2. 性能:单 Shard 写入吞吐一般可以达到 50k + rows/s。对于海量数据的场景,建议通过数据源治理后,并行导入不同分区来实现线性增速。 -3. 唯一键支持多字段和表达式。 -4. 支持分区级别唯一和表级别唯一两种模式。 -5. 支持自定义版本字段,写入低版本数据时自动忽略。 -6. 多副本部署,通过主备异步复制保障数据可靠性。 -7. 支持根据UNIQUE KEY实时删除数据。 - -建表示例 -## SQL 建表 - -### 建表语法 - -``` -CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster] -( - name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [TTL expr1], - name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2] [TTL expr2], - ... -) ENGINE = HaUniqueMergeTree(shard, replica, version_column) -- 默认为 '/clickhouse/bytehouse/库名.表名/{shard}','{replica}' -PARTITION BY toYYYYMM(EventDate) -ORDER BY expr -[PARTITION BY expr] -UNIQUE KEY expr -[SAMPLE BY expr] -[TTL expr - [DELETE|TO DISK 'xxx'|TO VOLUME 'xxx' [, ...] ] - [WHERE conditions] - [GROUP BY key_expr [SET v1 = aggr_func(v1) [, v2 = aggr_func(v2) ...]] ] ] -[SETTINGS name=value, ...] - -``` - -* Unique Key设置:支持多个字段(但不支持 Nullable,也不支持 Map,Array 等复合类型),也支持表达式,例如:`UNIQUE KEY (product_id, sipHash64(city))` - -注意 - -建议 Unique key 设置不超过5个,以避免可能产生的性能影响: - -1. 在使用 memory index 的场景下,会占用大量内存; -2. 会延长存储数据对象的序列化和反序列化时间。 - -* version\_column(版本字段): 选择一个字段作为版本控制的依据,用于根据版本更新,使用示例可查看例2。在设计表结构时,建议优先考虑分区值作为版本,减少内存占用。 - -其他的字段设置,如 `Order By`,`Partition By`等,和 MergeTree 家族的其他引擎的设置规则一致。 - -### Settings 设置项 - -| **参数名** | 常用字段 | **默认值** | **说明** | -| --- | --- | --- | --- | -| partition\_level\_unique\_keys | 是 | 1 | 0:UNIQUE KEY 表粒度唯一 1:UNIQUE KEY 分区粒度唯一 推荐选择:优选分区粒度唯一(即默认值)。如果业务语义必须使用表粒度唯一,考虑设置更短的 TTL、采用更粗粒度的分区(例如按月分区)等方式减少分区数量。 | -| enable\_unique\_partial\_update | 是 | 0 | 允许部分列更新写入(需要新引擎版本支持) | -| enable\_disk\_based\_unique\_key\_index | 是 | 1 | 0:in-memory mode。在此方式下,系统会在每张unique表维护一个in-memory key index,因此能支撑的数据量受限于内存 ; 1:disk-based mode。在此方式下,不限制数据量,但是性能比 in-memory 方式低 10-30%(插入数据越频繁,导入速度损失越大) 推荐选择:建议 整体数据量 < 1亿条\*集群 Shard 数时,选择 in-memory 模式,此外都选择 disk-based 模式。 | - -使用示例 -## 例1:分区级别唯一键 - -假设表Schema如下: - -``` --- 引擎默认保证 unique key 在分区内的唯一性 --- 注:UNIQUE KEY 不支持 Nullable -CREATE TABLE t1 -( - `event_time` DateTime, - `product_id` UInt64, - `city` String, - `category` String, - `amount` UInt32, - `revenue` UInt64 -) -ENGINE = HaUniqueMergeTree('/clickhouse/default/t1/{shard}', '{replica}') -PARTITION BY toDate(event_time) -ORDER BY (city, category) -UNIQUE KEY product_id; - -``` - -插入数据。此时,写入相同 key 的数据可以实现更新(upsert语义)。 - -``` -INSERT INTO t1 VALUES -('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), -('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200), -('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100); - -INSERT INTO t1 VALUES -('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 4, 400), -('2020-10-29 23:50:00', 10003, 'Beijing', '男装', 2, 200), -('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100), -('2020-10-30 00:00:05', 10001, 'Beijing', '男装', 1, 100), -('2020-10-30 00:00:05', 10002, 'Beijing', '男装', 2, 200); - -``` - -查询自动返回每个key最新的数据: - -``` -select * from t1 order by toDate(event_time), product_id; -┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ -│ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 5 │ 500 │ -│ 2020-10-29 23:50:00 │ 10002 │ Beijing │ 男装 │ 4 │ 400 │ -│ 2020-10-29 23:50:00 │ 10003 │ Beijing │ 男装 │ 2 │ 200 │ -│ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-30 00:00:05 │ 10001 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-30 00:00:05 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ -└─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘ - -``` - -UNIQUE KEY 也可以包含多个字段和表达式,如下面以两个字段:product\_id, sipHash64(city)为例: - -``` --- UNIQUE KEY 可以包含多个字段和表达式 -CREATE TABLE t1m -( - `event_time` DateTime, - `product_id` UInt64, - `city` String, - `category` String, - `amount` UInt32, - `revenue` UInt64 -) -ENGINE = HaUniqueMergeTree('/clickhouse/default/t1m/{shard}', '{replica}') -PARTITION BY toDate(event_time) -ORDER BY (city, category) -UNIQUE KEY (product_id, sipHash64(city)); - -INSERT INTO t1m VALUES -('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), -('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200), -('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100), -('2020-10-29 23:50:00', 10002, 'Shanghai', '男装', 4, 400), -('2020-10-29 23:50:00', 10003, 'Beijing', '男装', 2, 200), -('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100); - -select * from t1m; -┌──────────event_time─┬─product_id─┬─city─────┬─category─┬─amount─┬─revenue─┐ -│ 2020-10-29 23:40:00 │ 10001 │ Beijing │ 男装 │ 5 │ 500 │ -│ 2020-10-29 23:40:00 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ -│ 2020-10-29 23:50:00 │ 10003 │ Beijing │ 男装 │ 2 │ 200 │ -│ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-29 23:50:00 │ 10002 │ Shanghai │ 男装 │ 4 │ 400 │ -└─────────────────────┴────────────┴──────────┴──────────┴────────┴─────────┘ - -``` -## 例2:表级别唯一键 - -假设表Schema如下: - -``` -CREATE TABLE t2 -( - `event_time` DateTime, - `product_id` UInt64, - `city` String, - `category` String, - `amount` UInt32, - `revenue` UInt64 -) -ENGINE = HaUniqueMergeTree('xxxxxxx') -PARTITION BY toDate(event_time) --分区字段 -ORDER BY (city, category) --排序字段 -UNIQUE KEY product_id --唯一键 -SETTINGS partition_level_unique_keys = 0; --设置表级别唯一 - -``` - -顺序插入以下测试数据: - -``` -INSERT INTO t2 (event_time, product_id, city, category, amount, revenue) VALUES -('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), -('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200), -('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100); - -INSERT INTO t2 (event_time, product_id, city, category, amount, revenue) VALUES -('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 4, 400), -('2020-10-29 23:50:00', 10003, 'Beijing', '男装', 2, 200), -('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100), -('2020-10-30 00:00:05', 10001, 'Beijing', '男装', 1, 100), -('2020-10-30 00:00:05', 10002, 'Beijing', '男装', 2, 200); - -``` - -可以看到,10001,10002,10003 这三个产品都更新到了最新数据,且10001,10002 都从 2020-10-29 分区更新到了 2020-10-30 分区。 - -``` -select * from t2 order by toDate(event_time), product_id; -┌──────event_time─┬product_id─┬─city──┬category─┬amount─┬revenue─┐ -│ 2020-10-29 23:50:00 │ 10003 │ Beijing │ 男装 │ 2 │ 200 │ -│ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-30 00:00:05 │ 10001 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-30 00:00:05 │ 10002 │ Beijing │ 男装 │ 2 │ 200 │ -└─────────────┴───────┴─────┴──────┴─────┴─────┘ - -``` -## 例3:自定义版本字段使用 - -默认情况下,相同 unique key 后写入的数据会覆盖已有的数据。这可能会带来以下问题 - -* 回溯上游数据时,老数据可能覆盖新数据,导致查询到的数据结果出现回退 -* Lambda 架构下,如果离线和实时任务同时写一个分区,最终保留哪条数据取决于任务的执行顺序 - -为了解决上面的问题,HaUniqueMergeTree 支持将表中的某个字段指定为版本字段。引擎保证写入相同 key 的数据时,只有数据版本 >= 已有版本时,才会进行覆盖。版本字段支持所有UInt类型和Data/DateTime,且不能为 Nullable。 - -说明 - -使用版本字段时有以下限制: - -* 如需要使用整数作为版本字段,建议使用兼容UInt64的无符号整数 -* 支持Date、Datetime时间类型作为版本字段 -* 不支持float、Decimal、DateTime64等浮点数类型作为版本字段 - -假设schema如下: - -``` -CREATE TABLE t3 -( - `event_time` DateTime, - `product_id` UInt64, - `city` String, - `category` String, - `amount` UInt32, - `revenue` UInt64 -) -ENGINE = HaUniqueMergeTree('/clickhouse/default/t3/{shard}', '{replica}', event_time) --event_time为版本字段 -PARTITION BY toDate(event_time) --分区字段 -ORDER BY (city, category) --排序字段 -UNIQUE KEY product_id; --唯一键 - -``` - -顺序插入以下数据: - -``` -INSERT INTO t3 (event_time, product_id, city, category, amount, revenue) VALUES -('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), -('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200), -('2020-10-29 23:50:00', 10001, 'Beijing', '男装', 8, 800), -('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 5, 500); - -``` - -结果保留后两条。 - -``` -select * from t3 order by toDate(event_time), product_id; -┌──────event_time─┬product_id─┬─city──┬category─┬amount─┬revenue─┐ -│ 2020-10-29 23:50:00 │ 10001 │ Beijing │ 男装 │ 8 │ 800 │ -│ 2020-10-29 23:50:00 │ 10002 │ Beijing │ 男装 │ 5 │ 500 │ -└─────────────┴───────┴─────┴──────┴─────┴─────┘ - -``` - -若在此时重新导入回溯前两条数据。 - -``` -INSERT INTO t3 (event_time, product_id, city, category, amount, revenue) VALUES -('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500), -('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200); - -``` - -由于版本 < 已有版本,写入时自动跳过。10001 和 10002 的版本没有回退。 - -``` -select * from t3 order by toDate(event_time), product_id; -┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ -│ 2020-10-29 23:50:00 │ 10001 │ Beijing │ 男装 │ 8 │ 800 │ -│ 2020-10-29 23:50:00 │ 10002 │ Beijing │ 男装 │ 5 │ 500 │ -└─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘ - -``` -## 例4:使用分区值作为版本 - -考虑以下Lambda架构的需求场景: - -* 表按天分区,需要基于某个字段实现表粒度去重 -* 实时任务写T+0分区,可能将一些数据从 T+N 分区更新到 T+0 分区 -* 离线任务每天重写 T+1 分区 -* 每个 key 的最新数据需要从其所在的最新分区读取 - -我们可以将分区字段(日期)作为版本字段来实现该场景,然而这需要额外存储一个日期字段。由于分区下所有数据的日期都是一样的,这样做显然存在资源浪费。 - -为了优化该场景,HaUniqueMergeTree 支持直接使用分区表达式作为版本。当引擎发现版本字段为分区字段时,会自动从元数据中读取版本,避免额外的数据读写。 - -``` --- 创建一张按天分区、表粒度唯一的 unique 表,使用分区字段作为版本 -CREATE TABLE t4 -( - `event_time` DateTime, - `product_id` UInt64, - `city` String, - `category` String, - `amount` UInt32, - `revenue` UInt64 -) -ENGINE = HaUniqueMergeTree('/clickhouse/default/t4/{shard}', '{replica}', toDate(event_time)) -PARTITION BY toDate(event_time) -ORDER BY (city, category) -UNIQUE KEY product_id -SETTINGS partition_level_unique_keys = 0; - --- 10-29 实时任务数据写入 -INSERT INTO t4 VALUES -('2020-10-29 10:00:00', 10001, 'Beijing', '男装', 5, 500), -('2020-10-29 10:00:00', 10002, 'Beijing', '男装', 2, 200), -('2020-10-29 10:10:00', 10001, 'Beijing', '男装', 8, 800), -('2020-10-29 10:10:00', 10002, 'Beijing', '男装', 5, 500); --- 10-30 实时任务数据写入,将 10002 更新到 10-30 分区 -INSERT INTO t4 VALUES -('2020-10-30 08:00:00', 10002, 'Beijing', '男装', 10, 1000), -('2020-10-30 08:00:00', 10003, 'Beijing', '男装', 3, 300); --- 10-30 离线任务重写 10-29 数据 -INSERT INTO t4 VALUES -('2020-10-29 10:10:00', 10001, 'Beijing', '男装', 7, 700), -('2020-10-29 10:10:00', 10002, 'Beijing', '男装', 5, 500); --- 离线任务只会覆盖 10001 数据,10002 保留 10-30 中的最新数据 -select * from t4 order by toDate(event_time), product_id; -┌──────────event_time─┬─product_id─┬─city────┬─category─┬─amount─┬─revenue─┐ -│ 2020-10-29 10:10:00 │ 10001 │ Beijing │ 男装 │ 7 │ 700 │ -│ 2020-10-30 08:00:00 │ 10002 │ Beijing │ 男装 │ 10 │ 1000 │ -│ 2020-10-30 08:00:00 │ 10003 │ Beijing │ 男装 │ 3 │ 300 │ -└─────────────────────┴────────────┴─────────┴──────────┴────────┴─────────┘ - -``` -## 例5:删除字段使用 - -在某些应用场景下,用户希望在INSERT时加上一个字段来标识是否删除来扩展INSERT语义。 - -在HaUniqueMergeTree引擎中,为每张表都添加了一个保留字段`_delete_flag_`,可在 INSERT / INSERT SELECT 时指定,其类型为`UInt8`, 0 表示数据写入,非 0 表示数据删除。 - -需要注意的是,`_delete_flag_`字段仅可在 INSERT / INSERT SELECT 或者创建物化视图时指定,不可以在 CREATE TABLE 时指定,也不可查询该字段。 - -说明 - -删除行功能可以在对底表为HaUniqueMergeTree的分布式表进行操作,前提是分布式表需要配置参数**remote\_table\_is\_ha\_unique = 1。** - -用法示例如下: - -假设schema如下: - -``` -CREATE TABLE t1 -( - `event_time` DateTime, - `product_id` UInt64, - `city` String, - `category` String, - `amount` UInt32, - `revenue` UInt64, -) -ENGINE = HaUniqueMergeTree(xxxx) -PARTITION BY toDate(event_time) -ORDER BY (city, category) -UNIQUE KEY product_id; --唯一键 - -``` - -导入以下数据: - -> 注:需要选择单个节点,并插入本地表来使用 delete flag 做数据删除 - -``` -INSERT INTO t1_local (event_time, product_id, city, category, amount, revenue, _delete_flag_) VALUES -('2020-10-29 23:40:00', 10001, 'Beijing', '男装', 5, 500, 0), -('2020-10-29 23:40:00', 10002, 'Beijing', '男装', 2, 200, 0), -('2020-10-29 23:40:00', 10003, 'Beijing', '男装', 1, 100, 0), -('2020-10-29 23:50:00', 10001, 'Beijing', '男装', 4, 400, 5), -('2020-10-29 23:50:00', 10002, 'Beijing', '男装', 2, 200, 1), -('2020-10-29 23:50:00', 10004, 'Beijing', '男装', 1, 100, 0); - -``` - -查询结果中包含了新加入的一行数据,并删除了两行旧数据: - -``` -select * from t1 order by toDate(event_time), product_id; -┌──────event_time─┬─product_id┬─city──┬─category┬amount─┬revenue─┐ -│ 2020-10-29 23:40:00 │ 10003 │ Beijing │ 男装 │ 1 │ 100 │ -│ 2020-10-29 23:50:00 │ 10004 │ Beijing │ 男装 │ 1 │ 100 │ -└─────────────┴───────┴─────┴──────┴─────┴─────┘ - -``` - -针对某个 where 条件对多行数据进行批量删除的方式如下: - -``` -insert into `t1_local` (*, _delete_flag_) select *, 1 as _delete_flag_ from `t1_local` where product_id=10001; - -``` -## 例6:部分列更新 - -**使用条件:** - -1. **当前仅支持部分列更新功能。** -2. **不支持表级唯一**的部分列更新,仅支持分区级唯一的部分列更新。 -3. 如果是用Kafka导入数据,需要对Unique表打开开关 **enable\_unique\_partial\_update = 1。** -4. 可以对底表为HaUniqueMergeTree的分布式表进行操作,前提是分布式表需要配置参数**remote\_table\_is\_ha\_unique = 1。** - -**行更新模式:缺省列采用默认值填充** -**部分列更新模式:缺省列如果有原值则保留,否则填充默认值** - -1. unique表指定变量**enable\_unique\_partial\_update = 1**后允许写入以部分列更新模式进行,默认关闭。 -2. 部分列写入模式当且仅当unique表开启了部分列更新功能才有效,否则等效为行更新模式: - - 1. 对于交互式写入,Unique表默认为部分列更新模式,指定会话变量**enable\_unique\_partial\_update** = 0后切换到行更新模式。即参数**enable\_unique\_partial\_update**默认值为1。 - 2. 对于Kafka实时写入,kafka表新增**enable\_unique\_partial\_update**参数(默认值为1),1表示kafka消费使用部分列更新模式,0表示使用行更新模式。 -3. 类型的默认值 - - 1. 数值类型:0 - 2. 字符串类型:'' - 3. Nullable类型:null - 4. Map类型(更新规则比较特殊,详见下方 **5.列更新规则-Map类型**):{} - 5. Array类型:[] -4. 部分列更新写入模式时如果表包含版本字段 - - 1. 写入版本<当前版本,忽略写入行 - 2. 写入版本>=当前版本,应用列更新规则 - 3. 写入未指定版本时,版本字段取默认值 -5. 列更新规则 - - 1. 方式一:按功能列\_update\_columns\_(String类型)区分更新列方案的列更新规则 - - 1. \_update\_columns\_中的内容是需要更新的列,以**逗号分隔各列名**,引擎在解析时**不会处理列名前后的特殊字符**,如空格、Tab、换行符等,且**不支持正则表达式**。 - 2. **当\_update\_columns\_为空时表示更新所有列** - 3. 非Map类型:写入非默认值表示更新,不允许更新为默认值 - 4. Map类型:**partial\_update\_enable\_merge\_map = true**时对有旧值的key进行更新,对无旧值的key进行写入;为false时直接替换value - ``` - CREATE TABLE t1 ( - k Int32, - c1 Int32, - c2 Nullable(Float64), - c3 Nullable(String), - c4 Nullable(Int64), - m1 Map(String, Int32), - a1 Array(String)) - ENGINE = HaUniqueMergeTree('/clickhouse/default/t1/{shard}', '{replica}') - UNIQUE KEY k ORDER BY k SETTINGS enable_unique_partial_update = 1, - partial_update_enable_specify_update_columns = 1, - partial_update_enable_merge_map = 0; - - SET enable_unique_partial_update = 1; - - INSERT INTO t1 (k, c1, c2, m1, a1) VALUES (1, 10, 3.14, {'k1':1}, ['hello']); - -- 此时解析时会填充_update_columns_为'k,c1,c2,m1,a1' - ┌─k─┬─c1─┬───c2─┬─c3───┬───c4─┬─m1───────┬─a1────────┐ - │ 1 │ 10 │ 3.14 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ {'k1':1} │ ['hello'] │ - └───┴────┴──────┴──────┴──────┴──────────┴───────────┘ - - INSERT INTO t1 (k, c1, c2, m1, a1, _update_columns_) VALUES (1, 20, 31.4, {'k2':2}, ['world'], 'k,c1,m1,a1'); - ┌─k─┬─c1─┬───c2─┬─c3───┬───c4─┬─m1───────┬─a1────────┐ - │ 1 │ 20 │ 3.14 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ {'k2':2} │ ['world'] │ - └───┴────┴──────┴──────┴──────┴──────────┴───────────┘ - - INSERT INTO t1 (k, c1, c3, m1) VALUES (1, 0, 'foo', {'k3':3}); - -- 此时解析是会填充_update_columns_为'k,c1,c3,m1' - -- 等价于INSERT INTO t1 (k, c1, c3, m1, _update_columns_) VALUES (1, 0, 'foo', {'k2':2}, 'k, c1, c3, m1'); - -- 此时c1会强制更新为0,c3强制更新为'foo',m1强制更新为'k3':3 - ┌─k─┬─c1─┬───c2─┬─c3──┬───c4─┬─m1───────┬─a1────────┐ - │ 1 │ 0 │ 3.14 │ foo │ ᴺᵁᴸᴸ │ {'k3':3} │ ['world'] │ - └───┴────┴──────┴─────┴──────┴──────────┴───────────┘ - - INSERT INTO t1 (k, c1, c2, c3, c4, m1, a1, _update_columns_) VALUES (1, 10, 31.4, 'goo', 15, {'k4': 4}, ['hello', 'world'], ''); - -- _update_columns_为空时表示更新所有列 - ┌─k─┬─c1─┬───c2─┬─c3──┬─c4─┬─m1───────┬─a1────────────────┐ - │ 1 │ 10 │ 31.4 │ goo │ 15 │ {'k4':4} │ ['hello','world'] │ - └───┴────┴──────┴─────┴────┴──────────┴───────────────────┘ - - ``` - 2. 方式二:按类型默认值区分更新列方案的列更新规则 - - 1. 非Map类型:写入非默认值表示更新,不允许更新为默认值 - 2. Map类型:**partial\_update\_enable\_merge\_map = true**时对有旧值的key进行更新,对无旧值的key进行写入;为false时直接替换value - ``` - CREATE TABLE t1 ( - k Int32, - c1 Int32, - c2 Nullable(Float64), - c3 Nullable(String), - c4 Nullable(Int64), - m1 Map(String, Int32), - a1 Array(String)) - ENGINE = HaUniqueMergeTree('/clickhouse/default/t1/{shard}', '{replica}') - UNIQUE KEY k ORDER BY k SETTINGS enable_unique_partial_update = 1, - partial_update_enable_specify_update_columns = 0, - partial_update_enable_merge_map = 1; - - SET enable_unique_partial_update = 1; - - INSERT INTO t1 (k, c1, c2, m1, a1) VALUES (1, 10, 3.14, {'k1':1}, ['hello']); - ┌─k─┬─c1─┬───c2─┬─c3───┬───c4─┬─m1───────┬─a1────────┐ - │ 1 │ 10 │ 3.14 │ ᴺᵁᴸᴸ │ ᴺᵁᴸᴸ │ {'k1':1} │ ['hello'] │ - └───┴────┴──────┴──────┴──────┴──────────┴───────────┘ - - INSERT INTO t1 (k, c1, c3, m1) VALUES (1, 0, 'foo', {'k2':2}); - -- 对于未指定字段 - -- - c1为非组合类型,但写入的是默认值,因此保留旧值 - -- - c2/c4为Nullable类型,保留旧值 - -- - a1为Array空值,保留旧值 - -- 对于m1,更新m1{'k2'},保留m1{'k1'} - ┌─k─┬─c1─┬───c2─┬─c3──┬───c4─┬─m1──────────────┬─a1────────┐ - │ 1 │ 10 │ 3.14 │ foo │ ᴺᵁᴸᴸ │ {'k1':1,'k2':2} │ ['hello'] │ - └───┴────┴──────┴─────┴──────┴─────────────────┴───────────┘ - - INSERT INTO t1 (k, c1, c2, c3, c4, m1, a1) VALUES (1, 20, null, 'bar', 30, {'k2':0, 'k3':3}, ['world']); - -- c1不是默认值,更新 - -- c2为null,保留旧值;c3/c4不为null,更新 - -- 更新m1{'k2'}, m1{'k3'},保留m1{'k1'} - -- a1不为空值,更新 - ┌─k─┬─c1─┬───c2─┬─c3──┬─c4─┬─m1─────────────────────┬─a1────────┐ - │ 1 │ 20 │ 3.14 │ bar │ 30 │ {'k1':1,'k2':0,'k3':3} │ ['world'] │ - └ ───┴────┴──────┴─────┴────┴────────────────────────┴───────────┘ - - ``` - - 【注意】使用这种方式在建表时 **慎用默认值!** - - 在建表时可以在字段后指定默认值,默认值可以为表达式。如果用户指定了默认值,那么写入时对于缺省的列会按照建表时指定的方式进行填充,而部分列更新判断是否为默认值时是按照引擎内数据类型的默认值进行判断,因此可能会产生不符合预期的行为。下面将举个例子进行说明 - - ``` - CREATE TABLE t1 ( - k Int32, - c1 Int32 DEFAULT k + 1, - c2 Nullable(Float64)) - ENGINE = HaUniqueMergeTree('/clickhouse/default/t1/{shard}', '{replica}') - UNIQUE KEY k ORDER BY k SETTINGS enable_unique_partial_update = 1, - partial_update_enable_specify_update_columns = 0; - - insert into t1 values (1, 10, 3.14); - ┌─k─┬─c1─┬───c2─┐ - │ 1 │ 10 │ 3.14 │ - └───┴────┴──────┘ - - insert into t1 (k, c2) values (1, 31.4); - ┌─k─┬─c1─┬───c2─┐ - │ 1 │ 2 │ 31.4 │ - └───┴────┴──────┘ - -- 如果在建表时没有指定从c1的默认值,那么这条写入的语义是将k=1的那条数据的c2被更新为31.4,但是由于建表指定了默认值,等价于insert into t1 values (1, 2,31.4),因此c1也被更新为了2。这种情况下为了达到仅更新c2的目的,可以有以下两种方式:1. 建表时不要使用默认值;2. 显示指定默认值,即insert into t1 values (1, 0,31.4) - -- ┌─k─┬─c1─┬───c2─┐ - -- │ 1 │ 10 │ 31.4 │ - -- └───┴────┴──────┘ - - ``` - -性能 - -在使用 in-memory 模式下,HaUniqueMergeTree 的导入性能约是 HaMergeTree 或 社区的 MergeTree 引擎的一半,为 10k rows/s/shard,或 20 MB/s。性能相比社区的 ReplacingMergeTree 也接近一致。 - -但查询性能上,HaUniqueMergeTree 的性能和 HaMergeTree 或 MergeTree 引擎一致。 - -在使用 disk-based 模式下,HaUniqueMergeTree 的导入性能约是 HaMergeTree 或 社区的 MergeTree 引擎的 30-40%,为 6-8k rows/s/shard,或 12-16 MB/s。查询性能依旧不变。 - -引擎限制 - -* 在 Kafka 导入时,用户需要保证相同唯一键的数据写入同一个的 Topic Partition,并禁用 Topic 扩容; -* 唯一键所在的集群暂不支持扩容; -* 内存索引模式下,内存使用与唯一键大小及基数成正比,不适合单节点数据量超过 1 亿的表,如果使用不当会导致节点OOM;需要启用磁盘索引模式。 - -最佳使用实践 -## 建表参数推荐 - -### In-memory 与 Disk-based 选择 - -目前unique 表有两种key index使用方式:in-memory 和 disk-based。由表级参数`enable_disk_based_unique_key_index`控制。 - -* Disk-based 的使用方式不限制全表数据量,但是性能比 in-memory 模式的导入地低 40%。约为 6 MB/s Shard。 -* In-memory 性能更好,可达 10MB/s,但会限制数据量,建议如下: - + 【表粒度唯一】时,所有分区的内存索引都需要常驻内存,因此不建议对单节点超过1亿条数据的表启用唯一键。如果集群有20个分片,那么最多支持20亿条数据。 - + 【分区粒度唯一】时,这种情况只有最近有数据写入的分区需要加载内存索引。因此如果业务只会更新最近 N 天的数据,那么只要单节点上 N 天的数据条数不超过1亿,就可以使用 in-memory 模式。 - -### 唯一键选择 - -如果包含很多字段,考虑使用哈希值。例:`UNIQUE KEY sipHash64(val1,val2,val3....)` - -### 唯一键级别 - -优先使用默认的分区级别唯一。 - -如果业务场景必须使用表级别唯一,考虑设置 TTL、采用更粗粒度的分区(例如按月分区)等方式减少分区数量。 - -### 版本字段选择 - -如果需要指定版本字段,优先考虑分区值作为版本,减少内存占用。 - -## 表修改 - -1. Detach partition 和 Attach partiton 操作只能在 leader 节点执行; -2. 实时删除功能与唯一键级别、版本的作用规则: - * 实时删除级别和唯一键级别 保持一致,即唯一键级别为分区时删除仅会删除对应分区的数据。 - * 当指定版本(非0)时,实时删除会遵循版本规则;当不指定版本或者指定版本为0时,实时. 删除不会遵循版本规则。 -3. ByteHouse unique 表建立后,唯一键和唯一键列的类型均不可修改。 - -## Kafka 导入 - -Consumer 会直接写本地 shard。因此 - -* 业务需要保证**相同 unique key 的数据写入同一个 topic partition** -* 为了保证 key -> shard 的映射不变,**topic 需要设置固定大小的分区数,并禁用自动加减分区** -* 如果要同时通过批式和流式导入数据到同一张表,可以采用 批数据源 -> Kafka -> CH 的方式,保证离线数据的 sharding 方式与实时相同。 \ No newline at end of file From 932c8d1481f7a4c4e887426605459795b86bca96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8A=B3=E4=B8=80=E6=9F=AF?= Date: Wed, 30 Sep 2026 16:15:37 +0800 Subject: [PATCH 4/4] update toml version --- server/mcp_server_bytehouse/pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/mcp_server_bytehouse/pyproject.toml b/server/mcp_server_bytehouse/pyproject.toml index 6843425f..eb577ce9 100644 --- a/server/mcp_server_bytehouse/pyproject.toml +++ b/server/mcp_server_bytehouse/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp_bytehouse" -version = "0.1.0" +version = "0.2.0" description = "An MCP server for ByteHouse." readme = "README.md" license = "Apache-2.0"