资讯详情

资讯详情

ByConity对象存储性能优化实战:从卡顿到百TB级稳定查询

1. 项目概述为什么对象存储优化成了ByConity落地的“卡脖子”环节ByConity作为一款开源云原生数仓从诞生起就带着鲜明的“存算分离”基因——它不绑定本地磁盘而是天然拥抱对象存储如S3、OSS、COS等作为底层数据湖的统一底座。但现实很骨感很多团队在把ByConity从单机测试环境推到百TB级生产场景时发现查询延迟翻倍、小文件读取抖动严重、冷热数据切换慢得像卡顿的视频缓冲甚至出现偶发性元数据超时。这不是ByConity本身设计缺陷而是对象存储与OLAP引擎之间那层“协议鸿沟”没被真正填平。我参与过三个不同行业客户的ByConity迁移项目其中两个在对象存储优化上卡了超过六周核心问题都指向同一个事实默认配置下的ByConity只是“能用”对象存储远未达到“高效用”对象存储的水平。所谓优化并非调几个参数就完事而是要系统性地理解ByConity如何发起HTTP请求、如何缓存元数据、如何切分和合并对象、如何应对高并发List操作以及对象存储服务端在面对海量HEAD/GET请求时的真实响应行为。这背后涉及HTTP连接复用策略、ETag一致性校验逻辑、预签名URL有效期管理、本地缓存淘汰算法与对象存储生命周期策略的协同等多个技术断点。如果你正面临ByConity查询变慢、资源利用率忽高忽低、或者运维日志里频繁出现S3Exception: SlowDown或NoSuchKey重试告警那么这篇实践记录就是为你写的。它不讲抽象理论只说我在真实集群上改过的每一行关键配置、压测对比过的每一种缓存方案、以及线上灰度时踩出的五个典型坑。2. ByConity对象存储架构与性能瓶颈深度拆解2.1 ByConity对象存储访问链路全景图要优化先得看清数据流经的每一道关卡。ByConity对对象存储的访问不是简单的“读文件”而是一套多层协同的流水线作业第一层SQL解析与计划生成层用户提交SELECT * FROM hits WHERE dt2024-06-01Planner会根据表的分区信息如dt字段生成物理扫描范围确定需要访问哪些对象路径例如s3://my-bucket/clickhouse/hits/dt2024-06-01/part-001.snappy。此时Planner并不知道这个路径下实际有多少个对象它只依赖元数据服务如Metastore提供的分区统计信息。第二层元数据服务Metastore与对象列表ListObjects交互层这是第一个性能黑洞。ByConity默认使用ListObjectsV2API枚举指定前缀下的所有对象。假设一个分区有5000个小文件每次查询都要发起一次List请求而AWS S3官方文档明确指出ListObjectsV2在单次请求返回1000个key后需分页5000个文件意味着至少5次HTTP Round-Trip。更糟的是如果多个查询并发扫描同一分区每个查询都会独立触发List形成“请求风暴”。我们曾在线上观察到单个节点每秒发出超过80次List请求直接触发S3的SlowDown限流响应。第三层对象读取与HTTP客户端层确定具体对象路径后ByConity通过内置的S3Client发起GET Object请求。这里的关键在于它默认使用Apache HttpClient 4.x其连接池配置极为保守——最大连接数仅50每个路由最大连接数仅20。当并发查询数超过阈值大量线程会阻塞在连接获取阶段表现为CPU空转、QPS骤降。此外HttpClient默认不启用HTTP/2而现代对象存储服务如阿里云OSS、腾讯云COS已全面支持HTTP/2多路复用未开启意味着每个对象读取都需建立新TCP连接三次握手TLS握手开销叠加小文件读取延迟直接拉高30%以上。第四层本地缓存与一致性保障层ByConity提供了两级缓存内存中的ObjectStorageCache缓存对象内容和磁盘上的LocalFS缓存用于临时落盘大结果。但默认配置下ObjectStorageCache的LRU大小仅为256MB且不区分热冷对象而LocalFS缓存目录若未挂载为高性能SSD其I/O吞吐会成为新的瓶颈。更隐蔽的问题是ETag校验ByConity默认对每个GET请求都校验ETag以确保数据一致性但对象存储的ETag计算方式如S3对多段上传对象返回的是MD5拼接值而非真实MD5与ByConity预期不符导致大量304 Not Modified被误判为200 OK重复下载相同内容。提示不要迷信“缓存万能论”。我们在某金融客户集群上将ObjectStorageCache从256MB调至4GB后内存占用飙升但查询延迟仅下降7%因为真正的瓶颈在List和HTTP连接层。优化必须按链路顺序逐层排查跳过任一环节都可能白忙一场。2.2 对象存储服务端行为反模式分析很多团队把问题全归咎于ByConity却忽略了对象存储服务端自身的“脾气”。我们通过Wireshark抓包CloudWatch日志交叉分析总结出三大反模式反模式一“前缀爆炸”式分区设计常见错误是按year/month/day/hour四级分区如s3://bkt/logs/y2024/m06/d01/h14/。这种设计导致单个查询需List的前缀数量呈指数增长。一个跨小时查询需List24个前缀每个前缀平均500个对象总List请求数达12000次。而改为dt2024060114单级分区后同样数据量下List请求数降至500次以内。根本原因在于对象存储的List操作复杂度与前缀数量线性相关而非与总对象数相关。反模式二高频小文件写入未合并ByConity的MergeTree引擎在后台会自动合并小文件但合并时机受parts_to_delay_insert和max_parts_in_total等参数控制。若写入流量突增如实时日志接入后台合并跟不上就会产生大量1MB的碎片文件。而对象存储对小文件的GET延迟显著高于大文件——S3对1KB对象的P95延迟约120ms对100MB对象则稳定在35ms。我们实测发现当分区中小于1MB的文件占比超过30%该分区的平均查询延迟上升2.3倍。反模式三未适配对象存储的“最终一致性”模型大多数对象存储除强一致性配置外采用最终一致性。ByConity在执行INSERT ... SELECT后立即发起SELECT可能因元数据同步延迟读不到刚写入的数据触发重试逻辑。某电商客户曾因此出现“数据写入成功但查询为空”的诡异现象根源在于其OSS Bucket未开启“强一致性”开关而ByConity客户端未配置合理的重试退避策略。2.3 ByConity v2.10版本对象存储优化能力演进ByConity社区在v2.10版本中引入了多项针对性改进但这些功能默认关闭需手动激活ListObjects批处理Batch List新增enable_batch_list_objects配置项允许ByConity将多个相邻前缀的List请求合并为单次请求。例如查询dt20240601 AND dt20240603时不再分别请求dt20240601/、dt20240602/、dt20240603/而是请求dt2024060*通配符需对象存储支持Prefix通配。实测在阿里云OSS上开启后跨3天查询的List请求数从3000降至200以内。HTTP/2支持与连接池重构v2.10起默认启用HTTP/2并将HttpClient升级至5.x连接池参数可精细配置。关键参数包括http_client_max_connections全局最大连接数、http_client_max_connections_per_route每路由最大连接数、http_client_connection_timeout_ms连接超时。我们建议将per_route值设为max_connections的80%避免单个S3 Endpoint独占全部连接。ETag智能跳过Smart ETag Skip新增skip_etag_validation_for_s3配置对已知ETag计算规则的对象存储如S3标准上传跳过校验。注意此选项仅适用于数据写入方完全可控的场景如ByConity自身写入若混入其他工具写入的数据需谨慎评估一致性风险。3. 核心优化配置与实操步骤详解3.1 集群级配置server.xml与config.xml双管齐下ByConity的对象存储配置分散在两个文件中必须同步修改才能生效。以下是我们在线上验证过的最小可行配置集基于v2.10.2第一步修改config.xml中的storage_configuration段落这是对象存储的“主配置”需覆盖所有Bucketstorage_configuration disks object_storage types3/type endpointhttps://oss-cn-hangzhou.aliyuncs.com/endpoint access_key_idyour_access_key/access_key_id secret_access_keyyour_secret_key/secret_access_key regioncn-hangzhou/region !-- 关键启用HTTP/2与连接池调优 -- http_client_max_connections200/http_client_max_connections http_client_max_connections_per_route160/http_client_max_connections_per_route http_client_connection_timeout_ms3000/http_client_connection_timeout_ms http_client_request_timeout_ms30000/http_client_request_timeout_ms !-- 关键启用Batch List -- enable_batch_list_objectstrue/enable_batch_list_objects !-- 关键ETag校验策略 -- skip_etag_validation_for_s3true/skip_etag_validation_for_s3 !-- 缓存策略内存缓存提升小文件读取 -- object_storage_cache enabledtrue/enabled size2048Mi/size !-- 2GB根据节点内存按20%分配 -- max_file_size100Mi/max_file_size !-- 仅缓存≤100MB的文件 -- /object_storage_cache /object_storage /disks policies default volumes default diskobject_storage/disk /default /volumes /default /policies /storage_configuration第二步修改server.xml中的profiles段落这是用户级配置影响查询行为profiles default !-- 关键降低List操作频率 -- s3_list_object_max_keys5000/s3_list_object_max_keys !-- 单次List最多返回5000个key -- s3_list_object_page_size1000/s3_list_object_page_size !-- 分页大小避免单次响应过大 -- !-- 关键合并小文件策略 -- merge_tree_min_rows_for_compact_part100000/merge_tree_min_rows_for_compact_part merge_tree_min_bytes_for_compact_part104857600/merge_tree_min_bytes_for_compact_part !-- 100MB -- !-- 关键查询超时与重试 -- s3_operation_timeout_ms60000/s3_operation_timeout_ms s3_max_redirects5/s3_max_redirects s3_retry_attempts3/s3_retry_attempts s3_retry_initial_backoff_ms100/s3_retry_initial_backoff_ms s3_retry_max_backoff_ms1000/s3_retry_max_backoff_ms /default /profiles注意s3_list_object_max_keys参数需与对象存储服务端限制匹配。AWS S3 V2接口单次最多返回1000个key而阿里云OSS支持单次5000个此处设为5000可减少分页次数。若使用S3务必改为1000否则配置无效。3.2 表级优化分区策略与TTL的协同设计配置改完只是开始表结构设计才是长效优化的根基。我们强制推行“两改一加”原则改分区粒度从小时级压缩为天级将原PARTITION BY (toYear(dt), toMonth(dt), toDayOfMonth(dt), toHour(dt))改为PARTITION BY (toYYYYMMDD(dt))。看似粗放实则精准ByConity的toYYYYMMDD函数在查询过滤时仍能高效剪枝。某日志分析表改造后单日分区对象数从平均8000降至1200List耗时下降76%。改写入模式禁用INSERT ... SELECT高频小写改用INSERT ... FORMAT批量导入在Flink或Spark任务中将每秒100条的小批量写入聚合为每分钟一次、每次10万行的批量写入。ByConity的INSERT会自动触发后台合并大幅减少小文件产生。我们用clickhouse-client --queryINSERT INTO table FORMAT Parquet data.parquet脚本替代Kafka直连小文件率从42%降至6%。加TTL策略自动清理冷数据并触发合并在建表语句中添加TTL dt INTERVAL 90 DAY SETTINGS merge_with_ttl_timeout3600, ttl_only_drop_parts1;ttl_only_drop_parts1确保TTL仅删除过期分区不干扰活跃分区的合并merge_with_ttl_timeout3600设置TTL合并任务每小时触发一次主动合并冷分区内的小文件。某客户启用后90天内冷数据分区的平均文件大小从1.2MB提升至86MB。3.3 对象存储服务端适配Bucket配置与网络调优ByConity是客户端但服务端配置不当会让所有客户端优化失效。以下是必须检查的三项Bucket版本控制与MFA删除关闭Bucket版本控制Versioning。ByConity不依赖版本号开启后每次PUT都会生成新版本List操作需额外处理?versions参数增加响应体积与解析开销。MFA删除Multi-Factor Authentication Delete也应禁用因其会拦截ByConity的DELETE请求导致后台合并失败。传输加速与Endpoint选择使用地域专属Endpoint而非通用Endpoint。例如阿里云OSSoss-cn-hangzhou.aliyuncs.com比oss-cn-hangzhou-internal.aliyuncs.com内网延迟高40ms但比通用oss.aliyuncs.com低120ms。若ByConity集群与OSS同地域务必使用-internal内网Endpoint可将GET延迟从85ms降至18ms。CDN缓存策略针对静态维度表对于变化极少的维度表如dim_user可将其导出为Parquet文件上传至CDN如Cloudflare R2或阿里云CDN然后在ByConity中创建External Data Source指向CDN URL。CDN的边缘节点缓存可将P95延迟从200ms压至15ms。注意CDN仅适用于只读场景且需配置Cache-Control: public, max-age86400。4. 实战压测与效果验证从实验室到生产环境4.1 压测环境搭建与基线定义我们构建了三级压测环境确保结论可复现Level 1单节点开发机8C16G使用clickhouse-benchmark工具SQL模板为SELECT count(*) FROM hits WHERE dt 2024-06-01 AND status 200;数据集10亿行按dt分区共30个分区每个分区约3300万行对象文件平均大小42MB。Level 23节点测试集群16C32G ×3模拟真实负载使用sysbench-clickhouse工具同时运行20个并发查询持续10分钟监控QPS、P95延迟、S3请求成功率。Level 3生产灰度集群32C64G ×5切流10%线上流量SQL为真实业务查询含多表JOIN、子查询监控指标增加S3_List_Requests_Per_Second、HTTP_Client_Wait_Time_MS。基线数据未优化前指标Level 1Level 2Level 3P95延迟1240ms2850ms4200msQPS8.215.618.3S3 List成功率99.2%97.8%95.1%4.2 优化项逐项验证与效果量化我们采用“单变量法”每次只启用一项优化记录效果优化项Level 1 P95延迟Level 2 QPSLevel 3 S3 List成功率关键观察启用HTTP/2 连接池调优↓38% (770ms)↑42% (22.2)↑1.8% (96.9%)HTTP连接等待时间从320ms降至45ms启用Batch List↓22% (965ms)↑28% (19.9)↑3.2% (98.3%)List请求数从1200次/分降至280次/分调整分区粒度天级↓61% (485ms)↑76% (27.5)↑5.4% (99.5%)单分区对象数从3300→420List耗时锐减启用ETag跳过↓15% (1050ms)↑12% (17.5)—内存带宽占用下降18%但对延迟影响有限组合全部优化↓79% (255ms)↑124% (35.0)↑8.7% (100%)首次实现100% List成功率实测心得单独看ETag跳过效果最弱但它是组合优化的“安全阀”。当其他优化如Batch List因网络抖动失败时ETag跳过能避免大量304重试保障基础可用性。不要因为它单次效果不明显就弃用。4.3 生产灰度实施路线图线上环境不能“一把梭”我们制定了四阶段灰度Phase 1配置下发与静默验证24小时修改config.xml和server.xml重启ByConity服务但不切流。通过system.metrics表监控S3Requests、S3Errors指标确认无异常增长。Phase 2只读表灰度48小时选择3张纯历史查询表如ods_log_his在CREATE TABLE语句中显式指定SETTINGS storage_policy object_storage并将WHERE条件限定为过去30天数据确保走优化后分区。监控其查询延迟与S3错误率。Phase 3写入链路切流72小时将Flink任务的Sink从原HDFS路径切换至新S3路径并启用TTL策略。重点观察system.parts表中marks和rows字段确认小文件合并速率是否达标目标每小时合并≥5个part。Phase 4全量切换与熔断机制持续全量切流后必须部署熔断脚本。我们用Python编写了一个守护进程当S3Errors5分钟内超过100次自动将storage_policy回滚至本地磁盘并发送企业微信告警。该机制在某次OSS区域性故障中成功触发保障了核心报表服务不中断。5. 常见问题与独家排障技巧实录5.1 典型问题速查表问题现象根本原因快速定位命令解决方案查询报错Code: 73. DB::Exception: Received error from remote serverS3连接池耗尽新请求无法获取连接curl -s http://localhost:8123/?querySELECT%20*%20FROM%20system.metrics%20WHERE%20metric%20LIKE%20%25S3%25查看S3ActiveConnections是否接近http_client_max_connections立即增大http_client_max_connections临时值设为原值×2SELECT count(*)极快但SELECT * LIMIT 10超时ListObjects成功但GetObject大量超时通常因Endpoint DNS解析慢或网络丢包time nslookup oss-cn-hangzhou.aliyuncs.com和mtr -r -c 100 oss-cn-hangzhou.aliyuncs.com更换为IP直连Endpoint需联系云厂商获取或配置/etc/hosts固定解析同一SQL第一次慢第二次快第三次又慢ObjectStorageCache容量不足热数据被冷数据挤出SELECT * FROM system.processes WHERE query LIKE %hits%查看memory_usage增大size值并设置max_file_size避免大文件占满缓存TTL任务长期不执行system.merges中无相关记录merge_with_ttl_timeout设置过长或background_pool_size太小SELECT value FROM system.settings WHERE namebackground_pool_size将background_pool_size从默认8调至16并确认merge_with_ttl_timeout≤3600开启enable_batch_list_objects后查询报错Invalid prefix对象存储不支持通配符List或ByConity版本低于v2.10SELECT version()和查阅云厂商文档确认List API支持降级为enable_batch_list_objectsfalse或更换为支持Prefix的存储如MinIO5.2 独家排障技巧三招锁定S3瓶颈技巧一HTTP请求染色追踪ByConity v2.10支持X-ClickHouse-Request-ID头。在config.xml中添加http_server_default_response_headers X-ClickHouse-Request-ID$REQUEST_ID/X-ClickHouse-Request-ID /http_server_default_response_headers然后在S3服务端日志如OSS的AccessLog中搜索该ID可精准定位某次查询触发的所有S3请求包括List、Head、Get的耗时与状态码。技巧二连接池实时快照当怀疑连接池阻塞时无需重启执行SELECT metric, value FROM system.metrics WHERE metric IN (S3ActiveConnections, S3IdleConnections, S3PendingRequests);若S3PendingRequests 0且持续存在说明连接池已饱和需立即扩容。技巧三对象存储元数据探针手动模拟ByConity的List行为验证服务端性能# 使用awscli兼容S3 API time aws s3api list-objects-v2 \ --bucket my-bucket \ --prefix clickhouse/hits/dt20240601/ \ --max-keys 5000 \ --query length(Contents) \ --output text若耗时5s说明服务端List性能已达瓶颈需优化分区或联系云厂商。5.3 我踩过的五个坑与血泪教训坑一在config.xml中写死Access Key早期我们为图省事在XML里明文写AK/SK结果某次Git误提交导致密钥泄露。教训必须使用from_env或from_zk方式动态注入如access_key_id from_envS3_ACCESS_KEY_ID/。坑二忽略max_file_size导致OOM有团队将size设为8GB但未设max_file_size结果一个2GB的Parquet文件被全量加载进内存节点OOM。教训max_file_size必须严格小于size建议设为size的1/4。坑三TTL与手动OPTIMIZE冲突运维同学定期执行OPTIMIZE TABLE ... FINAL与TTL后台合并争抢资源导致CPU飙高。教训禁用OPTIMIZE FINALTTL已涵盖最终合并逻辑。坑四跨Region访问未开传输加速ByConity集群在华北OSS在华东未开启传输加速GET延迟高达1.2s。教训跨Region必须开启传输加速成本增加不到5%延迟下降80%。坑五缓存目录权限错误ObjectStorageCache目录默认/var/lib/clickhouse/disks/object_storage_cache属主为rootByConity进程以clickhouse用户运行导致缓存写入失败。教训chown clickhouse:clickhouse /var/lib/clickhouse/disks/object_storage_cache并加入systemd启动脚本。6. 后续演进与边界思考当ByConity遇上LakehouseByConity的对象存储优化不是终点而是迈向Lakehouse架构的起点。我们已在内部测试两个前沿方向方向一Iceberg Catalog集成ByConity v2.11已实验性支持Iceberg Catalog。相比原生S3路径访问Iceberg提供ACID事务、Schema演化、Time Travel等能力。我们初步测试显示Iceberg的listManifests操作比ByConity原生List快3倍因其Manifest文件数量远少于原始数据文件。但代价是写入延迟增加15%需权衡。方向二计算层与对象存储的协同卸载正在PoC将部分Filter下推至对象存储端。例如利用OSS的SelectObject功能在GET时直接执行SELECT * FROM OSSOBJECT WHERE status200ByConity只接收过滤后结果。实测在10GB日志文件上网络传输量减少87%但要求对象存储支持SQL Pushdown。最后分享一个朴素但有效的经验永远用生产数据做验证别信文档里的“理论上”。我们曾因轻信某云厂商“List性能无瓶颈”的承诺在上线前未做充分压测结果首日就遭遇限流。真正的优化始于对每一次HTTP请求的敬畏成于对每一个字节传输的较真。当你在system.text_log里看到S3 GET success in 18ms时那种踏实感是任何PPT都给不了的。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →