用50行代码编写数据库
作者:Rick Spencer / 开发者
2023年11月08日
导航到
编写自己的数据库
编写数据库并不是大多数人一时冲动要做的事情。要构建真正功能强大且性能良好的东西,有许多不同的组件和需要考虑的事项。
但如果这并不那么困难呢?如果可以使用像积木一样的一套工具,以模块化的方式交换它们,来编写一些简单就能工作的事情呢?在数据库方面,可能会有很多“重新发明轮子”的情况,所以你花在基础上的时间越少,你就能花更多的时间在你的真正需要的功能上。
这就是开源和贡献开源项目的开发者社区发挥作用的地方。我认为尝试使用开源工具尽可能少地编写代码来构建数据库将是一个有趣的练习。
FDAP堆栈
支撑我对这个想法的,是一篇我同事Andrew Lamb写的关于FDAP堆栈(Apache Flight、Arrow、DataFusion和Parquet)的优秀文章(FDAP堆栈)(Apache Flight、Arrow、DataFusion、和Parquet)。在撰写这篇文章的同时,我正在单独尝试使用Flight Python库。在许多情况下,这些库代表了用户与InfluxDB 3.0交互的最佳方式。
在学习了一段时间后,我将使用Python实现OLAP数据库的最简单示例缩减为几行代码。这展示了Apache Arrow项目的强大和简洁。结果真正突出了FDAP在创建特定领域的分析数据库方面的能力。
这是可能的,因为Apache Arrow项目维护了几乎所有内容的Python绑定和库。因此,从Python开始,我可以访问上游维护的高性能代码。这在DataFusion的情况下尤为突出,这是一个用于执行对Arrow表的SQL查询的Rust编写的库。正如您下面将看到的,我写了大约八行代码,这使我能够以功能完整和高性能的方式加载Parquet文件并对其内容执行SQL查询。
这是一个简单的示例,代码效率不高,但它证明了健康上游项目的力量,以及参与上游工作的社区利益。
FlightServer
有一个名为FlightServerBase的上游类,它实现了提供标准Flight gRPC端点的所有基本功能。Apache Flight是一种允许用户向数据库发送命令并接收回Arrow的协议。使用FlightServer有一些重要的优势:数据作为Arrow返回给客户端。我们可以高效地将Arrow数据转换为Pandas或Polars。这意味着我们可以支持高性能分析。
大多数流行的编程语言都有Flight客户端库。因此,用户不需要使用自己的定制工具,也不需要支持这些工具。
要实现这样一个简单的Flight服务器,您只需要在FlightServerBase上实现两种方法中的一种
- do_put():这个函数接收一个FlightDescriptor对象和一个MetadataRecordBatchReader。虽然类名听起来很长,但它实际上是一个允许服务器读取用户传入的数据的对象。它还支持像批处理这样的好东西。
- do_get():这是处理Flight票据、执行查询并返回结果的函数。正如您将看到的,为这个添加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://127.0.0.1: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()
函数接收一个票据对象,其中包含执行查询所需的信息。在这种情况下,这包括表名和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://127.0.0.1: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的推出,我们对开源的重视程度得到了扩展和深化。
我相信您能想到许多自称是某些开源技术“背后的公司”的公司。这种模式通常涉及公司支持一个免费使用、许可宽松且功能有限的开放源代码版本。公司还提供一种或多种功能更丰富的商业软件版本。在这些情况下,开源版本在很大程度上充当公司商业产品的潜在客户生成工具。
InfluxData,InfluxDB背后的公司,也不例外。对于InfluxDB的1和2版本,InfluxData也发布了付费的商业版本。
诚然,InfluxDB的源代码可能对任何希望构建自己的数据库的人来说都很有趣,但该代码也可能具有有限的实用性。当然,修复错误和扩展代码是有用的。但如果您想创建一种新的数据库类型,比如位置数据库,那么我们时间序列数据库的代码可能不是一个有用的起点。换句话说,数据库的核心代码和组件并不是固有的可重用的。
这正是InfluxDB 3.0真正发生变化的地方。是的,InfluxData仍然是InfluxDB背后的公司。是的,开源的InfluxDB 3.0已经在进行中。是的,我们提供商业版本。
然而,我们现在编写的相当一部分代码都投入到Apache Arrow的上游项目中。Apache Arrow项目,尤其是FDAP栈,是为了任何想要创建新类型数据库的人提供一个起点。这意味着我们对Apache Arrow项目的贡献有助于那些希望在自己的数据库领域进行创新的社区成员。
亲自尝试
如果您想玩代码和客户端示例,您可以在这个GitHub仓库中找到它们。
我希望这个基本的数据库示例展示了“我们重视开源”理念的力量。我们不必将我们的贡献局限于自己的项目来支持开源。希望我们对Apache Arrow项目的贡献和支持反映了这种承诺。我们也希望这些高质量和性能出色的工具的可用性能够激发他人围绕数据库空间的特定领域进行创新。