Postgres 被低估了——它处理的能力超出了你的想象
正在考虑扩展 Postgres 集群并添加其他数据存储,例如 Redis 或 Elasticsearch?在采用更复杂的基础架构之前,请花点时间再三考虑。充分利用现有的 Postgres 数据库完全有可能。它可以扩展以应对高负载,并提供一些乍一看并不明显的强大功能。例如,它可以启用内存缓存、文本搜索、专用索引和键值存储。
阅读本文后,您可能想列出您希望从数据存储中获得的功能,并检查 Postgres 是否适合它们。它对于大多数应用程序来说已经足够强大了。
为什么添加另一个数据存储并不总是一个好主意
正如弗雷德·布鲁克斯在《人月神话》中所说:“程序员就像诗人一样,他们的工作与纯粹的思想只有一线之隔。他们凭借着丰富的想象力,凭空建造空中楼阁。”
为这些城堡添加更多组件,并沉浸于设计之中,这本身就充满无限魅力;然而,在现实世界中,建造更多空中楼阁可能会阻碍你的前进。数据存储领域的最新炒作也是如此。选择枯燥乏味的技术有几个好处:
- 如果有新人加入您的团队,他们能轻松理解您的不同数据存储吗?
- 当您或其他团队成员一年后回来时,他们能否快速了解系统的运作方式?
- 如果您需要更改系统或添加功能,您需要移动多少个部件?
- 您是否考虑过维护成本、安全性和升级?
- 在生产环境中大规模运行新数据存储时,您是否考虑过未知因素和故障模式?
虽然可以通过周到的设计来管理,但添加多个数据存储确实会增加复杂性。在探索添加其他数据存储之前,值得先研究一下现有数据存储可以提供哪些额外功能。
Postgres 鲜为人知但功能强大的特性
很多人不知道 Postgres 提供的远不止一个 SQL 数据库。如果你的堆栈中已经有 Postgres,既然 Postgres 已经能胜任,为什么还要添加其他组件呢?
Postgres 也缓存
有一种误解,认为 Postgres 在每次查询时都会从磁盘读取和写入,尤其是当用户将其与纯内存数据存储(如 Redis)进行比较时。
实际上,Postgres 拥有一个设计精美的缓存系统,其中包含页面、使用计数和事务日志。大多数查询都不需要访问磁盘,尤其是在反复引用相同数据的情况下(许多查询往往会这样做)。
Postgres 配置文件中的shared_buffer配置参数决定了用于缓存数据的内存大小。通常,该参数应设置为总内存的 25% 到 40%。这是因为 Postgres 运行时也会使用操作系统缓存。有了更多内存,大多数引用同一数据集的重复查询将无需访问磁盘。以下是在 Postgres CLI 中设置此参数的方法:
ALTER SYSTEM SET shared_buffer TO = <value>
像 Heroku 这样的托管数据库服务提供了多种方案,其中 RAM(以及缓存)是主要区别所在。免费的业余版本不提供 RAM 等专用资源。当您准备好进行生产负载时,请升级,以便更好地利用缓存。
您还可以使用一些更高级的缓存工具。例如,检查pg_buffercache视图,了解哪些内容占用了实例的共享缓冲区缓存。另一个可用的工具是pg_prewarm函数,它是基础安装的一部分。该函数允许 DBA 将表数据加载到操作系统缓存或 Postgres 缓冲区缓存中。该过程可以手动或自动执行。如果您了解数据库查询的性质,这可以显著提高应用程序性能。
对于真正勇敢的人,请参阅本文以深入了解 Postgres 缓存。
文本搜索
Elasticsearch 非常出色,但很多情况下 Postgres 的文本搜索功能也能很好地胜任。Postgres 拥有一种特殊的数据类型,tsvector
以及一组函数,例如to_tsvector
和to_tsquery
,可以快速搜索文本。tsvector
表示通过对术语进行排序和规范化变体来优化文本搜索的文档。以下是该函数的示例to_tsquery
:
SELECT to_tsquery('english', 'The & Boys & Girls');
to_tsquery
---------------
'boy' & 'girl'
您可以根据查询在结果中出现的频率和字段,按相关性对结果进行排序。例如,您可以使标题比正文更具相关性。有关详细信息,请参阅 Postgres文档。
Postgres 中的函数
Postgres以多种编程语言提供了强大的服务器端函数环境。
尝试使用服务器端函数在 Postgres 服务器上预处理尽可能多的数据。这样,您可以减少在应用服务器和数据库之间来回传递过多数据所带来的延迟。这种方法对于大型聚合和连接操作尤其有用。
更棒的是,您的开发团队可以利用现有的技能来编写 Postgres 代码。除了默认的 PL/pgSQL(Postgres 的原生过程语言)之外,Postgres 函数和触发器还可以用 PL/Python、PL/Perl、PL/V8(Postgres 的 JavaScript 扩展)和 PL/R 编写。
以下是创建用于检查字符串长度的 PL/Python 函数的示例:
CREATE FUNCTION longer_string_length (string1 string, string2 string)
RETURNS integer
AS $$
a=len(string1)
b=len(string2)
if a > b:
return a
return b
$$ LANGUAGE plpythonu;
Postgres 提供强大的扩展
扩展之于 Postgres,就如同插件之于许多应用程序。合理使用 Postgres 扩展,意味着您无需与其他数据存储系统协作即可获得额外功能。Postgres 主网站上列出了许多可用的扩展。
地理空间数据
PostGIS是 Postgres 的一个专用扩展,用于处理地理空间数据并使用 SQL 执行位置查询。它在使用 Postgres 的 GIS 应用程序开发者中广受欢迎。您可以在此处找到一份优秀的 PostGIS 初学者指南。
下面的代码片段展示了如何将 PostGIS 扩展添加到当前数据库。在操作系统中,我们运行以下命令来安装该软件包(假设您使用的是 Ubuntu):
$ sudo add-apt-repository ppa:ubuntugis/ppa
$ sudo apt-get update
$ sudo apt-get install postgis
之后,登录到您的 Postgres 实例并安装扩展:
CREATE EXTENSION postgis;
CREATE EXTENSION postgis_topology;
如果您想检查当前数据库中有哪些扩展,请运行以下命令:
SELECT * FROM pg_available_extensions;
键值数据类型
Postgres hstore扩展允许存储和搜索简单的键值对。本教程提供了如何使用 hstore 数据类型的精彩概述。
半结构化数据类型
Postgres 中有两种用于存储半结构化数据的原生数据类型:JSON和XML。JSON数据类型既可以存储原生 JSON,也可以存储其二进制格式(JSONB)。后者可以显著提升搜索性能。如下所示,它可以将 JSON 字符串转换为原生 JSON 对象:
SELECT '{"product1": ["blue", "green"], "tags": {"price": 10, "discounted": false}}'::json;
json
---------------------------------------------------------------------
{"product1": ["blue", "green"], "tags": {"price": 10, "discounted": false}}
Postgres 扩展技巧
如果您出于性能原因考虑关闭 Postgres,请首先了解它提供的优化功能能带来多大的改进。这里我们假设您已经完成了一些基本操作,例如创建合适的索引。Postgres 提供了许多高级功能,虽然改动很小,但可以带来很大的改进,尤其是在避免基础架构复杂化的情况下。
不要过度索引
避免不必要的索引。谨慎使用多列索引。过多的索引会占用额外的内存,从而影响 Postgres 缓存的利用,而缓存对于性能至关重要。
使用类似的工具,EXPLAIN ANALYZE
你可能会惊讶于查询规划器实际上选择顺序表扫描的频率。由于表的大部分行数据已经被缓存,这些复杂的索引通常甚至不会被使用。
话虽如此,如果你确实发现了查询速度慢的问题,那么第一个也是最显而易见的解决方案就是查看表是否缺少索引。索引至关重要,但你必须正确使用它们。
部分索引节省空间
部分索引可以通过指定哪些值被索引来节省空间。例如,你想按用户的注册日期排序,但只关心已注册的用户:
CREATE INDEX user_signup_date ON users(signup_date) WHERE is_signed_up;
了解 Postgres 索引类型
为数据选择合适的索引可以提升性能。以下是一些常见的索引类型以及每种类型的适用情况。
- B 树索引B 树索引是平衡树,用于高效地对数据进行排序。如果您使用
INDEX
命令,它们是默认的。大多数情况下,B 树索引就足够了。随着规模的扩大,不一致问题可能会更加严重,因此请定期使用amcheck扩展。 - BRIN 索引:当您的表本身已按某列排序,而您需要按该列排序时,可以使用块范围索引 (BRIN)。例如,对于按顺序写入的日志表,在时间戳列上设置 BRIN 索引可以让服务器知道数据已排序。
- 布隆过滤器索引布隆索引非常适合在大型表上执行多列查询,只需测试相等性即可。它使用一种称为布隆过滤器的特殊数学结构,该结构基于概率,占用的空间显著减少。
CREATE INDEX i ON t USING bloom(col1, col2, col3);
SELECT * from t WHERE col1 = 5 AND col2 = 9 AND col3 = 'x';
- GIN 和 GiST 索引\ 使用 GIN 或 GiST 索引可基于文本、数组和 JSON 等复合值建立高效索引。
何时需要另一个数据存储?
在 Postgres 之外添加另一个数据存储是有合法理由的。
特殊数据类型
有些数据存储提供的数据类型在 Postgres 上是无法获得的。例如,Redis 中的链表、位图和 HyperLogLog 函数在 Postgres 上不可用。
在之前的创业公司,我们必须实现频次上限,这是一种基于会话数据(例如 Cookie)来统计网站上独立用户数量的计数器。一个网站的访问用户数量可能有数百万甚至数千万。频次上限意味着你每天只能向每个用户展示一次广告。
Redis 有一个HyperLogLog 数据类型,非常适合频率上限。它以非常小的错误率近似集合成员资格,代价是 O(1) 的时间复杂度和非常小的内存占用。例如,PFADD
将一个元素添加到 HyperLogLog 集合中。如果元素尚未包含在集合中,则返回 1;如果元素已包含在集合中,则返回 0。
PFADD user_ids uid1
(integer) 1
PFADD user_ids uid2
(integer) 1
PFADD user_ids uid1
(integer) 0
繁重的实时处理
如果您需要协调大量发布/订阅事件、作业和数十个工作进程,则可能需要更专业的解决方案,例如 Apache Kafka。LinkedIn 工程师最初开发 Kafka 是为了处理新用户事件(例如点击、邀请和消息),并允许不同的工作进程处理消息传递和作业数据。
即时全文搜索
如果您有一个负载很重的实时应用程序,并且一次进行十次以上的搜索,并且您需要自动完成等功能,那么您可能会从 Elasticsearch 等专门的文本解决方案中受益更多。
结论
Redis、Elasticsearch 和 Kafka 功能强大,但有时添加它们弊大于利。您可以利用我们这里介绍的鲜为人知的功能,获得 Postgres 所需的功能。确保充分利用 Postgres 可以节省您的时间,并帮助您避免额外的复杂性和风险。
为了节省更多时间,避免更多麻烦,不妨考虑使用像Heroku Postgres这样的托管服务。扩展只需添加额外的关注者副本即可,只需单击一下即可启用高可用性,Heroku 会为您代劳。如果您确实需要扩展 Postgres 以外的功能,我们上面提到的其他数据存储,例如 Redis、Apache Kafka 和 Elasticsearch,都可以轻松地在 Heroku 上配置。您可以继续构建您的空中楼阁,但要将它们锚定在可靠的基础上,这样您就可以梦想更好的产品和客户体验。
有关 Postgres 的更多信息,请收听Jon Daniel在《软件工程日报》上主持的“云数据库工作负载”节目。
文章来源:https://dev.to/heroku/postgres-is-undererated-it-handles-more-than-you-think-4ff3