方法

方法

我的理解是,新的 RDS 实例将根据需要从快照中“分页”块,如 EC2 卷所述这里

这目前让我很头疼:我正在一个新的测试实例上运行一个大型查询;它应该需要 10-15 分钟才能运行,但它已经运行了一个小时。这个实例有 1,000GB 的存储空间,所以有 3,000 IOPS,但我在控制台中看到的总 IOPS 不到 100(有时读取 IOPS 不到 20)。

通常我会mysqldump进行完整的数据库备份并将其发送给/dev/null-- 但这需要 12-18 小时。过去我曾一次对多个表进行表扫描查询,希望这些 IO 能够并行发生。

有谁知道更好的方法来预热音量?

答案1

由于看起来没有比我已经使用的方法更简单的方法,所以我决定评估这些方法。

对于测试环境,我为每种方法启动了一个实例:r3.large(2 VCPU,15 GB RAM),每个实例使用相同的快照。这些实例有 1,000 GB 的磁盘,因此应该能够维持 3,000 IOPS。

整个数据库包含数百个表,从几百行到几亿行不等(几个表主要用于日志记录,但可能涉及一些报告查询)。

我选择了两个表进行评估:我们的“用户”表,它包含 2000 万行并且非常宽;以及一个“链接”表,它也包含 20MM 行但只有两列。

加载后,我对用户表运行了两个查询:一个通过对非索引数字字段求和来强制进行表扫描,另一个对索引列执行聚合操作(应遍历整个索引)。我没有对链接表运行查询,因为它似乎没有提供更多信息。

所有计时均采用 H:MM:SS(小时:分钟:秒)格式,并且来自单次运行。我还使用 Cloudwatch 指标跟踪了读取和写入 IOPS(通常平均为 5-15 分钟)。

我们的数据库使用 MySQL,但我相信通用方法适用于任何 DBMS。

方法

将表转储到 /dev/null

mysqldump CONNECTION_OPTIONS --compress DATABASE TABLES > /dev/null

mysqldump程序用于备份数据库或单个表。它检索所有表数据并将其写入 StdOut,同时使用 DDL 重新创建表及其索引。

由于我并不关心是否真正备份表,因此我将输出重定向到/dev/null。由于我不想被网络所阻碍,因此我使用了选项--compress。即便如此,我还是在同一可用区的 EC2 实例上运行,以将所有网络流量保持在 Amazon 数据中心内。

这种方法的主要缺点是它不会触及索引块。

                          Users      Linkage  
---------------------------------------------
| time to touch blocks | 00:53:38 | 00:03:02 |
| read IOPS            | < 150    | 150+     |
| table-scan           | 00:01:29 |          |
| index aggregate      | 00:00:15 |          |

强制表扫描查询

与测试查询非常相似,此方法是一种简单的选择,可从非索引字段聚合数据。我为“touch”查询和“test”查询选择了不同的字段。

与转储操作一样,这仅访问表数据块。我可以将其扩展为通过某种形式的索引聚合来访问索引块,但我认为这与我的需求不太相关。

                          Users      Linkage  
---------------------------------------------
| time to touch blocks | 00:59:12 | 00:03:31 |
| read IOPS            | 150      | 150      |
| table-scan           | 00:02:04 |          |
| index aggregate      | 00:00:19 |          |

优化

优化表命令将重建 InnoDB 表和索引,并在此过程中释放空间。它是 MySQL 特有的,但我认为 PostgresVACUUM命令是类似的,而且我相信其他数据库系统也有等效的命令。

对于我们的大型表来说,这可能是一个不公平的测试,因为它需要进行多次更新,而且毫无疑问在其多年的生命周期中从未进行过优化。如果我们定期进行优化,也许数字会更低。

                          Users      Linkage  
----------------------------------------------
| time to touch blocks | 02:01:36 | 00:03:44 |
| read IOPS            | 100      | 150      |
| write IOPS           | 500+     | 1000+    |
| table-scan           | 00:00:05 |          |
| index aggregate      | 00:00:01 |          |

您会注意到我添加了一行用于写入 IOPS。此外,这些查询时间并非印刷错误:它们比其他查询快一个数量级以上。我怀疑这是因为这些块被缓存在内存中(我可能应该在接触块和执行查询之间重新启动实例)。

概括

OPTIMIZE对于大型表来说速度会显著变慢,并且会使用过多的写入 IOPS。但是,如果我使用的是 Postgres,那么这VACUUM可能是一个有效的选择,前提是源数据库定期进行清理。

全行选择和 之间的差异mysqldump很小,可能是由于网络或 VM 负载。但是,mysqldump执行起来要容易得多,因为全行选择需要一些思考来选择合适的查询。

运行完这些测试后,我启动了一个新实例并启动了 10 个并发mysqldump会话(随机在它们之间划分表)。一些观察结果:

  • 大多数会议都在 6 小时内结束。有几场会议持续了 7 个小时,还有一场(有几张大桌子)持续了 8 个小时以上。
  • CPU 一直保持在 40% 左右。我怀疑这完全是由于压缩造成的。
  • 读取 IOPS 缓慢从~400 增加到~600(使用 15 分钟的平均周期),并在接近尾声时飙升至 > 1000。

随着我对这个问题的进一步思考,我开始相信我的痛苦主要是因为我正在使用此实例来测试报告负载。如果它是一个 OLTP 实例,我怀疑我可以以最小的痛苦(尽管性能较慢)将其转换为服务。然而,同样的痛苦会影响读取副本,也许更严重,因为你只会在系统负载过重时启动另一个副本。

从长远来看,我只能希望亚马逊能够添加一个并行触及卷块的“快速初始化”操作。

答案2

不幸的是,强制将块调入页的唯一方法是获取它们,并且您已经找到了多种方法来做到这一点。

答案3

我一直在使用一个非常简单的脚本,它将生成一个查询,我很确定它应该触及数据库上的所有数据和索引块:

SCHEMA_NAME=your_schema_name
MYSQL_CMD="mysql -sN $SCHEMA_NAME"

for TBL in $($MYSQL_CMD -e "show tables;" | sort); do
        ALL_IDX=$($MYSQL_CMD -e "SHOW INDEX FROM $TBL;")
        while IFS= read -r IDX_DATA; do
            Key_name=$(echo $IDX_DATA | cut -d " " -f3)
            Seq_in_index=$(echo $IDX_DATA | cut -d " " -f4)
            Column_name=$(echo $IDX_DATA | cut -d " " -f5)

            if [[ $Seq_in_index -ne 1 ]]; then
              continue
            fi

            printf 'SELECT COUNT(*) FROM `%s`.`%s` WHERE CRC32(`%s`) = 1;\n' "$SCHEMA_NAME" "$TBL" "$Column_name"
        done <<< "$ALL_IDX"
done

这将生成如下查询:

SELECT COUNT(*) FROM `db`.`tb1` WHERE CRC32(`id`) = 1;
SELECT COUNT(*) FROM `db`.`tb2` WHERE CRC32(`col1`) = 1;
SELECT COUNT(*) FROM `db`.`tb2` WHERE CRC32(`col2`) = 1;

为了加快进程,您可以并行运行查询。这样会更快,OPTIMIZE因为您不会在进程中发生写入,并且应该触及所有块,这与mysqldump仅触及数据块的方法相反。

相关内容