住房城乡建设部门户网站烟气脱硫加强机关门户网站建设方案
2026/2/20 1:24:15 网站建设 项目流程
住房城乡建设部门户网站烟气脱硫,加强机关门户网站建设方案,营销策划是干嘛的,wordpress 菜单两列显示本文字数#xff1a;16645#xff1b;估计阅读时间#xff1a;42 分钟 作者#xff1a;Victor Gao 本文在公众号【ClickHouseInc】首发 chDB 是一个嵌入式 OLAP SQL 引擎#xff0c;它将 ClickHouse 强大的分析能力封装为一个 Python 模块#xff0c;使开发者无需安装或运…本文字数16645估计阅读时间42 分钟作者Victor Gao本文在公众号【ClickHouseInc】首发chDB 是一个嵌入式 OLAP SQL 引擎它将 ClickHouse 强大的分析能力封装为一个 Python 模块使开发者无需安装或运行 ClickHouse 服务器即可在 Python 环境中享受高性能的数据分析体验。近期我们完成了 chDB 的一次重大内核升级PR #383将内核版本从 v25.5 升级到 v25.8.2.29。此次升级不仅带来了新功能与性能优化也暴露出一系列技术挑战。本文将系统介绍此次升级过程中遇到的技术问题与应对方案。1. chDB 的架构与代码结构1.1 整体架构chDB 的核心理念是在 Python 进程中嵌入 ClickHouse 引擎构建真正的进程内查询引擎。其架构大致分为以下几个层次与传统的客户端/服务器模式不同chDB 并不依赖独立的服务进程零拷贝数据传输通过 Python 的 memoryview 与 C 的 WriteBufferFromVector 实现零拷贝的数据传递嵌入式引擎ClickHouse 直接在 Python 进程中运行避免了进程间通信的开销多格式原生支持内建支持 60 多种数据格式如 Parquet、CSV、JSON、Arrow、ORC 等1.2 代码结构chDB 的代码架构在继承 ClickHouse 的基础上进行了大量定制化改造chdb/ ├── chdb/ # Python package │ ├── __init__.py # Main query interface │ ├── session/ # Session management │ ├── dbapi/ # DB-API 2.0 implementation │ └── udf/ # User-defined functions ├── programs/local/ # Local query engine │ ├── LocalChdb.cpp # chDB main entry │ ├── PythonSource.cpp # Python Table Engine │ └── PandasDataFrame.cpp # Pandas integration ├── src/ # ClickHouse core source └── contrib/ # Third-party dependencies核心查询流程如下Python 端调用 chdb.query(sql, format)通过 pybind11 将请求传入 C 层ClickHouse 引擎解析并执行 SQL查询结果以 memoryview 方式零拷贝返回给 Python2. 为什么采用两个动态链接库chDB 采用了一个较为特殊的架构设计使用两个动态链接库 _chdb.abi3.so 和 libpybind11nonlimitedapi_chdb_3.8.so。虽然看起来略显复杂但正是这个设计解决了多版本 Python 兼容性的关键问题。2.1 多 Python 版本兼容性的挑战目前 Python 生态活跃着多个版本3.8、3.9、3.10、3.11、3.12、3.13每个版本的 C API 都存在细微差异。传统的 Python C 扩展通常需要为每个版本分别编译生成对应的二进制文件。但对于像 chDB 这样体积庞大的项目而言即便经过裁剪与压缩单个 .so 文件仍超过 120MB这种方式是难以接受的。2.2 动态库分层架构chDB 采用了一种模块化分层设计chdb.abi3.so稳定 ABI 层基于 Python 的 Limited API 实现只依赖 Python 的稳定 ABI 接口可在多个 Python 版本间通用复用文件大小约为 120MB包含完整的 ClickHouse 引擎主要内容包括ClickHouse 核心、查询执行引擎、格式解析器等libpybind11nonlimitedapi_chdb_3.x.so版本适配层使用完整的 Python C API非 Limited API需针对每个 Python 版本单独编译文件大小约 10–20MB包含 pybind11 的绑定逻辑以及 Python 对象的转换处理我们通过构建脚本 build_pybind11.sh 为每个目标 Python 版本构建 libpybind11 库# Build independent binding libraries for Python 3.8-3.13 for version in 3.8 3.9 3.10 3.11 3.12 3.13; do cmake -DPYBIND11_NONLIMITEDAPI_PYTHON_HEADERS_VERSION${version} .. ninja pybind11nonlimitedapi_chdb_${version} done这种架构的优点在于存储更高效核心引擎120MB只需下载一次不同 Python 版本仅需额外 ~15MB 的绑定库构建更高效核心引擎编译耗时较长数小时但绑定层构建速度快几分钟内完成维护更简便核心逻辑统一仅需根据不同 Python 版本适配绑定层即可2.3 jemalloc 与内存管理的挑战不过这样的设计也引入了新的技术难题。在《The birth of chDB》https://auxten.com/the-birth-of-chdb/一文中我们曾提到chDB 在早期开发过程中遇到了严重的内存管理问题。2.3.1 问题的发现与根源问题场景如下在将 Python 扩展模块集成进 chDB 时我们经常遇到段错误segmentation fault。通过分析 core dump 文件我们发现崩溃多发生在内存释放的阶段Program received signal SIGSEGV, Segmentation fault. 0x00007ffff7a9e123 in je_free () from /path/to/_chdb.abi3.so (gdb) bt #0 je_free (ptr0x7fffe8001000) #1 __wrap_free (ptr0x7fffe8001000) at AllocationInterceptors.cpp:451 #2 0x00007ffff7e8a456 in PyMem_Free (ptr0x7fffe8001000) #3 0x00007ffff7dab234 in list_dealloc ()深度根因分析这是一个典型的跨模块内存管理问题根本原因在于C/C 的内存管理遵循“谁分配谁释放且必须由相同分配器”这一原则。如果混用了不同的内存分配器可能导致以下问题元数据被破坏比如 jemalloc 在释放内存前尝试读取其元数据但如果该内存是由 glibc 分配的就可能读取到无效或随机的数据堆结构遭破坏各分配器的堆管理机制截然不同交叉释放会破坏彼此的数据结构出现未定义行为轻则立即崩溃重则内存数据被悄然破坏引发后续各种异常错误常见的触发场景包括// Scenario 1: Objects returned from Python C API PyObject* obj PyList_New(10); // Python uses malloc // ... used in C delete obj; // chDBs operator delete uses je_free → crash! // Scenario 2: Memory allocated by glibc functions char* cwd getcwd(NULL, 0); // glibc internally uses malloc // ... free(cwd); // wrapped to je_free → crash! // Scenario 3: Third-party libraries (e.g., libhdfs) hdfsFile file hdfsOpenFile(...); // libhdfs uses system malloc hdfsCloseFile(fs, file); // internally calls free, wrapped → crash!2.3.2 传统方案的局限性方案一完全禁用 jemalloc-DENABLE_JEMALLOC0❌ 会造成性能下降。ClickHouse 在 jemalloc 上做了大量优化禁用后整体性能下降达 20%–30%。方案二通过 LD_PRELOAD 强制使用 jemallocLD_PRELOAD/usr/lib/libjemalloc.so python chdb_example.py通过设置 LD_PRELOAD 变量让整个进程包括 Python 解释器强制使用 jemalloc 分配器。❌ 带来分发困难。用户必须在特定环境中配置运行使用门槛较高。2.3.3 chDB 的解决方案为兼顾性能与兼容性chDB 采用了运行时检测内存来源 智能路由的机制。核心思路是利用链接器的 --wrap 机制在编译链接阶段拦截所有的内存分配与释放调用并在运行时判断其来源。技术基础wrap 拦截机制链接器的 --wrap 选项允许在链接时重定向符号调用# Add wrap parameters at link time -Wl,-wrap,malloc -Wl,-wrap,free -Wl,-wrap,calloc -Wl,-wrap,realloc # ... other memory allocation functions其工作原理如下Application code calls: free(ptr) ↓ Linker redirects: __wrap_free(ptr) ← Our implementation ↓ Callback when needed: __real_free(ptr) ← Original glibc implementation通过该机制所有对 free() 的调用会被重定向至我们自定义的 __wrap_free() 函数中。在这个函数内我们可以添加内存归属判断逻辑并在必要时调用原始的 __real_free() 来完成释放。关键前提禁用 jemalloc 的符号重命名要让该方案生效chDB 需要区分 jemalloc 的 je_free() 与 glibc 的 free()。因此必须禁用 ClickHouse 默认启用的 jemalloc 符号重命名功能确保 jemalloc 的符号名得以保留# ClickHouse default config: Rename jemalloc symbols --with-jemalloc-prefixje_ # Effect: malloc → je_malloc, free → je_free # chDB config: Disable renaming -DJEMALLOC_PREFIX # Effect: Preserve je_malloc, je_free and other original symbol names这样我们就可以实现以下行为在 __wrap_free() 中判断当前内存块的分配来源若来自 jemalloc则调用 je_free(ptr)若来自 glibc则调用 __real_free(ptr)即系统默认的 free核心机制分配器指纹识别我们使用 jemalloc 提供的 mallctl API动态查询内存块的归属分配器inline bool isJemallocMemory(void * ptr) { // Query which arena this memory block belongs to int arena_ind; size_t sz sizeof(arena_ind); int ret je_mallctl(arenas.lookup, arena_ind, sz, // Output: arena index ptr, sizeof(ptr)); // Input: memory pointer // arena_ind 0: special value, indicates memory doesnt belong to jemalloc // arena_ind 0: memory belongs to a jemalloc arena return ret 0 arena_ind ! 0; }智能释放函数如下所示inline ALWAYS_INLINE bool tryFreeNonJemallocMemory(void * ptr) { if (unlikely(ptr nullptr)) return true; // Check memory source int arena_ind; size_t sz sizeof(arena_ind); int ret je_mallctl(arenas.lookup, arena_ind, sz, ptr, sizeof(ptr)); if (ret 0 arena_ind 0) { // This memory doesnt belong to jemalloc, use system free __real_free(ptr); return true; // Handled, no need to continue } // This memory belongs to jemalloc, or query failed (conservative handling) return false; // Continue with jemalloc release process } extern C void __wrap_free(void * ptr) { if (tryFreeNonJemallocMemory(ptr)) return; // Use jemalloc to release AllocationTrace trace; size_t actual_size Memory::untrackMemory(ptr, trace); trace.onFree(ptr, actual_size); je_free(ptr); }2.3.4 向 jemalloc 社区贡献代码在实现上述方案的过程中我们注意到 jemalloc 的 arenas.lookup 接口存在一个问题当接收到并非由 jemalloc 分配的内存指针时内存检测机制会导致程序崩溃。这并不是一个 bug而是 jemalloc 的设计预期——它默认所有传入指针都是合法的堆内存指针。但对于 chDB 的混合分配场景我们需要 arenas.lookup 能够安全处理任意类型的指针。为此Auxten Wang 向 jemalloc 提交了一个补丁增强了边界判断逻辑。// Before: Assumes pointer is valid, directly accesses metadata → invalid pointer crashes // After: First checks pointer validity, safely returns result if (ptr NULL || !isValidPointer(ptr)) { return EINVAL; // Return error code instead of crashing }这一改动已经被 jemalloc 官方接受并合并进主干分支Auxten Wang 也因此成为 jemalloc 的正式贡献者。3. 内核升级带来的 wrap 机制变更与适配3.1 ClickHouse 新版本引入 wrap 机制引发冲突在升级 ClickHouse 内核到 v25.8.2.29 版本后我们遇到了一个新的问题ClickHouse 在该版本中也引入了 wrap 机制用于拦截内存分配与释放操作以实现更精细的内存追踪。这与 chDB 现有的 wrap 实现方式产生了直接冲突chDBs wrap (introduced in Section 2): free(ptr) → __wrap_free() (chDB implementation) → Check memory source (je_mallctl) → Route to je_free or __real_free ClickHouse new versions wrap: free(ptr) → __wrap_free() (ClickHouse implementation) → Update MemoryTracker statistics → Call je_free Conflict: Two __wrap_free implementations cannot coexist!冲突的根本原因在于链接器的 --wrap 机制对每个符号只能使用一个 wrap 实现。当 ClickHouse 内核和 chDB 的绑定层都尝试定义 __wrap_free 等符号时链接器将无法确定该采用哪一方的实现。3.2 适配方案融合两套 wrap 实现为了解决冲突我们需要将 ClickHouse 的内存追踪逻辑与 chDB 的内存分配来源检测逻辑整合为一个统一的 wrap 实现具体思路如下保留 chDB 原有的 wrap 实现作为最终的拦截入口在 chDB 的 wrap 函数中调用 ClickHouse 提供的 MemoryTracker 实现内存追踪按照实际场景决定是否进行内存分配器来源检测最终实现效果如下融合后的 __wrap_free 函数结构如下所示extern C void __wrap_free(void * ptr) // NOLINT { #if USE_JEMALLOC // chDB logic: Check if its non-jemalloc memory if (tryFreeNonJemallocMemory(ptr)) return; // Already handled glibc-allocated memory via __real_free #endif // ClickHouse logic: Update MemoryTracker statistics size_t actual_size Memory::untrackMemory(ptr, trace); #if USE_JEMALLOC // Use jemalloc to release je_free(ptr); #else // If jemalloc not enabled, use system free __real_free(ptr); #endif }3.3 双库架构下的 operator delete 内存释放问题在实现第 2 节中提到的双动态链接库架构_chdb.abi3.so 与 libpybind11nonlimitedapi_chdb_xx.so时我们发现 operator delete 同样存在跨模块内存释放的问题与 free 的问题如出一辙。问题分析libpybind11nonlimitedapi_chdb_3.8.so: - Links to glibc at compile time - Calls malloc → Bound to glibc malloc (compile-time symbol binding) - Calls delete during object destruction _chdb.abi3.so: - Contains jemalloc and operator delete implementation - libpybind11*.so loads _chdb.abi3.so at runtime - delete call → Resolves to definition in _chdb.abi3.so at runtime问题性质虽然系统启用了 jemalloc但 libpybind11nonlimitedapi_chdb_xx.so 中的 malloc 在编译时就已绑定到 glibc 的分配符号。而在运行时delete 操作实际调用的是 _chdb.abi3.so 中的 delete 实现。由于两者使用了不同的分配器导致释放时发生不匹配的问题存在严重的内存安全隐患。In libpybind11*.so: char* obj malloc(100); // malloc() → glibc malloc (compile-time binding) delete obj; // operator delete → _chdb.abi3.so implementation (runtime binding) // Tries to use jemalloc to free glibc-allocated memory → crash!解决方案在 operator delete 中增加内存来源检测与处理 free 函数类似我们在 operator delete 实现中加入了内存来源检测逻辑void operator delete(void * ptr) noexcept { #if USE_JEMALLOC // Detect memory source, handle non-jemalloc memory early if (tryFreeNonJemallocMemory(ptr)) return; #endif // ClickHouse memory tracking Memory::untrackMemory(ptr, trace); // Actual release (jemalloc memory) Memory::deleteImpl(ptr); }该方案确保了在双库架构下的内存释放行为正确、安全如果内存是由 libpybind11.so 使用 glibc malloc 分配的通过 tryFreeNonJemallocMemoryConditional 检测后使用 glibc 的 free 正确释放如果是由 _chdb.abi3.so 使用 jemalloc 分配的则走正常的 MemoryTracker 与 jemalloc 的释放路径4. ClickBench Q29 查询性能问题与优化4.1 问题发现完成内核升级后我们注意到在 ClickBench 基准测试中Q29 查询性能出现严重下降。该查询涉及大量正则表达式匹配与替换操作。SELECT REGEXP_REPLACE(Referer, ^https?://(?:www\\\\.)?([^/])/.*$, \\\\1) AS k, AVG(length(Referer)) AS l, COUNT(*) AS c, MIN(Referer) FROM clickbench.hits WHERE Referer GROUP BY k HAVING COUNT(*) 100000 ORDER BY l DESC LIMIT 25;查询特征每一行都包含正则匹配和替换逻辑查询过程中会频繁创建和销毁字符串对象字符串操作密集触发内存分配与释放行为性能指标升级后的初始运行时间为约 300 秒经优化后降至约 4.9 秒性能提升达 61 倍4.2 根本原因分析通过性能分析工具与源码阅读我们发现性能瓶颈主要来源于 jemalloc 的锁竞争问题。在升级后的实现中每一次 delete 操作都会触发 tryFreeNonJemallocMemory()以判断该内存块是否由 jemalloc 分配。这种频繁的检测操作最终导致了大量线程在 jemalloc 内部锁上发生竞争从而拖慢了整体执行效率。// Pre-optimization code inline bool tryFreeNonJemallocMemory(void * ptr) { if (unlikely(ptr nullptr)) return true; // Key bottleneck: je_mallctl lock contention int arena_ind je_mallctl(arenas.lookup, nullptr, nullptr, ptr, sizeof(ptr)); if (unlikely(arena_ind ! 0)) { __real_free(ptr); return true; } return false; }瓶颈分析je_mallctl 锁竞争问题通过阅读 jemalloc 源码位于 contrib/jemalloc/src/jemalloc.c我们发现性能瓶颈出现在 check_entry_exit_locking() 函数该函数负责执行内存锁的获取与检查。int je_mallctl(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen) { ... check_entry_exit_locking(tsd_tsdn(tsd)); // Lock contention point! ... return ret; }在 Q29 的查询场景中该机制成为了严重的性能阻塞点主要原因如下高频调用正则表达式的匹配操作会大量生成临时字符串对象每次 delete 都触发检测每一次内存释放操作都会调用 je_mallctl(arenas.lookup, ...)多线程下出现严重锁竞争并行执行时大量线程集中调用 check_entry_exit_locking形成系统级瓶颈性能分析结果使用 perf 工具进行性能采样后发现约 99.8% 的 CPU 时间都耗费在该锁相关函数的调用中。operator delete └─ tryFreeNonJemallocMemory └─ je_mallctl └─ check_entry_exit_locking ← Hotspot! └─ pthread_mutex_lock4.3 优化方案引入 disable_memory_check 机制优化的核心思路是避免在无必要的场景下执行内存分配器检测。结合 chDB 的实际运行模式我们发现在 ClickHouse 引擎内部所有内存均由 jemalloc 管理因此无需执行额外检测在 Python 与 C 的交互接口处可能涉及 Python 内存分配器因此必须开启内存检测逻辑以确保安全基于以上判断我们设计并引入了一个名为 disable_memory_check 的运行时控制机制namespace Memory { thread_local bool disable_memory_check{false}; // Disable checking by default inside engine } // Version with conditional checking inline ALWAYS_INLINE bool tryFreeNonJemallocMemoryConditional(void * ptr) { if (unlikely(ptr nullptr)) return true; // Fast path: Skip checking directly inside engine if (likely(Memory::disable_memory_check)) return false; // Continue normal jemalloc release process ... } // Updated operator delete void operator delete(void * ptr) noexcept { #if USE_JEMALLOC if (tryFreeNonJemallocMemoryConditional(ptr)) // Use conditional version return; #endif ... }适用场景仅在跨越 Python 边界的关键位置启用内存检测逻辑// RAII helper class struct MemoryCheckScope { MemoryCheckScope() { Memory::disable_memory_check false; // Enable checking } ~MemoryCheckScope() { Memory::disable_memory_check true; // Restore disabled } }; // Use at Python interaction points void convertPandasDataFrame(...) { MemoryCheckScope scope; // Enter Python boundary, enable checking // Call Python C API, may use Pythons memory allocator PyObject* obj PyList_GetItem(...); // ... // Leave scope, automatically restore disabled state }具体应用范围包括PandasDataFrame.cpp用于 Pandas 列数据的转换PandasAnalyzer.cpp执行类型推断操作如 isinstance 判断PythonSource.cpp遍历 Python 对象并传入 ClickHouse 引擎PythonConversion.cpp将 Python 类型转换为 C 类型PythonImportCache.cpp导入模块时涉及 Python 内部分配的管理优化效果以 Q29 查询为例引擎内部的字符串操作在数百万次 delete 操作中均走快速路径仅需执行极少数 CPU 指令即可完成内存释放与 Python 交互部分仅在如 DataFrame 导入等个别流程中启用检测实际检测次数仅为数百次最终实现显著减少了锁竞争带来的性能开销。// Before optimization: Every delete calls je_mallctl if (tryFreeNonJemallocMemory(ptr)) // ~500 CPU cycles return; // After optimization: Most deletes directly skip if (likely(Memory::disable_memory_check)) // ~2 CPU cycles return false;该优化将每次内存释放的处理开销从约 500 个 CPU 时钟周期显著降低至仅约 2 个周期在频繁触发内存释放的场景下效果尤为明显。4.4 性能对比与影响分析性能提升效果如下查询类型新版本优化前新版本优化后性能提升倍数Q29~300s~4.9s61x ↑主要优化成果Q29 查询性能显著提升从原先的 300 秒缩短至 4.9 秒整体提升 61 倍有效解决锁竞争问题通过引入 disable_memory_check 机制大幅减少了约 98% 的锁等待开销广泛适用性该优化策略对于所有涉及高频内存分配与释放的场景都有显著的加速效果CPU 使用情况对比After optimization (Q29): 89.2% Regular expression engine ← As expected 7.1% String operations and memory allocation 2.3% Aggregation and sorting 1.4% Other在优化前CPU 时间主要耗费在 jemalloc 的内存管理逻辑中而在优化后系统热点已经回归到预期的核心计算逻辑说明整体性能瓶颈已被移除。影响范围分析disable_memory_check 是一种精细化、定点式的优化方案具备以下优点✅ 安全性高该机制仅作用于 ClickHouse 引擎内部的内存逻辑对 Python 边界区域不产生任何影响确保系统整体稳定性✅ 完全兼容不会更改现有 API 行为用户无需任何额外配置即可受益优化过程对外完全透明✅ 易于维护实现上采用 C 中经典的 RAII资源获取即初始化模式自动控制状态启停降低维护成本和出错概率✅ 具备可扩展性如需在其他模块中关闭内存检测只需添加对应的 MemoryCheckScope 即可具备良好的可扩展框架此外这一优化方案也为后续性能优化工作提供了方向启示在确保系统正确性的基础上精准识别关键瓶颈并设定高效执行路径将带来显著的性能提升。5. chDB 与 clickhouse-local 在 Parquet 查询上的性能对比5.1 问题背景Issue #115有用户反馈在查询包含 10 亿行数据的 Parquet 文件时chDB 的运行速度明显慢于 clickhouse-local。# ClickHouse Local $ time clickhouse local -q SELECT COUNT(*) FROM file(data.parquet, Parquet) # 1.734 seconds # chDB $ time python -c import chdb; chdb.query(SELECT COUNT(*) FROM file(\\\\data.parquet\\\\, Parquet)) # 2.203 seconds5.2 深度分析实验一执行时间分解分析import chdb import time t0 time.time() import chdb # Load library t1 time.time() result chdb.query(SELECT COUNT(*) FROM file(data.parquet, Parquet)) t2 time.time() print(fImport time: {t1-t0:.2f}s) # 0.58s print(fQuery time: {t2-t1:.2f}s) # 1.62s print(fTotal time: {t2-t0:.2f}s) # 2.20s print(fQuery elapsed: {result.elapsed():.2f}s) # 1.60s测试结果表明实际的 SQL 查询执行时间为 1.6 秒与 clickhouse-local 相当真正导致总耗时增加的是 Python 扩展模块加载所产生的额外开销约为 0.58 秒占总耗时 26%实验二加载延迟的成因$ ldd _chdb.abi3.so linux-vdso.so.1 (0x00007fff) libpybind11nonlimitedapi_chdb_3.8.so ./libpybind11... libpthread.so.0 /lib/x86_64-linux-gnu/libpthread.so.0 libdl.so.2 /lib/x86_64-linux-gnu/libdl.so.2 libm.so.6 /lib/x86_64-linux-gnu/libm.so.6 libgcc_s.so.1 /lib/x86_64-linux-gnu/libgcc_s.so.1 libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 $ ls -lh _chdb.abi3.so -rwxr-xr-x 1 user user 642M _chdb.abi3.so $ nm -D _chdb.abi3.so | wc -l 540000 # 540,000 exported symbols进一步分析发现加载时间长的根本原因包括动态库体积大chDB 的核心库文件为一个 640MB 的 .so 文件加载时需从磁盘完整读入符号解析耗时高由于该库在运行时需动态解析约 54 万个符号导致初始化时间较长相对比较clickhouse-local 是一个静态链接的可执行程序启动开销仅为约 48 毫秒5.3 优化方向与措施已实施的优化措施减少符号体积通过执行 strip --remove-section.comment --remove-section.note 命令裁剪符号表中无关调试信息缩减动态链接开销未来可探索的优化方向二进制拆分将如 HDFS、Kafka 等不常用的功能模块独立成可选插件减少主库体积启用持久会话对于长时间运行的程序可通过 chdb.session.Session() 机制来复用上下文状态避免每次查询都重新加载库文件# Create session once, query multiple times sess chdb.session.Session() # First query bears startup overhead result1 sess.query(SELECT * FROM file(data1.parquet, Parquet)) # 2.2s # Subsequent queries have no startup overhead result2 sess.query(SELECT * FROM file(data2.parquet, Parquet)) # 1.6s result3 sess.query(SELECT * FROM file(data3.parquet, Parquet)) # 1.5s5.4 性能总结Scenarioclickhouse-localchDB (Single)chDB (Session)Startup overhead0.048s0.580s0.580s (first only)Single query (1B rows)1.6s2.2s1.6s10 queries16.5s28.0s16.6s综合来看对于单次查询任务chDB 启动开销较大整体执行时间比 clickhouse-local 慢约 37%对于批量或交互式查询使用持久会话后chDB 的查询效率与 clickhouse-local 持平使用建议chDB 更适合需要频繁发起多次查询的交互式分析场景6. 内核升级后的整体性能变化6.1 与 chDB 3.6 的对比测试本次内核升级完成后我们在 ClickBench 基准测试集上对比评估了新版本基于 ClickHouse v25.8.2.29与旧版本 chDB 3.6基于 ClickHouse v24.4在整体性能上的差异。测试环境如下数据来源ClickBench hits 表包含约 1 亿条记录硬件配置AWS c6i.metal 裸金属实例对比对象chDB 3.6 与优化后的 chDB 新版本性能对比测试结果显示整体性能提升显著多个典型查询任务的执行时间缩短了 2 到 6 倍不等性能提升的主要原因升级后的 ClickHouse 内核中查询优化器能力更强生成的执行计划更加高效新版本引入了更高效的向量化执行机制极大提升了执行性能具体查询的性能提升情况如下QuerychDB New VersionchDB 3.6ImprovementQ10.027s0.125s4.6x ↑Q20.024s0.163s6.8x ↑Q30.036s0.120s3.3x ↑Q60.017s0.118s6.9x ↑Q70.025s0.150s6.0x ↑Q100.112s0.333s3.0x ↑Q110.097s0.367s3.8x ↑Q150.165s0.380s2.3x ↑Q294.9s300s61x ↑性能提升分析简单聚合查询如 Q1、Q2、Q6性能提升 4 至 7 倍主要得益于新版 ClickHouse 更高效的向量化执行引擎带过滤条件的聚合查询如 Q3、Q7提升 3 至 6 倍受益于查询优化器在逻辑下推与谓词重写方面的增强复杂聚合计算如 Q10、Q11、Q15提升 2 至 4 倍说明内存管理优化开始发挥实质性作用正则表达式密集型查询Q29性能提升高达 61 倍根本原因在于成功消除了 jemalloc 的 je_mallctl 锁竞争问题6.2 升级收益总结此次将 ClickHouse 内核从 v24.4 升级至 v25.8.2.29为 chDB 带来了显著的性能与功能提升性能方面多个典型查询的执行效率提升了 2 至 7 倍其中 Q29 的极端场景实现了 61 倍的提升整体系统更加稳定高效功能方面全面支持 ClickHouse v25.8 所新增的功能特性为后续拓展更多高级场景提供基础如改进的索引机制、更强的数据格式兼容性等参考资料PR #383: Feat(upgrade): update ch core to v25.8.2.29https://github.com/chdb-io/chdb/pull/383The birth of chDB https://auxten.com/the-birth-of-chdb/Issue #115: Parquet 文件读取性能问题分析 https://github.com/chdb-io/chdb/issues/115ClickHouse 官方文档https://clickhouse.com/docsPython Limited APIPEP 384https://peps.python.org/pep-0384/jemalloc 官方文档https://jemalloc.net/征稿启示面向社区长期正文文章内容包括但不限于关于 ClickHouse 的技术研究、项目实践和创新做法等。建议行文风格干货输出图文并茂。质量合格的文章将会发布在本公众号优秀者也有机会推荐到 ClickHouse 官网。请将文章稿件的 WORD 版本发邮件至Tracy.Wangclickhouse.com

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询