乐趣区

关于数据库:如何高效解决-C内存问题Apache-Doris-实践之路|技术解析

导读:Apache Doris 应用 C++ 语言实现了执行引擎,C++ 开发过程中,影响开发效率的一个重要因素是指针的应用,包含非法拜访、泄露、强制类型转换等。本文将会通过对 Sanitizer 和 Core Dump 剖析工具的介绍来为大家分享:如何疾速定位 Apache Doris 中的 C++ 问题,帮忙开发者晋升开发效率并把握更高效的开发技巧。

作者|Apache Doris Committer 杨勇强

Apache Doris 是一款高性能 MPP 剖析型数据库,出于性能的思考,Apache Doris 应用了 C++ 语言实现了执行引擎。在 C++ 开发过程中,影响开发效率的一个重要因素是指针的应用,包含非法拜访、泄露、强制类型转换等。Google Sanitizer 是由 Google 设计的用于动静代码剖析的工具,在 Apache Doris 开发过程中遭逢指针应用引起的内存问题时,正是因为有了 Sanitizer,使得问题解决效率能够失去数量级的晋升。除此以外,当呈现一些内存越界或非法拜访的状况导致 BE 过程 Crash 时,Core Dump 文件是十分无效的定位和复现问题的路径,因而一款高效剖析 CoreDump 的工具也会进一步帮忙更加快捷定位问题。

本文将会通过对 Sanitizer 和 Core Dump 剖析工具的介绍来为大家分享:如何疾速定位 Apache Doris 中的 C++ 问题,帮忙开发者晋升开发效率并把握更高效的开发技巧。

Sanitizer 介绍

定位 C++ 程序内存问题罕用的工具有两个,Valgrind 和 Sanitizer。

二者的比照能够参考:https://developers.redhat.com/blog/2021/05/05/memory-error-checking-in-c-and-c-comparing-sanitizers-and-valgrind

其中 Valgrind 通过运行时软件翻译二进制指令的执行获取相干的信息,所以 Valgrind 会十分大幅度的升高程序性能,这就导致在一些大型项目比方 Apache Doris 应用 Valgrind 定位内存问题效率会很低。

而 Sanitizer 则是通过编译时插入代码来捕捉相干的信息,性能降落幅度比 Valgrind 小很多,使得可能在单测以及其它测试环境默认应用 Saintizer。

Sanitizer 的算法能够参考:https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm

在 Apache Doris 中,咱们通常应用 Sanirizer 来定位内存问题。LLVM 以及 GNU C++ 有多个 Sanitizer:

  • AddressSanitizer(ASan)能够发现内存谬误问题,比方 use after free,heap buffer overflow,stack buffer overflow,global buffer overflow,use after return,use after scope,memory leak,super large memory allocation;
  • AddressSanitizerLeakSanitizer(LSan)能够发现内存泄露;
  • MemorySanitizer(MSan)能够发现未初始化的内存应用;
  • UndefinedBehaviorSanitizer(UBSan)能够发现未定义的行为,比方越界数组拜访、数值溢出等;
  • ThreadSanitizer(TSan)能够发现线程的竞争行为;

其中 AddressSanitizer, AddressSanitizerLeakSanitizer 以及 UndefinedBehaviorSanitizer 对于解决指针相干的问题最为无效。

Sanitizer 岂但可能发现错误,而且可能给出谬误源头以及代码地位,这就使得问题的解决效率很高,通过一些例子来阐明 Sanitizer 的易用水平。

能够参考此处应用 Sanitizer:https://github.com/apache/doris/blob/master/be/CMakeLists.txt

Sanitizer 和 Core Dump 配合定位问题十分高效,默认 Sanitizer 不生成 Core Dump 文件,能够应用如下环境变量生成 Core Dump 文件,倡议默认关上。

能够参考:https://github.com/apache/doris/blob/master/bin/start_be.sh

export ASAN_OPTIONS=symbolize=1:abort_on_error=1:disable_coredump=0:unmap_shadow_on_exit=1

应用如下环境变量让 UBSan 生成代码栈,默认不生成。

export UBSAN_OPTIONS=print_stacktrace=1

有时候须要显示指定 Symbolizer 二进制的地位,这样 Sanitizer 就可能间接生成可读的代码栈。

export ASAN_SYMBOLIZER_PATH=your path of llvm-symbolizer

Sanitizer 应用举例

Use after free

User after free 是指拜访开释的内存,针对 use after free 谬误,AddressSanitizer 可能报出应用开释地址的代码栈,地址调配的代码栈,地址开释的代码栈。比方:https://github.com/apache/doris/issues/9525 中,应用开释地址的代码栈如下:

82849==ERROR: AddressSanitizer: heap-use-after-free on address 0x60300074c420 at pc 0x56510f61a4f0 bp 0x7f48079d89a0 sp 0x7f48079d8990
READ of size 1 at 0x60300074c420 thread T94 (MemTableFlushTh)
    #0 0x56510f61a4ef in doris::faststring::append(void const*, unsigned long) /mnt/ssd01/tjp/incubator-doris/be/src/util/faststring.h:120
// 更具体的代码栈请返回 https://github.com/apache/doris/issues/9525 查看

此地址首次调配的代码栈如下:

previously allocated by thread T94 (MemTableFlushTh) here:
    #0 0x56510e9b74b7 in __interceptor_malloc (/mnt/ssd01/tjp/regression_test/be/lib/palo_be+0x536a4b7)
    #1 0x56510ee77745 in Allocator<false, false>::alloc_no_track(unsigned long, unsigned long) /mnt/ssd01/tjp/incubator-doris/be/src/vec/common/allocator.h:223
    #2 0x56510ee68520 in Allocator<false, false>::alloc(unsigned long, unsigned long) /mnt/ssd01/tjp/incubator-doris/be/src/vec/common/allocator.h:104

地址开释的代码栈如下:

0x60300074c420 is located 16 bytes inside of 32-byte region [0x60300074c410,0x60300074c430)
freed by thread T94 (MemTableFlushTh) here:
    #0 0x56510e9b7868 in realloc (/mnt/ssd01/tjp/regression_test/be/lib/palo_be+0x536a868)
    #1 0x56510ee8b913 in Allocator<false, false>::realloc(void*, unsigned long, unsigned long, unsigned long) /mnt/ssd01/tjp/incubator-doris/be/src/vec/common/allocator.h:125
    #2 0x56510ee814bb in void doris::vectorized::PODArrayBase<1ul, 4096ul, Allocator<false, false>, 15ul, 16ul>::realloc<>(unsigned long) /mnt/ssd01/tjp/incubator-doris/be/src/vec/common/pod_array.h:147

有了具体的非法拜访地址代码栈、调配代码栈、开释代码栈,问题定位就会非常容易。

阐明:限于文章篇幅,示例中的栈展现不全,残缺代码栈能够返回对应 Issue 中进行查看。

heap buffer overflow

AddressSanitizer 可能报出 heap buffer overflow 的代码栈。

比方 https://github.com/apache/dor… 里的,联合运行时生成的 Core Dump 文件就能够疾速定位问题。

==3930==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60c000000878 at pc 0x000000ae00ce bp 0x7ffeb16aa660 sp 0x7ffeb16aa658
READ of size 8 at 0x60c000000878 thread T0
    #0 0xae00cd in doris::StringFunctions::substring(doris_udf::FunctionContext*, doris_udf::StringVal const&, doris_udf::IntVal const&, doris_udf::IntVal const&) ../src/exprs/string_functions.cpp:98

memory leak

AddressSanitizer 可能报出哪里调配的内存没有被开释,就能够疾速的剖析出泄露起因。

==1504733==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 688128 byte(s) in 168 object(s) allocated from:
#0 0x560d5db51aac in __interceptor_posix_memalign (/mnt/ssd01/doris-master/VEC_ASAN/be/lib/doris_be+0x9227aac)
#1 0x560d5fbb3813 in doris::CoreDataBlock::operator new(unsigned long) /home/zcp/repo_center/doris_master/be/src/util/core_local.cpp:35
#2 0x560d5fbb65ed in doris::CoreDataAllocatorImpl<8ul>::get_or_create(unsigned long) /home/zcp/repo_center/doris_master/be/src/util/core_local.cpp:58
#3 0x560d5e71a28d in doris::CoreLocalValue::CoreLocalValue(long)

https://github.com/apache/doris/issues/10926

https://github.com/apache/doris/pull/3326

异样调配

调配过大的内存 AddressSanitizer 会报出 OOM 谬误,依据栈以及 Core Dump 文件能够剖析出何处调配了过大内存。栈举例如下:

Fix PR 见:https://github.com/apache/doris/pull/10289

UBSan 可能高效发现强制类型转换的谬误,如下方 Issue 链接中形容,它可能准确的形容出强制类型转换带来谬误的代码,如果不能在第一现场发现这种谬误,后续因为指针谬误应用,会比拟难定位。

Issue:https://github.com/apache/doris/issues/9105

UndefinedBehaviorSanitizer 也比 AddressSanitizer 及其它的更容易发现死锁。

比方:https://github.com/apache/doris/issues/10309

程序保护内存 Pool 时 AddressSanitizer 的应用

AddressSanitizer 是编译器针对内存调配、开释、拜访 生成额定代码来实现内存问题剖析的,如果程序保护了本人的内存 Pool,AddressSanitizer 就不能发现 Pool 中内存非法拜访的问题。这种状况下须要做一些额定的工作来使得 AddressSanitizer 尽可能工作,次要是应用 ASAN\_POISON\_MEMORY\_REGION 和 ASAN\_UNPOISON\_MEMORY\_REGION 治理内存是否能够拜访,这种办法应用比拟难,因为 AddressSanitizer 外部有地址对齐等的解决。出于性能以及内存开释等起因,Apache Doris 也保护了内存调配 Pool,这种办法不能确保 AddressSanitizer 可能发现所有问题。

能够参考:https://github.com/apache/doris/pull/8148

当程序保护本人的内存池时,依照 https://github.com/apache/dorisw/pull/8148 中办法,use after free 谬误会变成 use after poison。然而 use after poison 不可能给出地址生效的栈(https://github.com/google/sanitizers/issues/191),从而导致问题的定位剖析依然很艰难。

因而倡议程序保护的内存 Pool 能够通过选项敞开,这样在测试环境就能够应用 AddressSanitizer 高效地定位内存问题。

Core dump 剖析工具

剖析 C++ 程序生成的 Core Dump 文件常常遇到的问题就是怎么打印出 STL 容器中的值以及 Boost 中容器的值,有如下三个工具能够高效的查看 STL 和 Boost 中容器的值。

STL-View

能够将此文件 https://github.com/dataroaring/tools/blob/main/gdb/dbinit\_stl\_views-1.03.txt 搁置到~/.gdbinit 中应用 STL-View。STL-View 输入十分敌对,反对 pvector,plist,plist\_member,pmap,pmap\_member,pset,pdequeue,pstack,pqueue,ppqueue,pbitset,pstring,pwstring。以 Apache Doris 中应用 pvector 为例,它可能输入 vector 中的所有元素。

(gdb) pvector block.data
elem[0]: $5 = {
  column = {
    <COW<doris::vectorized::IColumn>::intrusive_ptr<doris::vectorized::IColumn const>> = {t = 0x606000fdc820}, <No data fields>},
  type = {<std::__shared_ptr<doris::vectorized::IDataType const, (__gnu_cxx::_Lock_policy)2>> = {<std::__shared_ptr_access<doris::vectorized::IDataType const, (__gnu_cxx::_Lock_policy)2, false, false>> = {<No data fields>},
      members of std::__shared_ptr<doris::vectorized::IDataType const, (__gnu_cxx::_Lock_policy)2>:
      _M_ptr = 0x6030069e9780,
      _M_refcount = {_M_pi = 0x6030069e9770}
    }, <No data fields>},
  name = {
    static npos = 18446744073709551615,
    _M_dataplus = {
      <std::allocator<char>> = {<__gnu_cxx::new_allocator<char>> = {<No data fields>}, <No data fields>},
      members of std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Alloc_hider:
      _M_p = 0x61400006e068 "n_nationkey"
    },
    _M_string_length = 11,
    {
      _M_local_buf = "n_nationkey\000\276\276\276\276",
      _M_allocated_capacity = 7957695015158701934
    }
  }
}
elem[1]: $6 = {
  column = {
    <COW<doris::vectorized::IColumn>::intrusive_ptr<doris::vectorized::IColumn const>> = {t = 0x6080001ec220}, <No data fields>},
  type = {
  ...

Pretty-Printer

GCC 7.0 开始反对了 Pretty-Printer 打印 STL 容器,能够将以下代码搁置到~/.gdbinit 中使 Pretty-Printer 失效。

留神:/usr/share/gcc/python 须要更换为本机对应的地址。

python
import sys
sys.path.insert(0, '/usr/share/gcc/python')
from libstdcxx.v6.printers import register_libstdcxx_printers
register_libstdcxx_printers (None)
end

以 vector 为例,Pretty-Printer 可能打印出具体内容。

(gdb) p block.data
$1 = std::vector of length 7, capacity 8 = {{
    column = {
      <COW<doris::vectorized::IColumn>::intrusive_ptr<doris::vectorized::IColumn const>> = {t = 0x606000fdc820}, <No data fields>},
    type = std::shared_ptr<const doris::vectorized::IDataType> (use count 1, weak count 0) = {get() = 0x6030069e9780
    },
    name = "n_nationkey"
  }, {
    column = {
      <COW<doris::vectorized::IColumn>::intrusive_ptr<doris::vectorized::IColumn const>> = {t = 0x6080001ec220}, <No data fields>},
    type = std::shared_ptr<const doris::vectorized::IDataType> (use count 1, weak count 0) = {get() = 0x6030069e9750
    },
    name = "n_name"
  }, {
    column = {
      <COW<doris::vectorized::IColumn>::intrusive_ptr<doris::vectorized::IColumn const>> = {t = 0x606000fd52c0}, <No data fields>},
    type = std::shared_ptr<const doris::vectorized::IDataType> (use count 1, weak count 0) = {get() = 0x6030069e9720
    },
    name = "n_regionkey"
  }, {
    column = {
      <COW<doris::vectorized::IColumn>::intrusive_ptr<doris::vectorized::IColumn const>> = {t = 0x6030069e96b0}, <No data fields>},
    type = std::shared_ptr<const doris::vectorized::IDataType> (use count 1, weak count 0) = {get() = 0x604000a66160
    },
    name = "n_comment"

Boost Pretty Printer

因为 Apache Doris 应用 Boost 不多,因而不再举例。

能够参考:https://github.com/ruediger/Boost-Pretty-Printer

总结

有了 Sanitizer 可能在单测、性能、集成、压力测试环境及时发现问题,最重要的是大多数时候都能够给出程序出问题的关联现场,比方内存调配的调用栈,开释内存的调用栈,非法拜访内存的调用栈,配合 Core Dump 能够查看现场状态,解决 C++ 内存问题从猜想变成了有证据的现场剖析。

作者介绍:杨勇强,SelectDB 联结创始人兼产品 VP,同时也是 Apache Doris Committer。曾负责百度智能云存储部总架构师,主导构建了云存储技术产品体系,是 Linux 内核社区贡献者。

退出社区

最初,欢送更多的开源技术爱好者退出 Apache Doris 社区,携手成长,共建社区生态。

相干链接:

SelectDB 官方网站:

https://selectdb.com

Apache Doris 官方网站:

http://doris.apache.org

Apache Doris Github:

https://github.com/apache/doris

Apache Doris 开发者邮件组:

dev@doris.apache.org

退出移动版