机器学习架构可能是现代系统中最复杂、最昂贵和最困难的领域。技术的数量和所需的硬件数量都在争夺减少员工人数、托管和延迟预算。不幸的是,随着围绕数据仓库、微服务和 NoSQL 数据库的最先进架构的使用越来越多,该行业的趋势只会变得更糟。
PostgresML 是对不断增长的复杂性的一种更简单的替代方案。在这篇文章中,我们探讨了更优雅架构的一些额外性能优势,并发现 PostgresML在本地测试中比传统 Python 微服务的性能高出8倍,在 AWS EC2 上高出40 倍。
候选架构
为了考虑 Python 微服务的所有可能优势,我们的第一个基准测试是在同一台机器上运行 Python 和 Redis。我们的目标是避免任何额外的网络延迟,这使其与 PostgresML 的基础更加平衡。我们的第二个测试在 AWS EC2 上进行,Redis 和 Gunicorn 由网络分隔;这个基准被证明是相对具有破坏性的。
Github上提供了两个基准测试的完整源代码。
PostgresML
PostgresML 架构由以下部分组成:
- 具有 PostgresML v2.0 的 PostgreSQL 服务器
- pgbench SQL 客户端
Python
Python架构由以下部分组成:
- 接受和返回 JSON 的 Flask/Gunicorn 服务器
- 包含训练数据的 CSV 文件
- 带有推理数据集的 Redis 特征存储,使用 JSON 序列化
- ab HTTP 客户端
机器学习
两种架构都托管相同的 XGBoost 模型,针对相同的数据集运行预测。有关详细信息,请参阅方法。
结果
吞吐量
吞吐量定义为架构每秒可以服务的 XGBoost 预测数量。在这个基准测试中,PostgresML 在同一台机器上运行的性能比 Python 和 Redis 高出8 倍。
在 Python 中,大部分瓶颈来自必须获取和反序列化 Redis 数据。由于特征是在外部存储的,它们需要通过 Python 传递到 XGBoost 中。XGBoost 本身是用 C++ 编写的,它的 Python 库只提供了一个方便的接口。XGBoost 的预测必须再次通过 Python,序列化为 JSON,并通过 HTTP 发送到客户端。
这几乎是您可以为推理微服务做的最低限度的工作。
另一方面,PostgresML 配置数据和计算。它从 Postgres 表中获取数据,该表已经采用标准浮点格式,Rust 推理层通过指针将其转发给 XGBoost。
当基准测试达到 20 个客户端时发生了一件有趣的事情:PostgresML 吞吐量开始迅速下降。这可能会让一些人感到惊讶,但对于 Postgres 爱好者来说,这是一个已知问题:Postgres 并不擅长处理比 CPU 线程更多的并发活动连接。为了缓解这种情况,我们在数据库前面引入了 PgBouncer(一个 Postgres 代理和池化器),并且吞吐量增加了备份,并在我们达到 100 个客户端时继续保持不变。
值得注意的是,基准测试机器只有 16 个可用的 CPU 线程(8 核)。如果有更多的内核可用,瓶颈只会出现在更多的客户端上。对 Postgres 服务器的一般建议是每个可用 CPU 内核打开大约 2 个连接,尽管新版本的 PostgreSQL 已经逐渐消除了这个限制。
为什么吞吐量很重要
吞吐量让您事半功倍。如果您能够使用单台机器每秒处理 30,000 个查询,但现在只使用 1,000 个,那么您不太可能很快需要升级。另一方面,如果系统只能处理 5,000 个请求,那么您在不久的将来就会进行昂贵且可能压力很大的升级。
Latency
延迟定义为返回单个 XGBoost 预测所需的时间。由于大多数系统的资源有限,吞吐量直接影响延迟(反之亦然)。如果有许多活动请求,则在队列中等待的客户端需要更长的时间才能得到服务,并且整体系统延迟会增加。
在这个基准测试中,PostgresML 的性能也比 Python高 8 倍。您会注意到在 20 个客户端上发生了相同的问题,并且使用 PgBouncer 进行的相同缓解降低了其影响。同时,Python 的延迟继续大幅增加。
在描述架构的性能时,延迟是一个很好的指标。换句话说,如果我要使用这项服务,我最多只能在这么长的时间内得到一个预测,而不管有多少其他客户正在使用它。
为什么延迟很重要
延迟在机器学习服务中很重要,因为它们通常作为主应用程序的补充运行,有时必须在同一个 HTTP 请求期间多次访问。
让我们以电子商务网站为例。一个典型的店面想要同时展示许多个性化模型。此类模型的示例可能包括针对重复购买的“再次购买”建议(二元分类),或“您所在地区的热门商品”(购买历史的地理聚类)或“像您一样购买此商品的客户”(最近邻模型)。
所有这些模型都很重要,因为随着时间的推移,它们已被证明在推动购买方面非常成功。如果推理延迟很高,模型开始争夺非常昂贵的房地产、首页和结账,企业不得不放弃其中的一些,或者更有可能遭受页面加载缓慢的影响。当他们尝试购买杂货或晚餐时,没有人喜欢缓慢的应用程序。
内存利用率
Python 以使用比更优化的语言更多的内存而闻名,在这种情况下,它使用的内存是PostgresML 的7 倍。
PostgresML 是 Postgres 扩展,它与数据库服务器共享 RAM。Postgres 在只获取和分配它需要的内存方面非常有效:它重用shared_buffers和 OS 页面缓存来存储行以进行推理,并且只需要很少甚至不需要内存分配来服务查询。
同时,Python 必须为它从 Redis 接收到的每个特性以及它返回的每个 HTTP 响应分配内存。该基准没有测量 Redis 内存利用率,这是运行传统机器学习微服务的额外成本,而且通常是巨大的成本。
训练
由于 Python 经常使用 Pandas 来加载和预处理数据,因此它尤其需要更多的内存。甚至在将数据传递到 XGBoost 之前,我们已经达到 8GB RSS(驻留集大小);在实际拟合期间,内存利用率几乎达到 12GB。该测试是 Python 的另一个最佳案例场景,因为数据已经过预处理,并且只是传递给算法。
同时,PostresML 喜欢与 Postgres 服务器共享 RAM,并且只分配 XGBoost 所需的内存。数据集的大小很大,但我们设法仅使用 5GB 的 RAM 训练了相同的模型。因此,PostgresML 允许在至少两倍于 Python 的数据集上训练模型,同时使用相同的硬件。
为什么内存利用率很重要
这是另一个事半功倍的例子。FAANG 和研究型大学之外的大多数机器学习算法都要求数据集适合单台机器的内存。分布式训练不是我们想要的,而且从简单的线性回归中仍然可以提取很多价值。
使用更少的 RAM 可以在更大、更完整的数据集上训练更大更好的模型。如果您碰巧遭受大量机器学习计算费用的困扰,那么在您的财政年度结束时使用更少的 RAM 可能会令人惊喜。
UltraJSON/MessagePack/Serializer X 呢?
我们花了很多时间讨论序列化,因此查看该领域的先前工作是有意义的。
JSON 是对用户最友好的格式,但肯定不是最快的。例如,MessagePack 和 Ultra JSON 有时在读取和存储二进制信息方面更快、更高效。那么,在这个基准测试中使用它们会更好,而不是 Python 的内置json模块吗?
答案是:不是。
(反)序列化的时间很重要,但最终需要(反)序列化是瓶颈。从远程系统(例如 Redis 之类的功能存储)中取出数据,通过网络套接字发送,将其解析为 Python 对象(需要分配内存),然后再次将其转换为 XGBoost 的二进制类型,导致系统中不必要的延迟。
PostgresML从 Postgres 中复制一份功能的内存副本。没有网络,没有(反)序列化,没有不必要的延迟。
那么现实世界呢?
通过 localhost 进行测试很方便,但它不是最现实的基准。在生产部署中,客户端和服务器位于不同的机器上,而在 Python + Redis 架构的情况下,功能存储是另一个网络跃点。
为了证明这一点,我们启动了 3 个 EC2 实例并再次运行基准测试。这一次,PostgresML 的性能比 Python 和 Redis高 40 倍。
Redis 和 Gunicorn 之间的网络差距让事情变得更糟……更糟。从远程功能存储中获取数据增加了 Python 架构无法避免的请求的毫秒数。额外的延迟更加复杂,并且在具有有限资源的系统中引起了争用。大多数 Gunicorn 线程只是在网络上等待,成千上万的请求被卡在队列中。
PostgresML 没有这个问题,因为功能和 Rust 推理层存在于同一个系统上。这种架构选择消除了等式中的网络延迟和(反)序列化。
您会注意到我们之前讨论过的并发问题在 20 个连接时命中 Postgres,我们再次使用 PgBouncer 来挽救局面。
一旦你知道如何做,扩展 Postgres 并不像听起来那么困难。
方法
硬件
第一个基准测试中的客户端和服务器都位于同一台机器上。Redis 也是本地的。该机器是 8 核 16 线程 AMD Ryzen 7 5800X,配备 32GB RAM、1TB NVMe SSD,运行 Ubuntu 22.04。
AWS EC2 基准测试是通过一个c5.4xlarge托管 Gunicorn 和 PostgresMLc5.large的实例以及两个分别托管客户端和 Redis 的实例完成的。它们位于同一个 VPC 中。
配置
Gunicorn 运行时有 5 个工人和每个工人 2 个线程。Postgres 分别为 1、5 和 20 个客户端使用 1、5 和 20 个连接。PgBouncer 的 adefault_pool_size为 10,因此最多 10 个 Postgres 连接用于 20 和 100 个客户端。
XGBoost 在推理期间被允许使用 2 个线程,在训练期间被允许使用所有可用的 CPU 内核(16 个线程)。
两者都ab使用pgbench所有可用资源,但都非常轻量级;这些请求分别是一个 JSON 对象和一个查询。两个客户端都使用持久连接,ab通过使用 HTTP Keep-Alives,并pgbench在基准测试期间保持 Postgres 连接打开。
机器学习
数据
我们使用了Kaggle 的Flight Status Prediction数据集。经过一些后期处理后,它最终得到了大约 2 GB 的浮点特征。我们没有使用所有列,因为其中一些是多余的,例如机场名称和机场标识符,它们指的是同一个东西。
模型
我们的 XGBoost 模型使用默认超参数和 25 个估计器(也称为增强轮)进行训练。
用于训练和推理的数据可在此处获得。存储在 Redis 功能存储中的数据可在此处获得。它只是一个子集,因为使用单个 Python 进程(2800 万行)将整个数据集加载到 Redis 需要花费数小时。与此同时,PostgresCOPY只用了大约一分钟。
PostgresML 模型经过以下训练:
SELECT * FROM pgml.train(
project_name => 'r2',
algorithm => 'xgboost',
hyperparams => '{ "n_estimators": 25 }'
);
它的准确性很差(就像 Python 版本一样),可能是因为我们错过了任何类型的天气信息,后者最有可能导致机场延误。
源代码
基准源代码可以在Github(https://github.com/postgresml/postgresml/tree/master/pgml-docs/docs/blog/benchmarks/python_microservices_vs_postgresml/)上找到。
反馈
非常感谢和❤️所有支持这项努力的人。我们很乐意听取更广泛的 ML 和工程社区关于应用程序和其他现实世界场景的反馈,以帮助确定我们工作的优先级。
原文标题:PostgresML is 8-40x faster than Python HTTP microservices
原文作者:Lev Kokotov
原文链接:https://postgresml.org/blog/postgresml-is-8x-faster-than-python-http-microservices/#data




