用 50 行代码编写数据库

导航至

编写您自己的数据库

编写数据库并不是大多数人一时兴起就会做的事情。构建真正实用且高性能的东西需要考虑大量不同的组件和事项。

但是,如果它没有那么困难呢?如果您可以使用一组像积木一样的工具,以模块化的方式互换它们,来编写一些可以简单工作的东西呢?在数据库方面,可能会有很多“重复造轮子”的情况,因此您在基本功能上花费的时间越少,您就可以在真正需要的功能上花费更多的时间。

这就是开源以及为开源项目做出贡献的开发者社区发挥作用的地方。我认为,看看我是否可以使用开源工具并尽可能少地编写代码来构建数据库会很有趣。

FDAP 堆栈

我的想法的基础是我同事 Andrew Lamb 撰写的一篇关于 FDAP 堆栈Apache FlightArrowDataFusionParquet)的精彩文章。在他从事这项工作的同时,我也在单独试验 Flight Python 库。在许多情况下,这些库代表了用户与 InfluxDB 3.0 交互的最佳方式。

经过一些学习,我将使用 Python 实现 OLAP 数据库的最简单示例放入了几行代码中。这证明了 Apache Arrow 项目的强大功能和简洁性。结果真正突出了 FDAP 在创建特定领域分析数据库方面的强大功能。

之所以有可能,是因为 Apache Arrow 项目维护了几乎所有内容的 Python 绑定和库。因此,从 Python 开始,我可以访问上游维护的底层高性能代码。DataFusion 就是一个特别值得注意的例子,它是一个用 Rust 编写的库,用于针对 Arrow 表执行 SQL 查询。您将在下面看到,我编写了大约八行代码,这使我能够加载 Parquet 文件并以功能完整且高性能的方式针对其内容执行 SQL 查询。

这是一个微不足道的示例,代码效率低下,但这证明了健康的上游项目的力量,以及上游工作对社区的好处。

FlightServer

有一个名为 FlightServerBase 的上游类,它实现了服务标准 Flight gRPC 端点的所有基本功能。Apache Flight 是一种协议,允许用户向数据库发送命令,并接收返回的 Arrow。使用 FlightServer 有一些重要的优势:数据以 Arrow 形式返回到客户端。我们可以有效地将该 Arrow 数据转换为 Pandas 或 Polars。这意味着我们可以支持高性能分析。

大多数流行的编程语言都有 Flight 客户端库。因此,用户不需要使用您自己的定制工具,您也不需要支持这些工具。

要实现如此简单的 Flight 服务器,您只需要实现 FlightServerBase 上的无数方法中的两个

  1. do_put():此函数接收 FlightDescriptor 对象和 MetadataRecordBatchReader。虽然这是一个很长的类名,但它实际上只是一个允许服务器读取用户传入的数据的对象。它还支持批量处理等好功能。
  2. do_get():此函数处理 Flight Ticket,执行查询并返回结果。您将看到,为此添加 SQL 支持非常容易。

do_put()

do_put 函数接收 FlightDescriptor 和 Reader,并使用这两个对象将数据添加到现有的 Parquet 文件中。

虽然此写入实现很简单 – 它性能不高并且缺乏任何类型的模式验证 – 但它的简单性也具有指导意义。

为了响应写入,代码从 FlightDescriptor 中提取表名,并将从 Reader 传递的数据加载到内存中。它还计算 Parquet 文件的文件路径,数据将持久化到该文件中。

table_name = descriptor.path[0].decode('utf-8')
data_table = reader.read_all()
file_path = f"{table_name}.parquet"

如果已经存在具有该表名的数据,则代码会将现有数据加载到表中,然后连接新表和旧表。

if os.path.exists(file_path):
    try:
        existing_table = pq.read_table(file_path)
        data_table = pa.concat_tables([data_table, existing_table])
    except Exception as e:
        print(e)

接下来,它将表写入 Parquet 文件

try:
pq.write_table(data_table, file_path)
except Exception as e:
print(e)

以下是一些客户端代码,用于创建一些数据,将其转换为 Arrow 表,创建 FlightClient,并调用 do_put() 来写入数据

import json
import io
from pyarrow.flight import FlightDescriptor, FlightClient
import pyarrow as pa

data = [{"col1":3, "col2":"one"},
{"col1":3, "col2":"two"}]

table = pa.Table.from_pylist(data)

descriptor = FlightDescriptor.for_path("mytable")
client = FlightClient("grpc://localhost:8081")

writer, _ = client.do_put(descriptor, table.schema)
writer.write_table(table)
print(f"wrote: {table}")
writer.close()

请注意,这些都是 Apache Arrow 社区中的上游对象。我们不需要任何自定义库。如果您运行示例 write.py 文件,您将看到它可以正常工作。

% python3 write.py                
wrote: pyarrow.Table
col1: int64
col2: string
----
col1: [[3,3]]
col2: [["one","two"]]

do_get()

此时,您可能会想:“这很酷,但将文件写入磁盘并不难。” 这可能是真的,但是编写可以查询该数据的 SQL 实现呢?您是否编写过解析器、规划器以及使 SQL 实现工作所需的一切?这并不容易。

在本节中,您可以看到如何 – 只需几行代码 – 即可使用 DataFusion 添加功能齐全且快速的查询体验。

do_get() 函数接收一个 ticket 对象,该对象具有执行查询所需的信息。在本例中,这包括表名和 SQL 查询本身。

ticket_obj = json.loads(ticket.ticket.decode())
sql_query = ticket_obj["sql"]
table_name = ticket_obj["table"]

然后,它创建一个 DataFusion SessionContext 并将 Parquet 文件读取到其中。

ctx = SessionContext()
ctx.register_parquet(table_name, f"{table_name}.parquet")

最后,它执行查询并返回结果。

result = ctx.sql(sql_query)
table = result.to_arrow_table()
return flight.RecordBatchStream(table)

以下是一些客户端代码,它使用 pyarrow 库执行查询并将结果输出为 Pandas DataFrame

from pyarrow.flight import Ticket, FlightClient
import json

client = FlightClient("grpc://localhost:8081")
ticket_bytes = json.dumps({'sql':'select * from mytable', 'table':'mytable'})
ticket = Ticket(ticket_bytes)
reader = client.do_get(ticket)
print(reader.read_all().to_pandas())
% python3 read.py 
  col1  col2
0  one     1
1  two     2
2  one     1
3  two     2

重要的是要注意,仅仅八行代码就可以让您添加 DataFusion 支持。这样做,您将获得一个非常快速且完整的 SQL 引擎。例如,您可以在其 文档中查看 DataFusion 支持的所有 SQL 语句。

以下是一个稍作修改的 SQL 查询,您也可以尝试一下

'select mean(col2) as mean from mytable'

% python3 read.py
   mean
0   1.5

我们重视开源

在 InfluxData,我们公司的核心价值观之一是“我们重视开源”。我们以不同的方式展示这一价值观。例如,我们对 Telegraf 项目的支持,以及我们始终提供 InfluxDB 的宽松许可开源版本的事实。随着 InfluxDB 3.0 的推出,我们重视开源的方式得到了扩展和深化。

我相信您可以想到许多公司将自己标榜为某些开源技术的“幕后公司”。这种模式通常需要公司支持一个免费使用、许可宽松且功能有限的开源版本。该公司还提供一个或多个功能更强大的商业版本软件。在这些情况下,开源版本主要充当公司商业产品的潜在客户生成工具。

InfluxDB 背后的公司 InfluxData 也不例外。对于 InfluxDB 的版本 1 和版本 2,InfluxData 也发布了付费商业版本。

诚然,对于任何希望构建自己的数据库的人来说,InfluxDB 源代码可能很有趣,但该代码的实用性也可能有限。当然,它对于修复错误和扩展代码很有用。但是,如果您想创建一种新型数据库,例如位置数据库,那么我们的时间序列数据库的代码可能不是一个有用的起点。换句话说,数据库的核心代码和组件本质上是不可重用的。

这就是 InfluxDB 3.0 真正发生变化的地方。是的,InfluxData 仍然是 InfluxDB 背后的公司。是的,开源 InfluxDB 3.0 已经在开发中。是的,我们提供商业版本

但是,我们现在编写的大量代码都进入了上游 Apache Arrow 项目。Apache Arrow 项目,特别是 FDAP 堆栈,旨在成为任何想要创建新型数据库的人的起点。这意味着我们对 Apache Arrow 项目的贡献有利于寻求在数据库领域进行自身创新的社区成员。

亲自尝试

如果您想试用代码和客户端示例,可以在 此 github 存储库中找到它们。

我希望这个基本的数据库示例能够证明“我们重视开源”理念的力量。我们不必将我们的贡献限制在我们自己的项目中来支持开源。希望我们对上游 Apache Arrow 项目的贡献和支持反映了这一承诺。我们也希望这些高质量和高性能工具的可用性能够激励其他人在数据库领域的特定领域进行创新。