重新分析 - 第2部分:简单性
#database #体系结构 #redis

让我们谈谈Redis的简单性。
Redis最初被设计为简单的商店,似乎它的API实现了这一目标。
不幸的是,Redis的简单设计使其在生产中不可靠且难以管理。

所以问题是 - 作为数据存储用户,简单性对您意味着什么?

在研究重新详细信息之前,披露:我在下面提到的一些问题
不要出现少量工作量。对于那些管理重新案例的人
大于12GB,请回答以下问题:

  • 有八个Redis Cache驱逐策略。您对他们的了解程度如何?你对任何一个
    都满意吗 其中之一?

  • 当您需要更改redis设置时,您有多自信,尤其是
    那些控制其内存消耗的人?您可以保证它的峰值内存使用方式是什么?

  • 您是否曾经需要调试无反应的REDIS实例或其OOM崩溃?它有多容易?

  • 当多个客户连接到redis实例时,您是否曾经观察过连接过载事件?

Redis成立十四年后,我们已经确定工程社区需要
一个简单,低延迟的类似REDIS的API,可以补充关系数据库。但是,社区不太可能与难以管理的脆弱技术解决。 API的简单性并不意味着基础不应该是牢固的。

我很幸运地观察了Redis并在全球范围内纪念用法,所以
我的看法是通过查看整个工作负载来塑造的:
我已经看到了那些“简单”的设计决策
在4-16GB的工作负载下引起了最佳但易于管理的怪癖,使其在64GB的尺度上痛苦,
并引起挫败感和缺乏100多种GB工作量的信任。

REDIS缓存

我已经提到了缓存策略。当用作缓存时,它是Redis中更重要的设置之一(Redis非常受欢迎的用例)。在一个完美的世界中,Redis的常规用户很乐意拥有一个神奇的缓存,可以执行以下操作:

  • 收回过期的项目,而不是在记忆中成长。顺便说一句,此要求也适用于非缓存方案。
  • 同样,如果有大量的过期项目,也不会驱逐非预期的条目 可以被驱逐。
  • 通过保持最有可能被击中的条目以强大的方式最大化命中率。

普通用户不想知道LFU或LRU是什么,或者为什么Redis实施
猜测这些启发式方法或为什么它不能有效驱逐过期的项目。
实际上,不仅期望用户知道内部实施详细信息
在Redis缓存算法中,他还需要在maxmemory-policy设置的八个“简单”选项之间做出决定。

Image description

持久性

如果我不得不选择一个看起来“简单”的设计选择
然而,对整个系统的可靠性和其他功能的复杂性产生了重大的负面影响 - 这将是基于叉子的BGSAVE命令。 BGSAVE算法允许REDIS生成内存数据的时间点快照或与其次要复制品同步。 REDIS通过将序列化例行程序置于儿童过程中来做到这一点。通过这样做,这个子过程从Linux获取“ Free”的父过程内存的时间点快照。 REDIS依赖于Linux属性,该属性不会在fork()期间复制物理内存,而是使用懒惰的复制操作。

使用操作系统实现一致的快照隔离,看起来像是一种优雅的选择。但是,这种方法存在一些严重的问题:

  1. 缺乏背压当redis父过程突变其条目时,实际上它用牛复制了Linux存储器页面。牛对父母的过程是透明的。所以 REDIS内存组件很难估计其实际内存使用情况。 即使这样做,也没有有效的机制来“推迟”或拖延传入的写入 - 执行线必须随着流动而进行。在沉重的写作下,这很容易导致OOM崩溃。

Image description

  1. 无界的内存开销 重新出现物理记忆。不幸的是,它不会随着副本的同步而停止:父还必须在快照过程中握住突变的复制日志,该快照会在内存中生长,直到同步完成为止。此外,由于用户继续添加项目,因此父数据集可以超出其初始尺寸。 这些因素可能会导致在不同的写入负载或数据库大小下疯狂地尖峰的RS使用。所有这些使得很难估计Redis的最大记忆使用量。

Image description

  1. Linux大页面启用大页面通常会改善数据库性能;但是,借助Redis和Bgsave,巨大的页面会产生高写入 - 很小的写入会导致2MB或1GB牛。这很快会导致100%的内存开销和写入期间的主要延迟峰值。
  2. 与其他REDIS功能的不良互动询问Redis维护者在Redis 6中实现TLS支持的困难。看似无关的功能由于如何与fork()调用相互作用,因此存在几个问题。结果:redis中的TLS具有平庸的性能。

到目前为止,我已经提到了Redis的一些稳定性问题,这些问题无法解决
与当前的Redis体系结构。还有一系列其他问题损害了可靠性,影响其资源使用情况或掩盖其API保证。这是其中一些按随机顺序的:

  • 不可靠的副本同步:如果副本同步期间主的复制缓冲区溢出, 复制品将重试整个同步流,可能进入无休止的尝试的无限循环。
  • 阻止命令中不可靠的超时:阻止命令可以随着超时而大得多。
  • 不受控制的冻结:像FLUSHDB这样的命令将在执行期间“阻止世界”。 这意味着Redis可能会完全停止处理正在进行的请求几分钟或更长时间。
  • LUA失速:运行不良LUA脚本时存在类似的问题。 此外,由于重新执行的顺序性质,即使是好的脚本也可能导致其他并发请求的延迟。
  • pub/sub不可靠,当子客户断开连接时易于数据丢失。

对抗复杂性

我认为是时候重新设计曾经挑战传统数据库但如今遭受
的系统了 从复杂性本身。

最近,我一直在从事一种库存和词典的小说设计
可能是下一代内存商店的基础石头的数据结构。
这项工作仍在进行中,但它已经显示出一些有希望的结果。
缓存设计是如此新颖,我认为它值得拥有自己的博客文章。因此,今天我将仅分享基本词典的基本特征。

在下面,您可以看到Redis 6.2与我的实验商店(POC),两者都在GCP中的专用64-CPU N2D实例上运行。

我在两个服务器上运行相同的三个命令:debug populatesaveflushdbpopulate很有趣,因为它证明了基础引擎的原始效率,
没有像网络这样的瓶颈。 saveflushdb很有趣,因为

是必须处理整个数据库的“停止世界”命令,其性能直接影响
数据库鲁棒性。

Image description

您可以看到,在Redis中创建200m的项目需要将近180。这个数字值得注意的
是在REDIS的写入吞吐量限制上设置上限:不管Redis服务器有多大,
它的行驶速度将比〜11m左右的速度更快,因为记录创建总是在单个线程中完全完成的。保存200m记录需要150秒,这表明服务器可以持续其数据或复制到副本的速度。注意差距:这些200M项目占用17GB的RAM,
因此,Redis在150年代或110MB/s(为了比较,AWS(SC1)中最慢的HDD
)移动17GB 达到250MB/s,在GCP(PD标准)达到400MB/s)。
最后,flushdb证明了一个简单的“空我的商店”
操作可能是CPU密集型的,并且将其他请求完全停滞了近3分钟!
不再那么简单了。

下面的快照是正在开发的POC商店。

Image description

您可以看到相同的命令更快地运行一个数量级。结果是部分
由于shared-nothing architecture在CPU上分发了操作,但也是由于新颖的
字典设计。顺便说一句,您可以看到flushdb操作没有计时-redis-cli不显示
“快速”操作的潜伏期低于500ms。因此,我们可以得出结论,在这种情况下,flushdb至少要快350倍。如果比较used_memory_human度量,则可以看到Redis
所需的RAM几乎比POC(16.9GB vs 9.5GB)多两倍。

还有很多要覆盖的东西。例如,redis SAVE失速了所有请求的处理,
类似于FLUSHDB。相比之下,新商店与其余的流量同时运行,同时仍会产生一致的时间快照。换句话说,它提供了BGSAVE的产品体验,并具有SAVE的可靠性。

如果我们结合了新设计带来的长期改进的尾巴,我们将获得产品
这重新定义了对内存数据库世界的简单性和易用性。