一次对 API 响应时间的优化探索。
在这普通的一天,我们用着普通的 API,突然发现响应速度过慢的警报意外亮起。
结果显示,我们的 API 需要约 70 秒的时间才能对常规流量下的客户端做出响应。开什么玩笑……
从问题入手先向大家汇报一下我们的这个慢速 API 是做什么,又是怎么做的。
在这款应用程序中,我们把书籍及其作者的目录存储在 MySQL 数据库中。其中共包含约 6800 万本书,每本书对应一家出版社。
下面来看书籍和作者的表结构。
CREATE TABLE `book` (
`id` int NOT NULL AUTO_INCREMENT,
`book_uuid_bin` binary(16) NOT NULL,
`publishing_house_uuid_bin` binary(16) NOT NULL,
`display_name` varchar(750) NOT NULL,
`normalized_name` varchar(750) NOT NULL,
`description` varchar(1000) DEFAULT NULL,
`level` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_book_uuid_bin` (`book_uuid_bin`),
KEY `book_description_idx` (`description`(768)),
KEY `book_display_name_idx` (`display_name`),
KEY `book_normalized_name_idx` (`normalized_name`),
KEY `publishing_house_uuid_bin_idx` (`publishing_house_uuid_bin`),
KEY `book_uuid_bin_idx` (`book_uuid_bin`)
)
CREATE TABLE `publishing_house` (
`id` int NOT NULL AUTO_INCREMENT,
`publishing_house_uuid_bin` binary(16) DEFAULT NULL,
`display_name` varchar(750) NOT NULL,
`normalized_name` varchar(750) NOT NULL,
`alias_uuid_bin` binary(16) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `unique_publishing_house_uuid_bin` (`author_uuid_bin`),
KEY `publishing_house_normalized_name_idx` (`normalized_name`),
KEY `publishing_house_display_name_idx` (`display_name`)
)
再说回 API,其用例是在 UI 上提供自动补全功能,方便用户更好地查找特定出版社出版的书籍,同时保证用户的查询字符串同书籍名称或描述的前缀相匹配。
API 中使用的 MySQL 查询如下所示:
select book_uuid_bin,
display_name,
normalized_name,
description,
author_uuid_bin
from book
where
((lower(display_name) like lower("%Software E%") or lower(description) like lower("%Software E%")) and publishing_house_uuid_bin = UUID_TO_BIN("d2230981-e570-5ba4-9a3a-16028c51d54f"))order by display_name asc limit 100;
即使查询在单表上就能完成,不需要连接作者表,这条 SQL 查询也需要 7 秒左右才能执行完成。
我们在 where 子句所使用的列上建立了索引。但这一实现还是存在问题,包括:
1、display_name 和 description 等列属于 VARCHAR 类型。
2、会在 VARCHAR 类型列上使用带有 OR 子句的 LIKE 运算符。
3、会使用 ORDER BY。
4、 WHERE 子句中使用的所有列,都缺少复合索引。
5、表中共包含 5800 万条记录。
我们曾尝试在查询中使用的各列上创建一个复合索引,但最终发现无济于事。因为对于 RDBMS 数据库内的大表来说,在 VARCHAR 列上搜索文本的效率就不可能太高。
我们知道 Elasticsearch 提供全文本搜索功能,所以想在自己的用例中试试看。我们一直在用 AWS 的云服务,因此选择了相应的 AWS OpenSearch 服务。
Amazon OpenSearch 托管服务能帮助用户轻松在 AWS 云中部署、操作和扩展 OpenSearch 集群。Amazon OpenSearch 是 Amazon Elasticsearch 的继任方案。
开始行动我们通过脚本将表数据从 MySQL 加载到了 AWS OpenSearch 集群当中。整个数据迁移过程大概用了几个小时。
我们为索引保留了 5 个分片和 1 个副本因子。
我们还为用例编写了一条等效的 OpenSearch 查询,具体如下所示:
API — POST /books-catalog/_search
{
"query": {
"bool": {
"must": [
{
"match_phrase": {
"publisherUuid": "1f754fc0-610c-5b29-b22b-fa8140afb7be"
}
},
{
"bool": {
"should": [
{
"match_phrase": {
"displayName": "Software E"
}
},
{
"match_phrase": {
"description": "Software E"
}
}
]
}
}
]
}
},
"size": 100,
"sort": [
{
"displayName.keyword": {
"unmapped_type": "keyword",
"order": "asc"
}
}
]
}
结果我们的 API 响应速度直接缩短至 70 毫秒以内。
API 响应速度提高了 1000 倍!
关于 OpenSearch 全文搜索的一些细节 :
在 ElasticSearch 中对文档进行索引(创建)时,AWS OpenSearch 会对字符串类型的字段使用文本分析器。
文本分析器会将字符串字段拆分为多个 token,为各 token 构建内部索引,然后根据查询中提供的 token 进行匹配。
权衡取舍为了避免重写整个服务,同时尽快在 MySQL 切换至 AWS OpenSearch 后恢复正常生产,我们决定只在这个特定用例中使用 OpenSearch。
而且速度提升 1000 倍的代价,就是多了一套需要在 OpenSearch 当中维护的数据副本。但由于我们的数据大多是静态的,持续更新量非常有限,所以维护强度和成本都很低。
可以看到,选择正确的数据库引擎往往会给业务用例带来翻天覆地的提升。
希望我们的经历能给大家带来一点启发,祝编程愉快!
-
API
+关注
关注
2文章
1498浏览量
61954 -
MySQL
+关注
关注
1文章
804浏览量
26526 -
响应时间
+关注
关注
0文章
11浏览量
6915
原文标题:将 API 从 MySQL 迁移到 AWS OpenSearch 后,我们将响应时间提高了 1000 倍
文章出处:【微信号:AI前线,微信公众号:AI前线】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论