
Eski:用Odin写的单机内存存储,单核性能对标Redis
来源标题:Show HN: Eski – an in-memory store in Odin, measured against Redis on one core
开发者发布用Odin语言编写的单机内存数据存储Eski,兼容RESP协议和单节点Redis命令集(含字符串、哈希、流、Lua脚本和持久化)。在单核测试中,Eski各项负载的每秒请求数均高于对比的三个引擎,p99延迟不高于它们;填充1000万个64字节键后每键常驻内存为113字节。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
Eski 是一个面向单机的内存数据存储。它使用 RESP 协议,因此现有的客户端、工具和脚本无需改动即可连接,并且实现了单节点命令面:字符串、带字段过期的哈希、列表、集合、有序集合、地理空间搜索、位图、HyperLogLog、流、发布/订阅、事务、ACL、RDB 和仅追加持久化、Lua 5.1.5 脚本(含 Function 库和交互式调试器),以及在同一键空间上可选的 memcached 文本端点。它用 Odin 编写,内嵌固定版本的 C Lua 运行时,以 Apache-2.0 许可发布,并在每次变更时通过上游一致性测试套件验证。
模块、复制、Sentinel 和 Cluster 不在本仓库范围内。Eski 是节点;节点之间的协调属于单独的层。
它在关键之处很快。在下面记录的运行中,单个服务器核心上,Eski 在每个工作负载下的每秒请求数都高于其他三个引擎中的每一个,且 p99 延迟从未高于它们。在 pipeline 16 下,内核在每个请求中的占比缩小,差距急剧拉大。
它很精简。在填充 10,000,000 个 64 字节的键之后,Eski 每个键占用 113 字节常驻内存;下面的填充表记录了全部四个引擎的数字。
它在测量上是诚实的。本页的每个数字都来自一份已提交的数据集,包含其原始每秒序列、机器采样和排除原因,由脚本生成到本 README 中,当表格过期时该脚本会拒绝运行。方法在下面描述,可以在一个下午内于租用的服务器上复现。
它的兼容性靠证据,而非声明。参考服务器的测试套件针对 Eski 运行,并设有受保护的基线;下面的一致性表由该次运行重新生成,跳过的测试会被列出而不是计入。
它是一个无运行时的单一二进制。没有垃圾回收器,引擎本身没有虚拟机,除非你开启,否则没有工作线程。事件循环、键空间和回复路径都用 Odin 编写,从上到下阅读即可。
记录的测量
这些结果来自已提交的 cloud-v2 数据集。每个引擎使用相同的服务器 CPU 分配和内存上限、独立的客户端核心、全新的数据集以及计时前的预热。运行记录了功耗、温度、CPU 空闲和负载生成器限制。无效的重复保留其原因,受客户端限制的测量保持标注。这些测量描述的是所记录的机器和工作负载;它们不构成多核扩展性或通用吞吐量的主张。
来自 bench/testbed/runs/cloud-v2 的记录中位数,仅使用有效重复。这些是来自一台机器的测量,不是对每个工作负载或部署的主张。
没有纳入的重复受客户端限制。观察到的 GET 64 B pipeline 1 中位吞吐量比率为 1.08x Eski / Redis。被排除的重复不会获得推断分数。方法、范围、图像和排除项。
阅读基准方法和表格、自包含报告以及记录的图表。
从已提交的输入复现表格和徽章,或在你自己的机器上重新测量: python3 -I test/readme_tables.py python3 -I test/readme_tables.py --check
在新机器上运行比较列出了十条命令,可将本仓库的克隆带到已提交的运行目录。
在 Linux 上使用 Odin、clang、OpenSSL 3 和 libunwind 开发库、GNU ar、sha256sum、flock 和 Python 构建。容器构建使用的编译器版本在 Dockerfile 中通过校验和固定;Lua 源代码和补丁已内嵌以支持离线构建。 ./build.sh check ./build.sh ./bin/eski --bind 127.0.0.1 --port 6399
在另一个终端中,使用任意 RESP 客户端: redis-cli -p 6399 SET demo:message hello redis-cli -p 6399 GET demo:message redis-cli -p 6399 HSET demo:user name Ada redis-cli -p 6399 HGETALL demo:user redis-cli -p 6399 EVAL 'return redis.call("INCR",KEYS[1])' 1 demo:counter
该示例故意绑定到回环地址。在暴露监听器之前,请配置网络访问和 ACL。认证和仅追加持久化是选择启用的;计划快照使用标准的默认保存规则。构建、运行和测试涵盖编译器发现、配置文件、容器、持久性和每个验证层。
一个循环,一个所有者。协调器在单个事件循环线程上运行。Linux epoll 报告就绪的套接字,每个连接保持自己的输入和输出缓冲区,解析器保留不完整的请求直到更多字节到达。完整的参数到达命令注册表,其条目携带处理程序、参数数量、标志、键规范和参数元数据。处理程序在选定的数据库上操作,并将类型化的 RESP 回复直接写入连接的输出缓冲区。流水线保持每个连接的命令顺序。一个请求就是一次读和一次写;循环通过 vDSO 读取时钟,从不为了查看时间而进入内核。
两种分配生命周期。每个事件循环迭代都有一个暂存分配器,在迭代结束时重置。解析、参数处理和回复格式化都在那里进行。任何必须比迭代存活更久的东西——存储的值、排队的操作、阻塞的客户端——都使用自有存储。这一条规则将瞬态工作与键空间分开,也是 Eski 中内存核算只需阅读代码而无需猜测收集器会做什么的原因。
为缓存构建的键空间。数据库表是开放寻址的,采用结构体数组布局:紧凑的控制字节在任何完整键比较之前识别候选槽位,槽位元数据携带过期和访问信息。短键和短字符串值适合它们自己的句柄,无需单独分配。较大的载荷使用 slab 或堆存储,集合值指向类型特定的对象。增长是增量的,因此没有操作会一次性重新哈希整个数据库;哈希、集合和有序集合的成员表在安静时段以有界步骤迁移。
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。