图数据库 NebulaGraph 的内存管理实践之 Memory Tracker

这篇具有很好参考价值的文章主要介绍了图数据库 NebulaGraph 的内存管理实践之 Memory Tracker。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

数据库的内存管理是数据库内核设计中的重要模块,内存的可度量、可管控是数据库稳定性的重要保障。同样的,内存管理对图数据库 NebulaGraph 也至关重要。

图数据库的多度关联查询特性,往往使图数据库执行层对内存的需求量巨大。本文主要介绍 NebulaGraph v3.4 版本中引入的新特性 Memory Tracker,希望通过 Memory Tracker 模块的引入,实现细粒度的内存使用量管控,降低 graphd 和 storaged 发生被系统 OOM kill 的风险,提升 NebulaGraph 图数据库的内核稳定性。

注:为了同代码保持对应,本文部分用词直接使用了英文,e.g. reserve 内存 quota。

可用内存

在进行 Memory Tracker 的介绍之前,这里先介绍下相关的背景知识:可用内存。

进程可用内存

在这里,我们简单介绍下各个模式下,系统是如何判断可用内存的。

物理机模式

数据库内核会读取系统目录 /proc/meminfo,来确定当前环境的实际内存和剩余内存,Memory Tracker 将“实际物理内存”作为“进程可以使用的最大内存”;

容器/cgroup 模式

nebula-graphd.conf 文件中有一个配置项 FLAG_containerized 用来判断是否数据库跑在容器上。将 FLAG_containerized(默认为 false)设置为 true 之后,内核会读取相关 cgroup path 下的文件,确定当前进程可以使用多少内存;cgroup 有 v1、v2 两个版本,这里以 v2 为例;

FLAG 默认值 解释
FLAG_cgroup_v2_memory_max_path /sys/fs/cgroup/memory.max 通过读取路径确定最大内存使用量
FLAG_cgroup_v2_memory_current_path /sys/fs/cgroup/memory.current 通过读取路径确定当前内存使用量

举个例子,在单台机器上分别控制 graphd 和 storaged 的内存额度。你可以通过以下步骤:

step1:设置 FLAG_containerized=true

step2:创建 /sys/fs/cgroup/graphd//sys/fs/cgroup/storaged/,并配置各自目录下的 memory.max

step3:在 etc/nebula-graphd.confetc/nebula-storaged.conf 添加相关配置

--containerized=true
--cgroup_v2_controllers=/sys/fs/cgroup/graphd/cgroup.controllers
--cgroup_v2_memory_stat_path=/sys/fs/cgroup/graphd/memory.stat
--cgroup_v2_memory_max_path=/sys/fs/cgroup/graphd/memory.max
--cgroup_v2_memory_current_path=/sys/fs/cgroup/graphd/memory.current

Memory Tracker 可用内存

在获取“进程可用内存”以后,系统需要将其换算成 Memory Tracker 可 track 的内存,“进程可用内存”与“Memory Tracker 可用内存”有一个换算公式;

memtracker_limit = ( total - FLAGS_memory_tracker_untracked_reserved_memory_mb ) * FLAGS_memory_tracker_limit_ratio

FLAG 默认值 解释 支持动态改
memory_tracker_untracked_reserved_memory_mb 50 M Memory Tracker 会管理通过 new/delete 申请的内存,但进程除了通过此种方式申请内存外,还可能存在其他方式占用的内存;比如通过调用底层的 malloc/free 申请,这些内存通过此 flag 控制,在计算时会扣除此部分未被 track 的内存。 Yes
memory_tracker_limit_ratio 0.8 指定 Memory Tracker 可以使用的内存比例,在一些场景,我们可能需要调小来防止 OOM。 Yes

这里来详细展开说下 memory_tracker_limit_ratio 的使用:

  • 在混合部署环境中,存在多个 graphd 或 storaged 混合部署是需要调小。比如 graphd 只占用 50% 内存,则需在 nebula-graphd.conf 中将其手动改成 0.5;
  • 取值范围:memory_tracker_limit_ratio 除了 (0,1] 取值范围外,还额外定义了两个特殊值:
    • 2:通过数据库内核感知当前系统运行环境的可用内存,动态调整可用内存。由于此种方式非实时,有一定的概率会感知不精准;
    • 3:limit 将被设成一个极大值,起到关闭 Memory Tracker 的效果;

Memory Tracker 的设计与实现方案

下面,讲下 Memory Tracker 的设计与实现。整体的 Memory Tracker 设计,包含 Global new/delete operatorMemoryStatssystem mallocLimiter 等几个子模块。这个部分着重介绍下 Global new/delete operator 和 MemoryStats 模块。

Global new/delete operator

Memory Tracker 通过 overload 全局 new/delete operator,接管内存的申请和释放,从而做到在进行真正的内存分配之前,进行内存额度分配的管理。这个过程分解为两个步骤:

  • 第一步:通过 MemoryStats 进行内存申请的汇报;
  • 第二步:调用 jemalloc 发生真正的内存分配行为;

jemalloc:Memory Tracker 不改变底层的 malloc 机制,仍然使用 jemalloc 进行内存的申请和释放;

MemoryStats

全局的内存使用情况统计,通过 GlobalMemoryStats 和 ThreadMemoryStats 分别对全局内存和线程内部内存进行管理;

ThreadMemoryStats

thread_local 变量,执行引擎线程在各自的 ThreadMemoryStats 中维护线程的 MemoryStats,包括“内存 Reservation 信息”和“是否允许抛异常的 throwOnMemoryExceeded”;

  • Reservation

每个线程 reserve 了 1 MB 的内存 quota,从而避免频繁地向 GlobalMemoryStats 索要额度。不管是申请还是返还时,ThreadMemoryStats 都会以一个较大的内存块作为与全局交换的单位。

alloc:在本地 reserved 1 MB 内存用完了,才问全局要下一个 1 MB。通过此种方式来尽可能降低向全局 quota 申请内存的频率;

dealloc:返还的内存先加到线程的 reserved 中,当 reserve quota 超过 1 MB 时,还掉 1 MB,剩下的自己留着;

 // Memory stats for each thread.
 struct ThreadMemoryStats {
   ThreadMemoryStats();
   ~ThreadMemoryStats();
 
   // reserved bytes size in current thread
   int64_t reserved;
   bool throwOnMemoryExceeded{false};
 };
  • throwOnMemoryExceeded

线程在遇到超过内存额度时,是否 throw 异常。只有在设置 throwOnMemoryExceeded 为 true 时,才会 throw std::bad_alloc。需要关闭 throw std::bad_alloc 场景见 Catch std::bac_alloc 章节。

GlobalMemoryStats

全局内存额度,维护了 limit 和 used 变量。

  • limit:通过运行环境和配置信息,换算得到 Memory Tracker 可管理的最大内存。limit 同 Limiter 模块的作用,详细内存换算见上文“Memory Tracker 可用内存”章节;

  • used:原子变量,汇总所有线程汇报上来的已使用内存(包括线程 reserved 的部分)。如果 used + try_to_alloc > limit,且在 throwOnMemoryExceeded 为 true 时,则会抛异常std::bac_alloc

Catch std::bac_alloc

由于 Memory Tracker overload new/delete 会影响所有线程,包括三方线程。此时,throw bad_alloc 在一些第三方线程可能出现非预期行为。为了杜绝此类问题发生,我们采用在代码路径上主动开启内存检测,选择在算子、RPC 等模块主动开启内存检测;

算子的内存检测

在 graph/storage 的各个算子中,添加 try...catch (在当前线程进行计算/分配内存) 和 thenError (通过 folly::Executor 异步提交的计算任务),感知 Memory Tracker 抛出 std::bac_alloc。数据库再通过 Status 返回错误码,使查询失败;

在进行一些内存调试时,可通过打开 nebula-graphd.conf 文件中的 FLAGS_memory_tracker_detail_log 配置项,并调小 memory_tracker_detail_log_interval_ms 观察查询前后的内存使用情况;

folly::future 异步执行

thenValue([this](StorageRpcResponse<GetNeighborsResponse>&& resp) {
    memory::MemoryCheckGuard guard;
    // memory tracker turned on code scope
    return handleResponse(resp);
})
.thenError(folly::tag_t<std::bad_alloc>{},
    [](const std::bad_alloc&) {
    // handle memory exceed
})

同步执行

memory::MemoryCheckGuard guard; \
try {
    // ...
} catch (std::bad_alloc & e) { \
    // handle memory exceed
}

RPC 的内存检测

RPC 主要解决 Request/Response 对象的序列化/反序列化的内存额度控制问题,由于 storaged reponse 返回的数据均封装在 DataSet 数据结构中,所以问题转化为:DataSet 的序列化、反序列化过程中的内存检测。

序列化:DataSet 的对象构造在 NebulaGraph 算子返回结果逻辑中,默认情况下,已经开启内存检测;

反序列化:通过 MemoryCheckGuard 显式开启,在 StorageClientBase::getResponse's onError 可捕获异常;

错误码

为了便于分辨哪个模块发生问题,NebulaGraph 中还添加了相关错误码,分别表示 graphd 和 storaged 发生 memory exceeded 异常:

E_GRAPH_MEMORY_EXCEEDED = -2600, // Graph memory exceeded
E_STORAGE_MEMORY_EXCEEDED = -3600, // Storage memory exceeded

延伸阅读

  • 什么是 malloc 以及动态内存分配:https://en.wikipedia.org/wiki/C_dynamic_memory_allocation
  • jemalloc
    • 原始论文:https://www.bsdcan.org/2006/papers/jemalloc.pdf
    • Facebook 对 jemalloc 的优化:https://engineering.fb.com/2011/01/03/core-data/scalable-memory-allocation-using-jemalloc/

谢谢你读完本文 (///▽///)文章来源地址https://www.toymoban.com/news/detail-448066.html

到了这里,关于图数据库 NebulaGraph 的内存管理实践之 Memory Tracker的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 跨平台C++ Qt数据库管理系统设计与实战:从理论到实践的全面解析

    在我们的日常生活中,数据无处不在。无论是我们的个人信息,还是我们的购物习惯,甚至是我们的工作记录,都可以被视为数据。这些数据的管理和处理,对于个人和企业来说,都是至关重要的。这就是数据库管理系统(Database Management System,简称DBMS)的重要性所在。 数据

    2024年02月06日
    浏览(63)
  • MySQL数据库内存配置与性能优化:合理分配内存,提升数据库性能

             引言 :MySQL是广泛使用的关系型数据库管理系统,而合理配置数据库的内存是保障其高性能运行的关键之一.本文将介绍如何根据MySQL数据库内存值大小来定义,以及这样配置如何影响数据库的性能   内存配置的基本原则 : innodb_buffer_pool_size :该参数定义了InnoDB存储引擎

    2024年02月22日
    浏览(54)
  • 内存数据库如何发挥内存优势?

    与以磁盘存储为主的普通数据库相比,内存数据库的数据访问速度可以高出几个数量级,能大幅提高运算性能,更适合高并发、低延时的业务场景。 不过,当前大部分内存数据库仍然采用 SQL 模型,而 SQL 缺乏一些必要的数据类型和运算,不能充分利用内存的特征实现某些高

    2024年02月03日
    浏览(41)
  • Redis内存数据库

    Redis内存数据库 NoSQL数据库简介 Redis简介 Redis应用场景 windows下安装和使用Redis 在linux下安装redis Redis数据可视化RedisDesktopManager Redis配置 Redis 数据类型 Redis 字符串(String) Redis 哈希(Hash) Redis 列表(List) Redis 集合(Set) Redis 有序集合(sorted set) Redis key命令 Redis连接命令 Redis服务器命令

    2024年02月09日
    浏览(39)
  • springboot启动加载数据库数据到内存

    一般来说,springboot工程环境配置放在properties文件中,启动的时候将工程中的properties/yaml文件的配置项加载到内存中。但这种方式改配置项的时候,需要重新编译部署,考虑到这种因素,今天介绍将配置项存到数据库表中,在工程启动时把配置项加载到内存中。 springboot提供了

    2024年01月17日
    浏览(46)
  • 大页内存配置引发的数据库性能问题

    问题背景:         用户来电报故障,他们一套正常运行的Oracle数据库,突然出现了10分钟左右的性能卡顿问题,期间全部的业务操作都变慢,他们通过查看问题期间的awr报告,发现数据库在问题时间出现大量的libary cache等待事件,但每秒的硬解析并不高,不知道是什么原

    2024年02月21日
    浏览(45)
  • 轻松学习阿里云原生内存数据库Tair

    😊各位小伙伴们,新文章它来了!!! 最近在探索阿里云产品的时候,偶然得知阿里云开发者社区有很多可以实践上手的活动,自己也就抱着好奇的心态去看了看,刚好有个活动是有关于Redis的,之前在了解后端的时候就经常听到很多有关Redis的介绍,所以突然之间对这个活

    2024年02月13日
    浏览(41)
  • 实用数据库开发实践MySQL——数据模型

    目录 第1关 关系模型 关系型数据模型 关系模型基本术语 关系模型的数据操纵与完整性约束 数据操纵 完整性约束 关系模型优缺点 优点 缺点 实验 头歌实验代码 第2关 层次模型 层次型数据模型 层次模型的数据操纵与完整性约束 数据操纵 完整性约束 层次模型优缺点 优点 缺

    2024年02月07日
    浏览(53)
  • 前端数据库与缓存实践

    前端数据库与缓存技术在现代网络应用中发挥着越来越重要的作用。随着前端技术的不断发展,前端数据库和缓存技术也在不断发展和进化。这篇文章将从以下几个方面进行阐述: 背景介绍 核心概念与联系 核心算法原理和具体操作步骤以及数学模型公式详细讲解 具体代码实

    2024年02月21日
    浏览(35)
  • Springboot集成轻量级内存数据库H2

    最近做一个小项目,需要存储的数据不多,用mysql太重了,用其他的Redis之类的也不太方便,然后就想到了H2,他就是一个jar包,可以和项目一起打包发布,非常适合数据量不多的微小系统,下面大概介绍下H2的基本知识和Springboot的集成 H2是一个用Java开发的嵌入式数据库,它本

    2024年02月07日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包