内存存储后端¶
ArcticDB 可以使用基于文件的 LMDB 后端或基于 RAM 的内存存储,作为 S3 和 Azure 等对象存储的替代方案。
对于临时内存解决方案,LMDB 可以设置为写入 tmpfs。正如本指南将探讨的,使用此解决方案,多个写入器可以并发访问数据库,并且与写入永久性磁盘文件系统的 LMDB 相比,性能也会提高。
在 Linux 上,设置 tmpfs 文件系统的步骤如下
$ mkdir ./tmpfs_mount_point
$ sudo mount -t tmpfs -o size=1g tmpfs tmpfs_mount_point
我们可以通过以下命令检查它是否存在
$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 1.0G 0 1.0G 0% /somedir/tmpfs_mount_point
在 ArcticDB 中,您可以像往常一样使用 LMDB 连接到此文件系统
import arcticdb as adb
import pandas as pd
ac_lmdb = adb.Arctic('lmdb:///somedir/tmpfs_mount_point')
lib = ac_lmdb.get_library('lib', create_if_missing=True)
lib.write('symbol', pd.DataFrame({'a': [1, 2, 3]})
print(lib.read('symbol').data)
结果是
a
0 1
1 2
2 3
内存存储的等效实例化使用 URI 'mem://'
,如下所示
ac_mem = adb.Arctic('mem://')
ac_mem
实例拥有存储,存储仅在其 ac_mem
Python 对象的生命周期内存在。在幕后,使用 Folly Concurrent Hash Map 作为键/值存储。对于测试用例和实验,内存后端是一个不错的选择。
如果多个进程想要并发访问相同的数据,我们建议使用基于 tmpfs 的 LMDB。
如何处理并发写入器?¶
再次需要注意的是,当配置了对象存储后端(例如 S3)时,ArcticDB 才能达到最高的性能和可伸缩性。尽管如此,应用程序可能希望并发写入 LMDB 存储。内存存储不适合此用例。
以下 Python 代码使用 multiprocessing
启动 50 个进程,这些进程并发写入不同的符号。(不支持针对同一符号的非分阶段并行写入)。
# Code tested on Linux
import arcticdb as adb
import pandas as pd
from multiprocessing import Process, Queue
import numpy as np
lmdb_dir = 'data.lmdb'
num_processes = 50
data_KB = 1000
ncols = 10
nrows = int(data_KB * 1e3 / ncols / np.dtype(float).itemsize)
ac = adb.Arctic(f'lmdb://{lmdb_dir}')
ac.create_library('lib')
lib = ac['lib']
timings = Queue()
def connect_and_write_symbol(symbol, lmdb_dir, timings_):
ac = adb.Arctic(f'lmdb://{lmdb_dir}')
lib = ac['lib']
start = pd.to_datetime('now')
lib.write(
symbol,
pd.DataFrame(
np.random.randn(nrows, ncols),
columns=[f'c{i}' for i in range(ncols)]
)
)
timings_.put((start, pd.to_datetime('now')))
symbol_names = {f'symbol_{i}' for i in range(num_processes)}
concurrent_writers = {
Process(target=connect_and_write_symbol, args=(symbol, lmdb_dir, timings))
for symbol in symbol_names
}
# Start all processes
for proc in concurrent_writers:
proc.start()
# Wait for them to complete
for proc in concurrent_writers:
proc.join()
assert set(lib.list_symbols()) == symbol_names
timings_list = []
while not timings.empty():
timings_list.append(timings.get())
pd.DataFrame(timings_list, columns=['start', 'end']).to_csv('timings.csv')
使用 matplotlib 绘制每个进程的生命周期,我们得到
图解:每个线段代表一个进程向共享 LMDB 后端写入的执行。LMDB 在 lib.write(..)
调用过程中反复获取和释放文件锁。
LMDB 文档主页指出多线程并发也是可能的。然而,正如本页所述,我们不应该在同一个进程中多次调用 mdb_env_open()
。因此,由于此函数在 Arctic 实例化中调用,上述代码无法转移到多线程应用程序中。
tmpfs 上的 LMDB 比磁盘上的 LMDB 快吗?¶
请参阅下面的计时结果和附录,以获取生成此数据的代码。差异并不大,tmpfs 仅在符号写入操作中优于磁盘。尽管如此,tmpfs 显然是临时 LMDB 后端更好的选择。我们还可以看到,正如预期的那样,内存存储在整体上要快得多。
注意:范围是基于每个数据大小重复五次的 95% 置信区间。硬件限制是使用 np.save
和 np.load
函数写入磁盘(而非 tmpfs)的限制。未寻求 RAM 的硬件限制数据。
附录:性能分析脚本¶
用于生成上述图表的性能分析脚本,以及使用 fio
确定理论最大读写速度的讨论已移至此 Wiki 页面。