# DolphinDB教程:内存管理 DolphinDB 是一款支持多用户多任务并发操作的高性能分布式时序数据库软件(distributed time-series database)。针对大数据的高效的内存管理是其性能优异的原因之一。本教程介绍以下内容: - [DolphinDB教程:内存管理](#dolphindb教程内存管理) - [1. 内存管理机制及相关配置参数](#1-内存管理机制及相关配置参数) - [2. 高效使用内存](#2-高效使用内存) - [3. 内存监控及常见问题](#3-内存监控及常见问题) - [3.1 内存监控](#31-内存监控) - [3.2 常见问题](#32-常见问题) - [4. 变量的内存管理](#4-变量的内存管理) - [4.1 创建变量](#41-创建变量) - [4.2 释放变量](#42-释放变量) - [5. 分布式数据库读缓存管理](#5-分布式数据库读缓存管理) - [5.1 OLAP引擎分布式表的缓存管理](#51-olap引擎分布式表的缓存管理) - [5.2 TSDB 引擎分布式表的读缓存管理](#52-tsdb-引擎分布式表的读缓存管理) - [6. 为分布式数据库提供写入缓存](#6-为分布式数据库提供写入缓存) - [6.1 OLAP引擎的写入缓存](#61-olap引擎的写入缓存) - [6.2 TSDB 引擎的写入缓存](#62-tsdb-引擎的写入缓存) - [7. 流数据消息缓存队列](#7-流数据消息缓存队列) ## 1. 内存管理机制及相关配置参数 DolphinDB 使用 [TCMalloc](https://google.github.io/tcmalloc/) 进行内存分配。当用户查询操作或编程环境需要内存时,DolphinDB 会以 512MB 为单位向操作系统申请内存。如果操作系统无法提供大块的连续内存,则会尝试 256MB,128MB 等更小的内存块。系统会每隔30秒扫描一次,如果内存块完全空闲,则会整体还给操作系统,如果仍有小部分内存在使用,比如 512MB 的内存块中仍有 10MB 在使用,则不会归还操作系统。 DolphinDB 开放了一些内存管理相关的配置项,方便用户根据系统情况进行合理设置。 __通过参数 maxMemSize 设定节点的最大内存使用量__:该参数指定节点的最大可使用内存。如果设置太小,会严重限制集群的性能;如果设置值超过物理内存,则内存使用量过大时可能会触发操作系统强制关闭进程。同一个服务器上各数据节点的最大内存使用量之和,建议设置为机器可用内存的 75%。例如机器内存为 16GB,并且只部署1个节点,建议将该参数设置为 12GB 左右。 __参数 warningMemSize 设定最多可缓存的数据量__:当节点的内存使用总量小于 warningMemSize(以 GB 为单位,默认值为 maxMemSize 的 75%)时,DolphinDB 会尽可能多的缓存数据库分区数据,以便提升用户下次访问该数据块的速度。 当内存使用量超过 warningMemSize 时,系统采用 LRU 的内存回收策略,自动清理部分数据库的缓存,以避免出现 OOM 异常。 __参数 reservedMemSize 和 maxBlockSizeForReservedMemory__:当系统剩余可用的内存不能满足用户的操作需要时,可能会出现数据读写失败、OOM 等问题。当已分配内存不满 maxMemSize 但已达到 maxMemSize - reservedMemSize 时, 新分配内存的操作如大于 maxBlockSizeForReservedMemory 将受限或失败,从而减少系统关键操作失败的概率。例如,当出现因内存不够导致写入失败时,仍然能够正常回滚,避免出现数据不一致的问题。 __参数 memoryReleaseRate 控制将未使用的内存释放给操作系统的速率__:memoryReleaseRate 是0到10之间的浮点数。 memoryReleaseRate=0 表示不会主动释放未使用的内存。设置值越高,DolphinDB 释放内存的速度越快。默认值是5。 __参数 maxPartitionNumPerQuery 控制单次查询数据量__:系统默认允许单次最多可查找 65536 个分区的数据。若一次查询过多分区,需加载到内存的数据量过大,则可能导致 OOM。可根据需求以及可用内存量,适当调节该参数,控制单次可查询的分区数量。 关于参数配置的详情,参见用户手册,内存配置参数([单实例配置](https://www.dolphindb.cn/cn/help/DatabaseandDistributedComputing/Configuration/StandaloneMode.html)) ## 2. 高效使用内存 在企业的生产环境中,DolphinDB 往往作为流数据处理中心以及历史数据仓库,为业务人员提供数据查询和计算。当用户较多时,不当的使用容易造成 Server 端内存耗尽,抛出 "Out of Memory" 异常。可遵循以下建议,尽量避免内存的不合理使用。 * __合理均匀分区__:DolphinDB 以分区为单位加载数据,因此,分区大小对内存影响巨大。合理均匀的分区,不管对内存使用还是对性能而言,都有积极的作用。因此,在创建数据库的时候,根据数据规模,合理规划分区大小。每个分区压缩前的数据量在 100M B到 1GB 之间为宜。具体分区设计,请参考[分区注意事项](./database.md#4-分区设计注意事项)。 * __及时释放数据量较大的变量__:若用户创建数据量较大的变量,例如 ```v = 1..10000000```,或者将含有大量数据的查询结果赋值给一个变量 ```t = select * from t where date = 2010.01.01```,v 和 t 将会在用户的 session 占用大量的内存。如果不及时释放,执行其他任务时,就有可能因为内存不足而抛出异常。用户的私有变量在其创建的 session 里面保存。session 关闭的时候,会回收这些内存。可通过 `undef` 函数将其赋值为 NULL,或者关闭 `session` 来及时释放变量的内存。 * __只查询需要的列__:避免使用select \*,select \* 会把该分区所有列加载到内存,而实际查询往往只需要几列的数据。因此,为避免内存浪费,尽量明确写出所有查询的列。 * __数据查询尽可能使用分区过滤条件__:DolphinDB 按照分区进行数据检索,如果不加分区过滤条件,则会全部扫描所有数据,数据量大时,内存很快被耗尽。若存在多个过滤条件,将包含分区列的过滤条件前置。 * __合理配置流数据的缓存区__:一般情况下流数据的容量(capacity)会直接影响发布节点的内存占用。比如,在对流数据表进行持久化时,若 capacity 设置为1000万条,那么流数据表在超过1000万条时,会将约一半的数据进行存盘并回收,也就是内存中会保留500万条左右。因此,应根据发布节点的最大内存,合理设计流表的 capacity。尤其是在多张发布表的情况,更需要谨慎设计。 ## 3. 内存监控及常见问题 ### 3.1 内存监控 #### 3.1.1 controller 上监控集群中节点内存占用 在 controller 上提供函数 `getClusterPerf()` 函数,显示集群中各个节点的内存占用情况。包括: __memoryAlloc__:节点上分配的总内存,近似于向操作系统申请的内存总和。 __memoryUsed__:节点已经使用的内存。该内存包括变量、分布式表缓存以及各种缓存队列等。 __maxMemSize__:节点可使用的最大内存限制。 #### 3.1.2 `mem()` 函数监控某个节点内存占用 `mem()` 函数可以显示整个节点的内存分配和占用情况。`allocatedBytes` 为已分配内存;`freeBytes` 是可用内存。两者之差为已占用内存。通过 `mem().allocatedBytes - mem().freeBytes` 得到节点所使用的总的内存大小。(注:如使用 1.20.0 及更早版本,通过 `sum(mem().allocatedBytes - mem().freeBytes)` 进行计算。) #### 3.1.3 `objs()` 函数监控某个会话内各变量的内存占用 通过函数 `objs` 来查看会话内所有变量的内存占用。该函数返回一个表,其中列 bytes 表示变量占用的内存块大小。`objs(true)` 除返回当前会话中变量的内存占用外,还返回共享变量的内存占用(包括其他会话共享的变量)。 #### 3.1.4 查看某个对象占用的内存大小 通过函数 `memSize` 来查看某个对象占用内存的具体大小,单位为字节。比如 ``` v=1..1000000 memSize(v) ``` 输出:4000000。 #### 3.1.5 监控节点上不同 session 的内存占用 可通过函数 `getSessionMemoryStat()` 查看节点上每个 session 占用的内存量,输出结果只包含 session 内定义的变量。由于共享表和分布式表不属于某个用户或会话,因此返回结果中不包含它们占用的内存信息。由于输出结果亦包含用户名,也可查看每个用户的内存使用情况。 #### 3.1.6 查看后台正在运行的任务 以上方法无法获取后台运行任务所占用的内存。可以通过函数 `getRecentJobs` 来查看正在运行的后台任务。目前,DolphinDB 无法通过函数查看后台任务的内存占用情况,需用户根据业务逻辑进行估算。若内存比较紧张,可以取消一些暂无必要执行的后台任务,以释放内存空间。 ### 3.2 常见问题 #### 3.2.1 监控显示节点内存占用太高 若节点内存接近 warningMemSize,通过函数 `clearAllCache()` 来手动释放节点的缓存数据。如果内存占用仍然未显著降低,请依次排查下述问题: * 通过函数 `getSessionMemoryStat()` 结合 `objs()` 和 `objs(true)` 排查是否某个用户忘记 `undef` 了变量 ; * 通过函数 `getRecentJobs()` 和 `getConsoleJobs()` 查看是否还有超过预期运行时长的后台或交互任务。运行中的任务的内存占用不反映在 `getSessionMemoryStat()` ; * `getStreamingStat()` 查看流数据内存占用,详见 [7. 流数据消息缓存队列](#7-流数据消息缓存队列)。 #### 3.2.2 Out of Memory (OOM) 该异常往往是由于 query 所需的内存大于系统可提供的内存导致的。请先执行下列脚本查看所有数据节点内存使用情况,其中 maxMemSize 为配置的节点最大可用内存,memoryUsed 为节点已使用内存,memoryAlloc 为节点已分配内存。 ``` select site, maxMemSize, memoryUsed, memoryAlloc from rpc(getControllerAlias(),getClusterPerf) ``` OOM 一般可能由以下原因导致: - 查询没有加分区过滤条件或者条件太宽,导致单个 query 涉及的数据量太大。 查询涉及多少分区可用 `sqlDS` 函数来判断,示例脚本如下: ``` ds=sqlDS(