Rust 对象存储捐赠

导航至

今天,我们很高兴正式宣布 InfluxData 已将 通用对象存储实现捐赠给 Apache Arrow 项目

使用此 crate,通过简单的运行时配置更改,相同的代码可以轻松地与 AWS S3、Azure Blob Storage、Google Cloud Storage、本地文件、内存等进行交互。

您可以在 crates.io 上找到 最新版本

我们期望这将加速 Rust 生态系统内的创新步伐。无论您是构建云无关的服务来处理用户上传的视频、图像和文档、高性能分析系统,还是其他需要访问商品对象存储的系统,此 crate 都可以帮助您,我们迫不及待地想看看人们用它构建什么。

为什么您需要对象存储 crate?

除了为许多基于云的服务提供批量数据存储外,我们认为分析系统的未来尤其涉及查询存储在对象存储上的数据。

对象存储是通用术语,可以粗略地描述为“云中的无限 FTP 服务器”,它按需提供几乎无限的、高度可用且持久的键值存储。与虚拟机和块存储一起,对象存储是所有现代云服务提供商提供的关键商品服务之一。示例包括 S3Microsoft Azure Blob StorageGoogle Cloud StorageMinIOCeph Object GatewayHDFS 等。

为了实现这种近乎无限的扩展,对象存储提供了传统文件系统(如 NTFSext4)功能的子集。具体来说,它们使用“键”标识对象,并将任意字节存储为值

Rust Object Store Donation - Figure 1 图 1: 对象存储存储由字符串键标识的任意字节。

与文件系统不同,对象存储通常缺少目录的显式概念,最佳实践是对键使用受限的 ASCII 子集。相反,类路径遍历是通过使用带有前缀的 LIST 操作实现的,非法字符序列会被百分比编码。

Rust Object Store Donation - Figure 2

图 2: 对象存储可以 LIST 具有指定前缀的对象,这可以用于将文件分组在一起。在此示例中,请求前缀为“/pictures/”的对象会返回所有 .jpg 对象,而请求前缀为“/parquet/”的对象会返回所有 .parquet 对象。

跨对象存储实现和本地文件系统一致地列出和遍历编码在对象键中的准目录结构是一个常见的挫折来源,因为不仅文件系统的行为与对象存储非常不同,而且每个对象存储实现都有自己的怪癖。

拥有一个专注于、易于使用、高性能、异步对象存储库(以惯用的 Rust 编写),使您可以摆脱对这些细节的担忧,而专注于系统的逻辑。底层实现从应用程序代码中抽象出来,并且可以在运行时轻松选择,从而允许相同的二进制文件在多个云中运行。

这种灵活性还有助于本地开发,因为它允许针对本地文件系统甚至内存存储进行测试,而无需任何额外的二进制文件(如 MinIO),并允许使用熟悉的工具,如 ls、cat 或您选择的文件浏览器。

如何使用它?

这是一个简单的示例,用于查找远程对象存储上的文件中零的数量

let object_store: Arc<dyn ObjectStore> = get_object_store();

    // list all objects in the "parquet" prefix (aka directory)                                                                                                                     
    let path: Path = "parquet".try_into().unwrap();
    let list_stream = object_store
        .list(Some(&path))
        .await
        .expect("Error listing files");

    // List all files in the store                                                                                                                                                  
    list_stream
        .map(|meta| async {
            let meta = meta.expect("Error listing");

            // fetch the bytes from object store                                                                                                                                    
            let stream = object_store
                .get(&meta.location)
                .await
                .unwrap()
                .into_stream();

            // Count the zeros                                                                                                                                                      
            let num_zeros = stream
                .map(|bytes| {
                    let bytes = bytes.unwrap();
                    bytes.iter().filter(|b| **b == 0).count()
                })
                .collect::<Vec<usize>>()
                .await
                .into_iter()
                .sum::<usize>();

            (meta.location.to_string(), num_zeros)
        })
        .collect::<FuturesOrdered<_>>()
        .await
        .collect::<Vec<_>>()
        .await
        .into_iter()
        .for_each(|i| println!("{} has {} zeros", i.0, i.1));
}

它会打印出类似这样的内容

test_fixtures/parquet/1.parquet has 174 zeros
test_fixtures/parquet/2.parquet has 53 zeros

编写的代码以分页方式列出文件,并并行获取其内容。如果文件数以千计,这可能不是很好。但是,我们可以轻松利用 Rust 流并更改

.collect::<FuturesOrdered<_>>()

.buffered(10)

现在这将限制程序最多并行发出 10 个 GET 请求。

object_store crate 最酷的部分是,相同的代码适用于所有不同的对象存储,唯一改变的是 get_object_store 的定义

从 S3 读取

fn get_object_store() -> Arc<dyn ObjectStore> {
    let s3 = AmazonS3Builder::new()
        .with_access_key_id(ACCESS_KEY_ID)
        .with_secret_access_key(SECRET_KEY)
        .with_region(REGION)
        .with_bucket_name(BUCKET_NAME)
        .build()
        .expect("error creating s3");

    Arc::new(s3)
}

从 Azure 读取

fn get_object_store() -> Arc<dyn ObjectStore> {
    let azure = MicrosoftAzureBuilder::new()
        .with_account(STORAGE_ACCOUNT)
        .with_access_key(ACCESS_KEY)
        .with_container_name(BUCKET_NAME)
        .build()
        .expect("error creating azure");

    Arc::new(azure)
}

从 GCP 读取

fn get_object_store() -> Arc<dyn ObjectStore> {
    let gcs = GoogleCloudStorageBuilder::new()
        .with_service_account_path(PATH_TO_SERVICE_ACCOUNT_JSON)
        .with_bucket_name(BUCKET_NAME)
        .build()
        .expect("error creating gcs");
    Arc::new(gcs)
}

从本地文件系统读取

fn get_object_store() -> Arc<dyn ObjectStore> {
    let local_fs =
        LocalFileSystem::new_with_prefix(PREFIX)
          .expect("Error creating local file system");
    Arc::new(local_fs)
}

重申一下,主要的好处是您不必为不同的对象存储集成不同的抽象——客户端代码始终相同,并且在底层使用适当的优化实现。

object_store crate 也是可扩展的,这允许插入其他对象存储系统,同时仍然保留从本地文件系统读取文件的能力,以利用某些系统提供的优化文件访问——请参阅 GetFileResult

可以在 rust_object_store_demo 存储库中找到更完整和可用的示例。

为什么要捐赠给 Apache

Rust 的梦想是拥有 Python 或 Ruby 的开发效率,以及 C/C++ 的速度和内存效率。实现这一梦想的部分原因是确保它能够轻松地与更广泛的技术生态系统集成,而在现代分析系统中,这越来越意味着对象存储上的数据。

因此,重要的是使 Rust 程序能够轻松且高效地读取和写入对象存储(AWS、S3、GCP)的数据。有一些单独的 crate 实现了云提供商特定的 SDK,例如 rusoto_s3Azure_storage;但是,通常需要通过相同的接口访问最常见的功能集,以加速跨云分析系统的开发。此 crate 明确地不打算取代功能齐全的云 SDK,而是提供一个一致的对象存储抽象,该抽象可在许多不同的底层实现之间移植。

当我们着手开发 influxdb_iox 时,我们正好有这种需求。InfluxDB 和 InfluxData Cloud 在 AWS、GCP、Azure 和本地运行,我们也需要 IOx 这样做。我们找不到适合我们需求的现有库,因此 InfluxData IOx 团队在我们的项目中开发了一个。

这项工作最初由 Rust 生态系统传奇人物 Carol (Nichols II Goulding) @carols10centsRust Book 的主要作者)实施,并由 Marco NeumannRaphael Taylor-Davies 大力扩展,因为我们精心设计了它与 DataFusion 的集成。

IOx 使用 Rust、Apache Arrow、Apache Parquet 和 DataFusion 项目,我们也为这些项目做出了重要贡献,并且 IOx 的对象存储交互通过 DataFusion 高效地进行变得越来越重要。当我们研究替代方案时,我们遇到了需要与对象存储更深入集成的情况。

我们希望这次捐赠能够进一步加速 Rust 中高质量分析系统的创建,并且迫不及待地想看看社区用它构建什么!我们特别希望与 Apache Arrow 的对齐将允许与库进行优雅的集成体验,这些库可以轻松高效地从本地或远程对象存储中读取与 Arrow 兼容的文件,例如 parquet、CSV 和换行符分隔的 JSON。对于希望使用 SQL 或其他更高级别查询引擎功能的应用程序,请查看 Apache Arrow DataFusion

您可以在 此 GitHub 问题此问题 中查看有关捐赠及其理由的更多信息。

下一步是什么

在短期内,我们计划更好地与 parquet crate 集成。特别是 异步 parquet 读取器 的开发明确考虑了通用 object_store crate。它目前支持投影和行组级别谓词下推,以最大程度地减少从对象存储中获取的数据,并且对页面和行级别谓词下推的支持可能会在计划于 2022 年 8 月 22 日发布的下一个版本中实现。

我们还期望继续改进与 Apache Arrow DataFusion 的集成,确保它为查询对象存储中的数据提供一流的支持,有效地将 IO 与 CPU 密集型工作解耦,并最有效地利用现代多核处理器。

最后,正在努力摆脱对大型 SDK(如 rusoto 和 Azure SDK for Rust)的依赖。虽然它们为我们提供了良好的服务,但摆脱它们将显着减少依赖负担,简化实现,并进一步提高各种实现之间的一致性。

加入社区

我们认为蓬勃发展的社区会推动所有人前进。我们鼓励您查看 crate,并伸出援手!在您的项目中试用它,并告诉我们效果如何,或者在 github 此处 找到我们。这里列出了一些适合新手的良好开放项目 此处

致谢

感谢 Raphael Taylor-DaviesPaul DixNga TranMarco Neumann,他们审阅了本文档的早期版本并贡献了许多改进。