Amazon Redshift 新エントリーノードタイプ dc2.large と dc1.large の徹底比較

2017.12.18

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

先日、Amazon Redshiftの新しいノードタイプ dc2.large と dc2.8xlarge がリリースされました。Amazon Redshift: 新しいノードタイプ「DC2」が発表されました でご報告したとおり、「Amazon Redshiftに新世代のDC2ノードが追加 – 価格はそのままで最大2倍の性能向上」と発表されていますが、実際のユースケースでどれ位の性能向上が見られるか試してみたいと思います。

Dense Compute 2(dc2)ノードでは、安価で高パフォーマンスのデータウェアハウスを作成できるように、高速 CPU、大容量 RAM、および NVMe-SSD が使用されます。ディスク容量よりもIOや処理性能を重視したノードタイプで、上位のノードタイプとしてdc2.8xlargeが提供されています。また、dc2.largeはRedshiftが提供する最も利用費が安いノードタイプです。

スペックの新旧比較

dc2はdc1の後継となるノードであり、高いスループットと低いレイテンシを必要とするDWHワークロードのために設計されています。CPUはIntel E5-2686 v4(Broadwell)になり、高速なDDR4メモリを搭載。ストレージはNVMe接続のSSDです。

dc1.large と dc2.large

Node Size vCPU ECU Memory (GiB) Storage I/O Node Range Slices Total Capacity
dc1.large 2 7 15 0.16 TB SSD 0.20GB/s 1–32 2 5.12 TB
dc2.large 2 7 15.25 0.16 TB NVMe-SSD 0.60GB/s 1–32 2 5.12 TB

dc1.8xlarge と dc2.8xlarge

Node Size vCPU ECU Memory (GiB) Storage I/O Node Range Slices Total Capacity
dc1.8xlarge 32 104 244 2.56 TB SSD 3.70GB/s 2–128 32 326 TB
dc2.8xlarge 32 99 244 2.56 TB NVMe-SSD 7.50GB/s 2–128 16 326 TB

性能検証 - dc1.large vs dc2.large

パフォーマンスの計測条件

  • クエリ性能検証 リージョン:バージニア(us-east-1)
  • クラスタ:dc1.large と dc2.large ともに 2ノードクラスタ構成
  • データ:AWSが提供しているサンプルデータ(s3://awssampledb/ssbgz/)
  • レコード数:約6億レコード
  • テーブル:lineorder
--DROP TABLE "lineorder";
CREATE TABLE IF NOT EXISTS "lineorder"
(
"lo_orderkey" INTEGER NOT NULL
,"lo_linenumber" INTEGER NOT NULL ENCODE delta
,"lo_custkey" INTEGER NOT NULL
,"lo_partkey" INTEGER NOT NULL
,"lo_suppkey" INTEGER NOT NULL
,"lo_orderdate" INTEGER NOT NULL
,"lo_orderpriority" VARCHAR(15) NOT NULL ENCODE bytedict
,"lo_shippriority" VARCHAR(1) NOT NULL ENCODE runlength
,"lo_quantity" INTEGER NOT NULL ENCODE delta
,"lo_extendedprice" INTEGER NOT NULL ENCODE lzo
,"lo_ordertotalprice" INTEGER NOT NULL ENCODE lzo
,"lo_discount" INTEGER NOT NULL ENCODE delta
,"lo_revenue" INTEGER NOT NULL ENCODE lzo
,"lo_supplycost" INTEGER NOT NULL ENCODE delta32k
,"lo_tax" INTEGER NOT NULL ENCODE delta
,"lo_commitdate" INTEGER NOT NULL ENCODE delta
,"lo_shipmode" VARCHAR(10) NOT NULL ENCODE bytedict
)
DISTSTYLE KEY
DISTKEY ("lo_partkey")
SORTKEY (
"lo_orderdate"
)
;

データロード時間の計測

S3からRedshiftにデータをロード(COPYコマンド)する時間を計測します。以下の処理ではデータのロード、VACUUM、ANALYZEが内部的に実行されます。

dc1.large

メトリックス: QUERIES/CPU/NETWORK | WRITE | READ

-- COPY lineorder FROM 's3://awssampledb/ssbgz/lineorder0000_part_00.gz'
COPY lineorder FROM 's3://awssampledb/ssbgz/lineorder'
CREDENTIALS 'aws_iam_role=arn:aws:iam::123456789012:role/RedshiftRole'
COMPUPDATE OFF
GZIP
;
COPY
Time: 1403450.740 ms (23:23.451)

dc2.large

メトリックス: QUERIES/CPU/NETWORK | WRITE | READ

-- COPY lineorder FROM 's3://awssampledb/ssbgz/lineorder0000_part_00.gz'
COPY lineorder FROM 's3://awssampledb/ssbgz/lineorder'
CREDENTIALS 'aws_iam_role=arn:aws:iam::123456789012:role/RedshiftRole'
COMPUPDATE OFF
GZIP
;
COPY
Time: 1230910.691 ms (20:30.911)

一数行インサート・複数行インサート時間の計測

Redshiftでは、空のテーブルに対して INSERT INTO ... SELECT でデータを投入するとソート済みデータとして投入できますが、後ほどVACUUM時間を計測したいので、ソート状態にならないように事前に1レコード投入しているところがポイントです。

dc1.large

メトリックス: QUERIES/CPU/NETWORK | WRITE | READ

-- 一行インサート時間 - 100%非ソート状態とするため先頭にわざと1レコード追加
INSERT INTO lineorder_cp VALUES(1,1,1,1,1,1,'9','8',1,1,1,1,1,1,1,1,'7');
INSERT 0 1
Time: 1927.633 ms (00:01.928)

-- 複数行インサート時間 - 約6億レコードをコピー
INSERT INTO lineorder_cp SELECT * FROM lineorder;
INSERT 0 600037902
Time: 481281.076 ms (08:01.281)

dc2.large

メトリックス: QUERIES/CPU/NETWORK | WRITE | READ

-- 一行インサート時間 - 100%非ソート状態とするため先頭にわざと1レコード追加
INSERT INTO lineorder_cp VALUES(1,1,1,1,1,1,'9','8',1,1,1,1,1,1,1,1,'7');
INSERT 0 1
Time: 1341.825 ms (00:01.342)

-- 複数行インサート時間 - 約6億レコードをコピー
INSERT INTO lineorder_cp SELECT * FROM lineorder;
INSERT 0 600037902
Time: 325370.569 ms (05:25.371)

VACUUM時間の計測

約6億レコードのデータに対してVACUUM FULLを実行します。内部的にDELETE、SORTが実行されるので一般に時間のかかる処理です。

lineorder_cpテーブルの未ソート割合(pct_unsorted)100(100%)の状態であることを確認

事前にlineorder_cpテーブルの未ソート割合が100%であることを確認します。

tablename | megabytes | rowcount | unsorted_rowcount | pct_unsorted | recommendation
----------------------------+-----------+-----------+-------------------+--------------+-------------------------
lineorder | 28728 | 600037902 | 0 | 0 | n/a
lineorder_cp | 28668 | 600037903 | 600037902 | 100 | VACUUM SORT recommended

dc1.large

メトリックス: QUERIES/CPU/NETWORK | WRITE | READ

VACUUM lineorder_cp;
VACUUM
Time: 2361741.838 ms (39:21.742)

dc2.large

メトリックス: QUERIES/CPU/NETWORK | WRITE | READ

VACUUM lineorder_cp;
VACUUM
Time: 2234033.311 ms (37:14.033)

lineorder_cpテーブルの 未ソート割合(pct_unsorted)0(0%)の状態であることを確認

VACUUM実施後にlineorder_cpテーブルの未ソート割合が0%であることを確認します。

tablename | megabytes | rowcount | unsorted_rowcount | pct_unsorted | recommendation
----------------------------+-----------+-----------+-------------------+--------------+----------------
lineorder | 28728 | 600037902 | 0 | 0 | n/a
lineorder_cp | 28752 | 600037903 | 0 | 0 | n/a

ANALYZE時間の計測

VACUUMをかけたことに加え、INSERT INTO ... SELECT で投入したデータに対してANALYZEが実行されていませんので、テーブルをANALYZEを実行して、適切なクエリプランが得られるように準備します。

dc1.large

ANALYZE lineorder_cp;
ANALYZE
Time: 40893.048 ms (00:40.893)

dc2.large

ANALYZE lineorder_cp;
ANALYZE
Time: 29371.240 ms (00:29.371)

行カウント時間の計測

実際のユースケースでは条件付きクエリーを実行して結果を得ることが多いですが、今回はクライアントの実装やネットワークIOで計測値がブレるのを避けるため、行カウントする時間を計測しています。また、以前であれば、コンパイル時間を排除するために、参照系クエリは2回実行して、2回目の時間をベンチマークの計測値としましたが、現在はResult Cachingがデフォルトで有効になっており、結果キャッシュを返すので1度目の値を計測値としています。

dc1.large

SELECT COUNT(*) FROM lineorder WHERE lo_custkey < 2000000 and lo_linenumber < 7;
count
-----------
385740049
(1 row)

Time: 5906.958 ms (00:05.907)

dc2.large

SELECT COUNT(*) FROM lineorder WHERE lo_custkey < 2000000 and lo_linenumber < 7;
count
-----------
385740049
(1 row)

Time: 5097.764 ms (00:05.098)

集計インサート時間の計測

DWHでは、ビックデータに対して1次集計、2次集計...マート作成等の集計テーブルの作成が日常的に行われます。以下の検証用クエリでは、フィルタ・集計・ソートした結果をディープコピーして、集計テーブルを作成する時間を計測します。

dc1.large

CREATE TABLE lineorder_summary
(
lo_linenumber INTEGER NOT NULL,
lo_custkey INTEGER NOT NULL,
lo_ordertotalprice INTEGER NOT NULL
)
distkey(lo_custkey)
sortkey(lo_ordertotalprice, lo_linenumber, lo_custkey);
CREATE TABLE
Time: 404.250 ms

INSERT INTO lineorder_summary
SELECT lo_linenumber, lo_custkey, SUM(lo_ordertotalprice) as lo_ordertotalprice
FROM lineorder_cp
WHERE lo_orderdate >= '1993-01-01' AND lo_orderdate < '1997-01-01' GROUP BY lo_linenumber, lo_custkey ORDER BY lo_ordertotalprice, lo_linenumber, lo_custkey; INSERT 0 13982744 Time: 83363.175 ms (01:23.363) ``` #### dc2.large ```sql CREATE TABLE lineorder_summary ( lo_linenumber INTEGER NOT NULL, lo_custkey INTEGER NOT NULL, lo_ordertotalprice INTEGER NOT NULL ) distkey(lo_custkey) sortkey(lo_ordertotalprice, lo_linenumber, lo_custkey); CREATE TABLE Time: 348.203 ms INSERT INTO lineorder_summary SELECT lo_linenumber, lo_custkey, SUM(lo_ordertotalprice) as lo_ordertotalprice FROM lineorder_cp WHERE lo_orderdate >= '1993-01-01' AND lo_orderdate < '1997-01-01'
GROUP BY lo_linenumber, lo_custkey
ORDER BY lo_ordertotalprice, lo_linenumber, lo_custkey;
INSERT 0 13982744
Time: 66380.902 ms (01:06.381)

性能の検証結果

これまでの検証結果を御覧頂いたとおり、全ての検証項目において dc2.largeは概ね20〜30%の性能向上が確認できました。

  • データロード: 13.3% ↑
  • 一行インサート: 30.4% ↑
  • 複数行インサート: 32.4% ↑
  • VACUUM: 5.4% ↑
  • ANALYZE: 28.2% ↑
  • 行カウント: 13.7% ↑
  • 集計インサート: 20% ↑

最後に

今回の検証では、クエリの傾向を把握する目的でシンプルなクエリを同時実行無しで計測しています。他にも結合、フィルタ、集計のクエリを実行しましたが、概ね20〜30%範囲でした。このようなシンプルなクエリでは、READ/WRITEよりもCPUのボトルネックが先に顕在化してdc2の「IOが3倍」という特性を活かしきれず、2倍の性能向上に至らなかったと推測しています。複雑なクエリーが同時に複数実行するユースケースで、かつボトルネットがREAD/WRITEの場合、ノードタイプをdc1からdc2に変更をすることで、今回の検証結果よりも良い結果が得られるのではないかと考えられます。何れにしてもAWS利用費が変わらず、チューニングレスで20〜30%の性能向上という結果は検討に値すると言えるでしょう。