本日も乙

ただの自己満足な備忘録。

Redshiftクラスタの暗号化をやってみた 〜移行編〜

昨日の続きです。

blog.jicoman.info

実際に移行してみてつまづいたことや、データ移行時間の短縮について解説します。

前回記事の「最後に」でちょろっと書きましたが、UnloadCopyUtilityを動かすとわかりますが、小さいテーブルの移行でしか考慮されていないような作りになっていることがわかります。

途中でキャンセルされてしまう問題について

行数が多いテーブルを移行しようとすると以下のようにキャンセルされてしまいます。

# unload
INFO:root:unload ('SELECT * FROM public.mytable_a')
                     to 's3://copy-redshift-cluster/mytable_a/2018-05-09_13:32:17/mydb.public.mytable_a.' credentials
                     'aws_iam_role=arn:aws:iam::123456789012:role/redshift-iam-role;master_symmetric_key=REDACTED'
                     manifest
                     encrypted
                     gzip
                     delimiter '^' addquotes escape allowoverwrite
WARNING:root:ERROR:  Query (684) cancelled on user's request

# copy & fetch
INFO:root:copy public.mytable_a
                   from 's3://copy-redshift-cluster/mytable_a/2018-05-09_17:38:32/mydb.public.mytable_a.manifest' credentials
                   'aws_iam_role=arn:aws:iam::123456789012:role/redshift-iam-role;master_symmetric_key=REDACTED'
                   manifest
                   encrypted
                   gzip
                   delimiter '^' removequotes escape compupdate off REGION 'ap-northeast-1'
INFO:  Load into table 'mytable_a' completed, 336038406 record(s) loaded successfully.
WARNING:root:ERROR:  Query (2094) cancelled on user's request
DETAIL:
  -----------------------------------------------
  error:  Query (2094) cancelled on user's request
  code:      1010
  context:   Query (2094) cancelled on user's request
  query:     2094
  location:  a:0
  process:   padbmaster [pid=3245]
  -----------------------------------------------

移行している最中のクエリを見てみると、20分経過するとキャンセルされてしまっているようでした。最初はRedshiftのタイムアウト(statement_timeout)を疑いましたが、私の手元の環境では0(無制限)になっていました。 UnloadCopyUtilityのソースを追っていくとTCPキープアライブとstatement_timeoutに関する設定があることがわかりました。

UnloadCopyUtility/util/redshift_cluster.py#L13-L15

from datetime import datetime, timedelta
import sys
import logging
import pg
import re
from util.sql.sql_text_helpers import GET_SAFE_LOG_STRING
import pytz


import boto3
from pg import connect

options = "keepalives=1 keepalives_idle=200 keepalives_interval=200 keepalives_count=6 connect_timeout=10"

set_timeout_stmt = "set statement_timeout = 1200000"

UNLOADもしくはCOPYコマンドが実行されている間は、Redshiftへの接続がアイドルになってしまうため、その間はTCP Keepaliveによって接続し続けることになります。
オプション値の意味についてPyGreSQLのドキュメントには見当たりませんでしたが、PostgreSQLのドキュメントで発見しました。

keepalives
  Controls whether client-side TCP keepalives are used. The default value is 1, meaning on, but you can change this to 0, meaning off, if keepalives are not wanted. This parameter is ignored for connections made via a Unix-domain socket.
keepalives_idle
  Controls the number of seconds of inactivity after which TCP should send a keepalive message to the server. A value of zero uses the system default. This parameter is ignored for connections made via a Unix-domain socket, or if keepalives are disabled. It is only supported on systems where TCP_KEEPIDLE or an equivalent socket option is available, and on Windows; on other systems, it has no effect.
keepalives_interval
  Controls the number of seconds after which a TCP keepalive message that is not acknowledged by the server should be retransmitted. A value of zero uses the system default. This parameter is ignored for connections made via a Unix-domain socket, or if keepalives are disabled. It is only supported on systems where TCP_KEEPINTVL or an equivalent socket option is available, and on Windows; on other systems, it has no effect.
keepalives_count
  Controls the number of TCP keepalives that can be lost before the client's connection to the server is considered dead. A value of zero uses the system default. This parameter is ignored for connections made via a Unix-domain socket, or if keepalives are disabled. It is only supported on systems where TCP_KEEPCNT or an equivalent socket option is available; on other systems, it has no effect.
connect_timeout
  Maximum wait for connection, in seconds (write as a decimal integer string). Zero or not specified means wait indefinitely. It is not recommended to use a timeout of less than 2 seconds.
https://www.postgresql.org/docs/9.2/static/libpq-connect.html

TCPのmanページにもオプションに関する説明が記載されています。

UNLOADもしくはCOPYコマンドが実行されている間、keepalives_idle(TCP_KEEPIDLE = 200)秒を超えると、TCP は keepalives_interval(TCP_KEEPINTVL = 200)秒の間隔で keepalive プローブを送りはじめます。keepalives_count(TCP_KEEPCNT = 6)回試行、つまり 200秒 * 60回 = 1,200秒 = 20分 経っても応答が得られない場合は接続を切断してしまいます。

AWSドキュメントにも同じようなことが記載されていて、TCPキープアライブの設定値を変更する方法が紹介されていました。
Amazon EC2 以外から接続する — ファイアウォールタイムアウトの問題 - Amazon Redshift

先程のソースで statement_timeout も 1,200,000ミリ秒(= 1,200秒 = 20分)に固定されているため、どちらによ20分経過したクエリはキャンセルされてしまうようになっていました。

わざわざシステム値を変えたくないので、スクリプトの設定値を変えることで対処したいのですが、スクリプト及び設定ファイルを読む限りでは設定値を変えるような作りになっていません。仕方がないので、ソースを改変することで対処しました(後にGitHubのPRとして残しているURLを共有します)。

--- a/src/UnloadCopyUtility/util/redshift_cluster.py
+++ b/src/UnloadCopyUtility/util/redshift_cluster.py
@@ -10,7 +10,7 @@ import pytz
 import boto3
 from pg import connect

# 200秒 * 54回 = 3時間まで伸ばす
-options = "keepalives=1 keepalives_idle=200 keepalives_interval=200 keepalives_count=6 connect_timeout=10"
+options = "keepalives=1 keepalives_idle=200 keepalives_interval=200 keepalives_count=54 connect_timeout=10"

# 無制限に設定
-set_timeout_stmt = "set statement_timeout = 1200000"
+set_timeout_stmt = "set statement_timeout = 0"

移行時間を短縮する

移行中は移行元のRedshiftクラスタへのアクセスを止める必要があるため、その間はメンテナンスとなります。できるだけ早くデータ移行することでメンテナンス時間を短縮できます。移行時間を短縮するための方法を2つ紹介します。

ワークロード管理(WLM)を利用する

これは私の環境特有の問題でもあるのですが、移行したテーブルは30弱程度ですが、一部のテーブルの件数がかなり多く、それ以外のテーブルは件数がかなり少ないことがわかりました。

そこで移行を早く終わらせるようにするため、件数が多いテーブルとそれ以外のテーブルの移行で割り当てるリソースを変えることで並行でUNLOAD & COPYできるようにします。
Redshiftにはワークロードに応じてリソース配分が設定できる、WLMという仕組みがあるのでそれを利用します。

具体的には、まず、移行用のパラメータグループを作成し、そのパラメータグループに対して以下のようなWLMを作成しました(数値はかなり決めでして、各々の環境で設定が異なると思います)。 サイズが最も多いテーブルに多めにメモリを割り当ててデータロードさせつつ、他のテーブル用に別のワークロードで並列実行させます。

キュー クエリグループ 同時実行数 メモリ(%) 用途
キュー1 heavy_wl 15 75% 重いクエリ用(件数が多いテーブル用)
キュー2 light_wl 15 20% 軽いクエリ用(他のテーブル用)
デフォルトキュー - 5 5% デフォルトのキューで今回は使わない

UnloadCopyUtility にはQueryGroupの設定や wlm_query_slot_count を変更する仕組みがないのでソースを改変して設定ファイル(JSON)から変更できるようにしました。修正内容はこちらにあげています。本家にPR出していないので永久に取り込まれることはないので、各自で修正ください。

https://github.com/ohsawa0515/amazon-redshift-utils/pull/1

修正してWLMを適用してやると以下の図のようにうまいこと平行してデータ移行できました。

UNLOAD

UNLOAD

COPY

COPY

リサイズ

WLMを駆使し平行処理したとしてもRedshiftクラスタのスペックが低いといつまでたっても終わりません。最後は札束の力を使いますw 今回の移行で dc2.8xlarge × 8ノードにリサイズして移行しかなり時間短縮になりました。
ただ、リサイズに時間がかかるため、あらかじめリサイズにどのぐらいの時間がかかるのかを検証しておくと良いでしょう。

最後に

暗号化済Redshiftクラスタへデータ移行する際のつまづいたことや時間短縮のtipsを書きました。
ちなみに暗号化Redshiftクラスタは、暗号化する前よりも当然パフォーマンスが落ちます。 Redshift の暗号化についてまとめてみた によりますと、ピーク時に40%オーバーヘッドが生じるとのことです。移行した身としては、そこまで落ちてないのですが、やはり落ちているなぁとという感覚です。どうしても暗号化しなければならない場合を除いて、できるだけ暗号化しないのが一番です。
それにしてももう少し暗号化の有効化が簡単になりませんかね・・・AWSさん!