规模上的简单性
Frank Wessels 是 Sneller 的创始人,之前曾在 MinIO 工作。
介绍
在 MinIO 期间,也许我学到的最重要的一课是关于简单性。如果您想实现(真正的)可扩展性,简单性是最重要的。这从前到后渗透到 MinIO 的架构和软件设计中,并推动了我接下来将要描述的重要 Sneller 决策等。
首先,一个关键决定是避免为元数据使用任何(外部)存储系统或数据库。虽然乍一看可能很吸引人以“快速入门”,但这会增加大量的复杂性,这在 TB 或 PB 级甚至更高的规模上变得更具挑战性。想象一下,让“你的”软件完成它的工作,但让另一个依赖系统成为瓶颈和/或关键依赖。不管你喜欢与否,你最终都会成为这个系统的专家(甚至可能比你自己的系统和软件更重要)。
其次,坚持“单一二进制”方法极大地简化了设置和安装(并且几乎完全有效地避免了安装)。只需下载一个可执行文件就可以了,同样的可执行文件可以在从边缘设备(甚至 Raspberry Pi)到提供 100 GB/秒网络吞吐量的最大服务器安装的所有设备上运行。此外,没有要调整的参数,并且可以在 MinIO 允许的不同操作模式(文件系统模式与纠删码模式)中使用相同的二进制文件。
第三,对于 MinIO 多服务器模式,我们决定构建自己的分布式锁定机制。虽然那里有各种共识协议机制,但我们评估了所有这些机制,但最终决定这些都不适合 MinIO 架构,并且对于我们正在寻找的“简单”机制来说基本上是过大的。此外,使用这些系统中的任何一个都会违反我们的单一二进制方法,并使安装过程非常复杂,并可能成为维护负担。
最后但并非最不重要的一点是,通过严格遵守“纯”Golang 方法意味着 MinIO 可以保持其开发过程的灵活和敏捷(直到今天,您可以 `git clone` minio 并在_seconds_ 内构建它)。但为了做到这一点,我们必须避免对诸如 `cgo` 之类的工具的任何依赖,以包含高性能算法。鉴于擦除编码和位腐烂检测是任何对象存储系统的关键算法,我们花费了大量精力在 Golang 原生汇编中针对 Intel、ARM 和 PowerPC 等各种架构优化这些算法(参见 [此处]( https://blog.min.io/intel_vs_gravitron/)如果您想了解更多信息)。
斯内勒的指导原则
Sneller 是一个专为 JSON 设计的高性能 SQL 引擎。借助 Sneller,您可以对存储在 S3 上的深度嵌套半结构化 JSON 的 TB 级大型数据集运行交互式 SQL。目标用例是事件数据管道,例如安全性、可观察性、操作、用户事件和传感器/物联网。我们将稍微谈谈 Sneller 和我们的方法,然后向您展示如何在 MinIO 上放手。
Sneller 的性能源自 SIMD 的广泛使用,特别是其 250 多个核心原语中的 AVX-512 汇编。主引擎能够为每个内核并行处理许多通道,以实现非常高的处理吞吐量。

下面我想描述一下 Sneller 架构的指导原则:
你不应该使用本地磁盘
从一开始,我们就决定不使用任何形式的本地磁盘存储,例如 NVMe 或 SSD。正如您所了解的,根据过去的经验,我们确切地知道以正确的方式使用对象存储的速度有多快。我们的云只是一个内核池、RAM 和快速网络,仅此而已。
您应拥有您的数据(或者:我们将_不_拥有您的数据)
鉴于我们对所有数据和状态都完全依赖对象存储,我们决定所有数据都应由客户控制。也就是说,我们不会将潜在的 PB 级数据导入或摄取到“我们的”云中,而是使用客户存储桶中的数据。对我们的客户来说,最大的优势是他们可以完全控制自己的数据,这正是应该的方式。当然,这也意味着我们不能通过故意使数据导出变得困难和昂贵(规模越大越重要)来“挟持”我们的客户。
你不需要计划
S3 API 的最佳方面之一是“免费”调用的_absense_。不仅 S3 或对象存储通常具有令人难以置信的耐用性和弹性(如果您构建自己的复制等,则很难实现),而且您不再需要担心运行“空间不足”的事实令人难以置信的解放。
实际上,在构建架构时,很难准确预测使用模式以及这将如何影响数据的生成(=存储)以及数据的处理。将所有状态保留在对象存储上真正将存储与计算分离,并允许您根据实际使用情况而不是某些固定或预定义的比率(这可能是也可能不是您需要的,而且通常不是您需要的)完全独立地进行扩展。 .
你不需要架构
现代数据堆栈已通过 REST API 聚合到 JSON,并且 JSON 已成为网络事实上的“通用语”。越来越多的 API 允许自定义负载,这些负载会因记录而异,甚至(意外地)更改相同字段或列名称的“类型”。这可能会导致诸如“映射爆炸”或“模式漂移”之类的现象,这些现象很难大规模检测(当您检测到它时,您很可能已经丢失了大量数据)。
从根本上说,使用 JSON 真正解决这个问题的唯一方法是 100% 读取模式。也就是说,能够在查询数据的同时处理多种类型。这就是为什么 Sneller 支持以 `WHERE foo.bar = 1 OR foo.bar = "one" OR foo.bar = TRUE` 的形式进行查询,并在遍历数据时根据字段类型选择正确的比较。
你不应该索引
传统上,数据库/查询系统依赖于各种索引来加速查询。虽然这些方法确实有效,但在规模上它们开始导致一些问题。首先,它通常会导致正在存储的数据在一定程度上放大(当然,这不是对象存储的主要问题),但更重要的是会导致_您要索引哪些_字段的问题?
特别是对于高基数数据集(例如,GitHub 存档数据有 100 个字段,其中大部分是嵌套的),“全部”答案变得非常昂贵,并且会导致摄取速度极慢。因此,作为表的“设计者”,您很可能被迫做出预先选择,而这些选择很可能不是您的表的用户可能想要使用或感兴趣的那些字段。
因此,除了非常稀疏的基于时间戳的索引(每 100 MB 数据一个最小值和最大值)之外,Sneller 不进行任何索引,而是依靠其 AVX-512 SIMD 原语的功能和速度来执行其查询。
斯内勒*少
按照流行的说法“少即是多”,Sneller 架构(遵循我在 MinIO 学到的很多知识)可以通过以下属性和优势来表征:
无服务器:停止考虑为您全天候 24x7 运行的单个服务器。根据您查询的数据量,开始考虑永远在线和现收现付的模型。让 Sneller 根据所有租户(和幕后)的总体查询需求来处理动态的向上或向下扩展。
无状态:通过完全依赖于任何持久数据的对象存储,就不可能由于任何查询服务器错误或异常而损坏或丢失数据。在查询节点崩溃的情况下,只需启动另一个节点并让它从 S3 重新加载它的部分数据并继续不间断。
无模式:无需预先定义模式,Sneller 将无缝处理任何模式漂移。您不再需要 ETL 或 ELT 来将 JSON 转换或重塑为替代格式。
无分支:Sneller 的性能来自其_无分支_SIMD 代码,该代码允许在单个 CPU 内核上并行处理多个通道。
无索引:通过避免索引,简化和加速摄取/同步过程,并以经济高效的方式实现大规模。
MinIO 上的 Sneller
如果您想自己尝试一下,启动一堆“Sneller on MinIO”非常容易,它基本上会为您提供大规模处理 JSON 的所有工具。基本上只使用 curl 就可以将 SQL 查询提交给 REST API,如下所示:
```
$ curl -G -H "Authorization: Bearer $SNELLER_TOKEN" --data-urlencode "database=gha" \
--data-urlencode 'json' --data-urlencode 'query=SELECT type, COUNT(*) FROM gharchive GROUP BY type ORDER BY COUNT(*) DESC' \
'http://localhost:9180/executeQuery'
{“类型”:“PushEvent”,“计数”:1303922}
{“类型”:“CreateEvent”,“计数”:261401}
...
{“类型”:“GollumEvent”,“计数”:4035}
{“类型”:“MemberEvent”,“计数”:2644}
```
使用Docker,您可以在自己的笔记本电脑上执行此操作,或者如果您对开发或测试环境更感兴趣,则可以尝试Kubernetes集成。
开源
我们已经开源了 Sneller 技术的核心(包括所有 AVX-512 代码)。
如果您想了解更多信息,请前往GitHub 上的 Sneller 存储库(如果您喜欢您所看到的内容,欢迎您为其加注星标。)
结论
对象存储在管理云中最大数量的数据方面非常强大,并且已将自己确立为这样做的事实标准。
将其与高性能无服务器查询引擎相结合,可以实现存储和计算的真正分离,并为用户提供最大的灵活性,使其完全独立地扩展到各自的最佳状态。