系统与数据库2019pages.cs.wisc.edu
远程内存键值存储:一个已经过时的主意?
Remote, in-memory key-value stores: An idea whose time has come and gone?
进阶 · 约 60 分钟读完前置知识:分布式系统基础、缓存系统使用经验、Redis/Memcached 基本原理
导读
这篇论文探讨远程内存键值存储(RInK,如 Redis/Memcached 这类独立缓存服务)是否还值得使用。文章质疑这一架构的必要性,认为将状态放回应用进程内的有状态服务器在性能上可能更有优势,引发对缓存架构取舍的讨论。
HN 关注点:HN 读者长期争论应用内缓存 vs 远程缓存(Redis)的取舍,涉及性能、可靠性和运维复杂度,与许多工程师的日常架构决策直接相关。
HN 评论精选
这几条评论里:质疑 1 补充 2 亲历 1
质疑有状态服务器适合早期原型,但团队协作和频繁部署时代码/数据迁移复杂;远程 KV 存储让部署与数据解耦、升级可渐进进行
有状态应用服务器其实是新应用初期原型的常见架构,因为简单。HN、Mailinator、Plenty of Fish,以及我记得 StackOverflow 都是用这种架构构建的。它真正出问题是在团队协作添加新功能时:代码变更远比数据频繁,每次部署新代码都需要复杂易错的流程来启动新进程、读取现有状态、确保与新功能一致,不一致时还要迁移格式。而使用 RInK 的一大好处是应用可以随意启停,持久状态都在那里,且 RInK 本身通常处理持久化和错误检查(因为它强制统一数据模型)。对于产品早期占大头的 UI 微调和按请求的功能改动,根本不需要迁移,新版本直接生效。对于更复杂的数据格式变更,可以让应用代码同时读写新旧格式,必要时把旧格式转换为新格式并只写回新格式,等旧格式在持久存储中消失后再丢弃。这样可以渐进式升级,避免操作失误毁掉整个数据存储。
nostrademons查看英文原文
The stateful application server is actually a common architecture for initial prototypes of new apps, because of its simplicity. Hacker News, Mailinator, Plenty of Fish, and IIRC StackOverflow were all built with this architecture. Where it really falls down is when you have a team of developers all trying to add new features. Code changes a lot more frequently than data. With stateful app servers, every time you deploy new code you need complex and error-prone routines to bring the new process up, have it read the existing state, make sure it's consistent with the new features, and if not migrate it to the new format. With RInKs, a huge benefit is that you can take the application up and down and all persistent state is already there, and usually the RInK itself handles persistence & error-checking (because it enforces a common data model itself). For minor UI tweaks and per-request features (which are a large portion of changes in the early part of the product lifecycle), you don't need to do a migration at all, the new version just magically appears. For more complex data format changes, you make it so that the application code can read both the old and new formats, converts old to new if necessary, and writes back only in the new format, and then you can drop the old format once it is no longer present in the persistent store. This lets you do the upgrade piecemeal, and avoids corrupting your entire data store if you do it wrong (because the architecture functions as a canary on every deployment, and you can rollback with zero to a handful of corrupted records if things aren't working properly). The other situation where stateful app servers are useful is when scalability is much more important than change velocity. This may be why Google is coming out with this paper now. This would never have flown within Google when I was there (2009-2014), but I've heard that the focus on maintenance, performance, and architectural improvements is now much greater than that on new features, so it may be appropriate for the Google of today.
亲历用高性能语言时应用内缓存远优于 Redis:性能差约 20 倍,Redis 只在慢语言或特殊场景有价值
我惊讶于公司里那么多开发者在只需要缓存时就上 Redis。相比 Java 的 Caffeine,这是巨大的精力浪费,性能更差、可靠性更低。有状态服务器在他人从非固定 IP 访问时会遇到大问题(粘性会话通常基于 IP),我们至少在我参与的应用里把共享状态塞进 JWT token 来规避。其他需要缓存的数据放进应用内内存存储。如果你一开始就用'快'的语言,除非达到 Google 规模,否则不需要超过十几台服务器,即使每个实例维护自己的缓存,命中率也不错。而且用快语言时,应用内存储的内存开销并不比专用 KV 存储高多少——性能通常差 20 倍的情况下实在没理由用它。我们大量使用 DropWizard 和 Vert.X,多数端点每秒能处理数千请求,用缓存而非每次查库的端点能达到每秒 100 万以上请求,而远程 KV 存储在同一应用里能到 5 万就非常幸运了。多数负载均衡策略出于 TCP 性能考虑本来就会把同一客户端的连续请求发到同一后端。KV 缓存唯一的理由是你在用 Ruby、PHP、Python 这类慢语言,对象内存开销太大……
nullwasamistake查看英文原文
I'm astounded at the number of developers in my own company that reach for Redis when they just need a cache. It's a huge waste of effort for far worse performance and less reliability than something like Java's Caffiene. Stateful servers run into huge problems when other people try to access them from non-static ip (usually sticky sessions are IP based). We, at least in apps I work on, stuff all the shared state into JWT tokens to avoid this. Everything else that needs to be cached goes into an in-memory in-app store. If you're using a "fast" language to start with, you won't need more than a dozen servers unless you're at Google scale so cache hit rates are good even though each instance maintains it's own. Again if you're using a "fast" language memory overhead from in app storage isn't much more than putting things in a dedicated KV store. There's really no point to it when the performance drop is usually 20x. We use DropWizard and Vert.X extensively, and most of our endpoints can handle many thousands of requests per second. The ones that use the cache instead of hitting the DB every time hit upwards of 1 million requests/sec. You're very lucky to get 50k with any remote KV store in the same app. Most load balancer strategies send many requests in a row from a client to the same backend server anyways for TCP performance reasons. The only reason for a KV cache is if you're running a slow language like Ruby, PHP, Python, where the memory overhead for objects is large and the speed of a remote cache is roughly the same as one in memory. If you find yourself needing a cache for good enough performance you shouldn't be using these languages in the first place. In memory caches don't work well when you need 100+ server instances to handle the load anyways
补充权衡在于进程内缓存的不稳定性(如 OOM 崩溃)vs 远程缓存的网络开销,作者团队为稳定性选择了远程
这是进程内存储给主应用带来不稳定性与网络和序列化开销之间的权衡。比如内存溢出错误会让应用崩溃,而不是提供降级体验。答案照例是'看情况'。我们从进程内迁移到了远程,因为我们更看重应用稳定性而非极致性能。
neeleshs查看英文原文
It's a balance between how inprocess stores bring instability to the main application vs cost of n/w & marshalling. Out of memory errors crashing the application, instead of providing a degraded experience etc. Answer as usual is, "it depends".We moved from inprocess to remote, because we valued application stability more than raw performance.
补充指出论文遗漏了 FlatBuffers/Cap'n Proto 等零拷贝序列化方案,可消除反序列化开销
有趣的是这篇文章没有提到 FlatBuffers 或 Cap'n Proto 这类免解析数据结构,它们不需要反序列化。
pwpwp查看英文原文
Funny that this doesn't mention parse-free data structures like FlatBuffers or Cap'n Proto that don't need unmarshalling. https://google.github.io/flatbuffers/ https://capnproto.org/
相关资源
从这篇论文所有 HN 讨论帖的全部评论里整理出来的链接。
历次讨论
- 2019Hacker News 讨论帖146 分 · 72 评
同领域还值得读
I/O 比 CPU 更快——让我们划分资源并消除操作系统抽象
I/O Is Faster Than CPU – Let’s Partition Resources and Eliminate OS Abstractions
指出在现代服务器硬件上 I/O 速度已接近甚至超过 CPU,传统操作系统抽象反而成为瓶颈。
系统与数据库2019 · 817 分 · 277 评进阶 · 约 30 分钟编辑精选使用 io_uring 的高效 I/O
Efficient IO with io_uring
由 Linux 内核开发者 Jens Axboe 撰写,介绍 Linux 新的异步 I/O 接口 io_uring。
系统与数据库2019 · 426 分 · 96 评进阶 · 约 45 分钟编辑精选线程对大多数用途而言是个坏主意(1995)
Threads Are a Bad Idea for Most Purposes (1995)
这篇 1995 年的文章由 John Ousterhout 撰写,讨论为什么线程对大多数用途而言是坏主意,并主张事件驱动编程通常更简单、更高效。
系统与数据库2017 · 423 分 · 382 评进阶 · 约 25 分钟编辑精选学习型索引结构之论证
The Case for Learned Index Structures
提出把索引看作一种"模型":既然 B-tree 等结构本质上是在学习数据的分布,就可以用机器学习模型(如回归模型、神经网络)来近似键到位置的映射,从而替代 B-tree、哈希索引…
系统与数据库2017 · 398 分 · 66 评 · 引用 25进阶 · 约 60 分钟编辑精选FoundationDB:一个分布式、解耦的事务型键值存储
FoundationDB: A distributed, unbundled, transactional key value store
介绍 FoundationDB:一个分布式、解耦设计的事务性键值存储。
系统与数据库2021 · 283 分 · 99 评进阶 · 约 90 分钟编辑精选