RDS Proxy を使うとパフォーマンスは悪くなる
前回の続き
blog.luispc.com
目的
RDS Proxy 良いじゃん と前回記事を書いたけど、パフォーマンスは落ちると思ったので
どれぐらい落ちるかを超簡単にベンチマークをする。
HammerDB
HammerDB の使い方は atsuizo さんのを参考にしました。
atsuizo.hatenadiary.jp
TPC-C を流す
設定
hammerdb>print dict Dictionary Settings for MySQL connection { mysql_host = hasegawa.cluster.ap-northeast-1.rds.amazonaws.com mysql_port = 3306 } tpcc { mysql_count_ware = 1 mysql_num_vu = 1 mysql_user = root mysql_pass = ZokWAWywPwQtO7xr mysql_dbase = tpcc mysql_storage_engine = innodb mysql_partition = false mysql_total_iterations = 1000000 mysql_raiseerror = false mysql_keyandthink = false mysql_driver = timed mysql_rampup = 2 mysql_duration = 5 mysql_allwarehouse = false mysql_timeprofile = false mysql_async_scale = false mysql_async_client = 10 mysql_async_verbose = false mysql_async_delay = 1000 }
hammerdb>print vuconf Virtual Users = 4 User Delay(ms) = 500 Repeat Delay(ms) = 500 Iterations = 1 Show Output = 1 Log Output = 0 Unique Log Name = 1 No Log Buffer = 0 Log Timestamps = 1
結果
5回計測した、平均 TPM
without RDS Proxy
TEST RESULT : System achieved 29349 MySQL TPM at 9754 NOPM TEST RESULT : System achieved 23279 MySQL TPM at 7660 NOPM TEST RESULT : System achieved 30078 MySQL TPM at 9929 NOPM TEST RESULT : System achieved 30173 MySQL TPM at 9892 NOPM TEST RESULT : System achieved 30125 MySQL TPM at 9940 NOPM
平均 TPM: 28,601
with RDS Proxy
TEST RESULT : System achieved 29353 MySQL TPM at 9709 NOPM TEST RESULT : System achieved 20001 MySQL TPM at 6526 NOPM TEST RESULT : System achieved 23056 MySQL TPM at 7553 NOPM TEST RESULT : System achieved 20105 MySQL TPM at 6551 NOPM TEST RESULT : System achieved 26769 MySQL TPM at 8721 NOPM
平均 TPM: 23,857
まとめ
RDS Proxy を使うとパフォーマンスは落ちる。
RDS Proxy を使うとフェイルオーバー時のコネクションプーリング問題が良い感じになるのでは?
目的
フェイルオーバー時のエラーレートを下げたい
RDS Proxy の公式ドキュメントに書かれている
Doesn't drop idle connections during failover, which reduces the impact on client connection pools
を試す
環境
- Aurora MySQL 2.08.1
- Lambda (Go)
やり方
Lambda から DB(もしくは RDS Proxy) に対して 0.5秒間隔で Ping を打つ
3分間起動している間に 5回フェイルオーバーをする
コード
package main import ( "fmt" "github.com/aws/aws-lambda-go/lambda" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "log" "os" "time" ) type PingErr struct { Time time.Time } const ( executeTimeSec = 300 DBMaxOpenConn = 100 DBMaxIdleConn = 10 DBMaxLifeTime = time.Second * 10 ) func main() { lambda.Start(realMain) } func realMain() { time.Local = time.FixedZone("JST", 9*60*60) log.Println("initializing") // direct rw, err := gorm.Open("mysql", "root:ZokWAWywPwQtO7xr@tcp(hasegawa.cluster.ap-northeast-1.rds.amazonaws.com:3306)/mysql?charset=utf8mb4&parseTime=True&loc=Local") // proxy // rw, err := gorm.Open("mysql", "root:ZokWAWywPwQtO7xr@tcp(hasegawa.proxy.ap-northeast-1.rds.amazonaws.com:3306)/mysql?charset=utf8mb4&parseTime=True&loc=Local") defer rw.Close() if err != nil { panic(err) } rw.DB().SetMaxOpenConns(DBMaxOpenConn) rw.DB().SetMaxIdleConns(DBMaxIdleConn) rw.DB().SetConnMaxLifetime(DBMaxLifeTime) log.Println("finish initialized") now := time.Now().Unix() var pingErrs []PingErr for { diff := time.Now().Unix() - now if diff >= executeTimeSec { if len(pingErrs) != 0 { log.Println("ping error detected.", "count: ", len(pingErrs)) for _, v := range pingErrs { fmt.Println(v.Time) } } log.Println("finish") os.Exit(0) } check(rw, &pingErrs) time.Sleep(time.Second / 2) } } func check(db *gorm.DB, pingErrs *[]PingErr) { err := db.DB().Ping() if err != nil { *pingErrs = append(*pingErrs, PingErr{Time: time.Now()}) } }
もっとクエリ数を増やす
SELECT 1 を並列で流す。
SetConnMaxLifetime(DBMaxLifeTime) は直接つないだ時で使う。
RDS Proxy を経由するときはここを指定せずに全コネクションを永遠に使い回す設定にして試す。
package main import ( "context" "github.com/aws/aws-lambda-go/lambda" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "log" "sync" "time" ) type PingErr struct { Time time.Time } const ( timeout = 120 // sec goroutines = 100 // db.r5.large = 1000 queryCount = 1000000 DBMaxOpenConn = 900 DBMaxIdleConn = 900 DBMaxLifeTime = time.Second * 10 DBQuery = "SELECT 1" ) var ( wg sync.WaitGroup ) func main() { lambda.Start(realMain) // realMain() } func realMain() { time.Local = time.FixedZone("JST", 9*60*60) log.Println("initializing") // rw, err := gorm.Open("mysql", "root:ZokWAWywPwQtO7xr@tcp(hasegawa.cluster.ap-northeast-1.rds.amazonaws.com:3306)/mysql?charset=utf8mb4&parseTime=True&loc=Local") rw, err := gorm.Open("mysql", "root:ZokWAWywPwQtO7xr@tcp(hasegawa.proxy.ap-northeast-1.rds.amazonaws.com:3306)/mysql?charset=utf8mb4&parseTime=True&loc=Local") defer rw.Close() if err != nil { panic(err) } rw.DB().SetMaxOpenConns(DBMaxOpenConn) rw.DB().SetMaxIdleConns(DBMaxIdleConn) rw.DB().SetConnMaxLifetime(DBMaxLifeTime) log.Println("finish initialized") var pingErrs []PingErr ctx, cancel := context.WithCancel(context.Background()) q := make(chan *gorm.DB) for i := 0; i < goroutines; i++ { wg.Add(1) go check(ctx, q, &pingErrs) } now := time.Now().Unix() for i := 0; i < queryCount; i++ { diff := time.Now().Unix() - now if diff >= timeout { log.Println("timed out: ", timeout) break } q <- rw } cancel() wg.Wait() if len(pingErrs) != 0 { log.Println("ping error detected: ", len(pingErrs)) } log.Println("finish") } func check(ctx context.Context, q chan *gorm.DB, pingErrs *[]PingErr) { for { select { case <-ctx.Done(): wg.Done() return case db := <-q: err := db.Exec(DBQuery).Error if err != nil { *pingErrs = append(*pingErrs, PingErr{Time: time.Now()}) } } } }
最大 16,000/qps が流れる。
RDS Proxy を"使う”場合
742
まとめ
RDS Proxy は月 1vCPU 辺り $0.018/h なので、案外安い
SetConnMaxLifetime これをこっちで意識しなくてよくなるし、フェイルオーバーの速さと、フェイルオーバー時のエラーレートも下がるので SLA/SLO が厳しい要件では入れおいたほうが良さそう。
RDS Proxy を通すことでホップ数が増えるのと、RDS Proxy 内部でも処理が走るので少なからずパフォーマンスが落ちるはず。
MySQL EXPLAIN の結果は良い感じなのに、何故か遅いクエリの原因を調べる
1回目は7秒かかるクエリが、2回目は速い。
実行計画を見ても、Slow Query を見てもインデックスは使われてそうなイキフンを感じる。
前置き
新しく Aurora MySQL を作成し、検証用のクエリ以外は流れないようにする。
遅いクエリは 7.5秒, 2回目実行すると 20ms とかで返ってくる。
クエリ
SELECT c1, c2, c3, c4, c5 FROM t1 WHERE ((c3 = 1489930231868609 and c4 in (7, 1169) and c2 between '2018-05-29 10:33:35.495' and '2020-05-29 10:33:35.495')) ORDER BY c2 desc, c1 desc;
テーブル
mysql> show create table t1 \G; *************************** 1. row *************************** Table: t1 Create Table: CREATE TABLE `t1` ( `c1` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `c2` datetime NOT NULL, `c3` bigint(20) unsigned NOT NULL, `c4` bigint(20) unsigned NOT NULL, `c5` varchar(255) NOT NULL, `c6` varchar(255) NOT NULL, `c7` varchar(255) NOT NULL, `c8` varchar(255) DEFAULT NULL, `c9` datetime NOT NULL, `c10` datetime NOT NULL, PRIMARY KEY (`c1`,`c2`), KEY `idx_c3_c2` (`c3`,`c2`), KEY `c2_c4_idx` (`c2`,`c4) ) ENGINE=InnoDB AUTO_INCREMENT=2081930928 DEFAULT CHARSET=utf8 /*!50500 PARTITION BY RANGE COLUMNS(c2) (PARTITION part_201501 VALUES LESS THAN ('2015-01-01 00:00:00') ENGINE = InnoDB, ... PARTITION part_202010 VALUES LESS THAN ('2020-10-01 00:00:00') ENGINE = InnoDB, ... */
EXPLAIN
id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
1 | SIMPLE | t1 | range | idx_c3_c2, c2_c4_idx | idx_c3_c2 | 13 | NULL | 21 | Using where |
Slow Query
# Query_time: 6.397257 Lock_time: 1.034133 Rows_sent: 0 Rows_examined: 1914
調べる
考えられる原因として、I/O に時間がかかってると予想し、
とりあえず Performance Schema を有効にする。
Performance Schema
mysql> SHOW VARIABLES LIKE 'per%'; mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES'; mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES';
file_summary_by_instance
ファイルごとに I/O の統計情報が見れる。
table_io_waits_summary_by_table という便利なテーブルもあるけど、何故か Aurora MySQL だと見れなかった。
今回は検証用の DB なので他のクエリは流れないのでこれで行く。
純粋にテーブルを見ても良いし、ps-top を見ても良い。
もし、I/O が発生していたら Buffer Pool に載っておらず、Buffer Pool への取り込むのに時間がかかっていると予測できる。
MySQL 5.7 で EXPLAIN をする
MySQL 5.7 では EXPLAIN の情報が増えていて、partitions というカラムがある。
パーティショニングされたテーブルだと、舐めるであろうパーティションが見れる
id|select_type|table |partitions |type |possible_keys |key |key_len|ref|rows|filtered|Extra | --|-----------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----|----------------------------------------------|------------------|-------|---|----|--------|----------------------------------| 1|SIMPLE |t1|part_201806,part_201807,part_201808,part_201809,part_201810,part_201811,part_201812,part_201901,part_201902,part_201903,part_201904,part_201905,part_201906,part_201907,part_201908,part_201909,part_201910,part_201911,part_201912,part_202001,part_202002,par|range|idx_account_number,execute_date_partner_id_idx|idx_account_number|13 | |1914| 20.0|Using index condition; Using where|
結果
Buffer Pool が空の状態と、2回目の file_summary_by_instance を比較する
1回目
2回目
1回目でクエリが返ってくるまで 7.5秒ぐらいあったので、ほとんどが I/O による時間であることが予測できる。
2回目は Buffer Pool にデータが載ってるので I/O が発生せずに数ミリ秒で返ってくる。
このテーブルは数億行のテーブルで、頻繁にアクセスもないようなので Buffer Pool に常に載っているわけではないと予想。
解決方法
教えてください。
ふと思いつくのは
- innodb_file_per_table を付ける(今回はついてた)
- 不要なレコード数を削除して、Buffer Pool に乗る量を減らす(不要なレコードはない)
- クエリを見直して舐める行数を減らす(舐める行数は2000行ぐらいしかないけど、パーティショニングの範囲が広すぎるせい)
- それでもダメなら Buffer Pool を増やす(これが一番?)
ぐらいなのかなぁ。と思ったけどパーティションを20以上も見てるということはこれ以上 I/O は削減できなさそう。
いらないレコード削除できるだけでも良いかもしれないけどこれは履歴を保存してるのでそれは厳しそう。
今んとこの解決方法としてはメモリを増やす ということしか思いつかば無いんですが
他にいい案があったら教えてください。
EXPLAIN と実際の舐める行数が剥離する理由
WHERE 句によってその時どきで結果は変わるけど、今回の WHERE 句には不変な値しか指定していないのにも関わらず
EXAPLAIN では rows 21、Slow Query 読みだと 1914行と90倍の差がある。
EXPLAIN はインデックスのカーディナリティから推測する。
もし、オプティマイザがベストなインデックスを選択していれば実際に舐める行数(今回で言う Slow Query)と差異があっても
何も問題ないよう。
勉強になりました。
8.0.19 以降ならもっと便利に
EXPLAIN ANALYZE
Aurora MySQL でスロークエリの調査をするのはダルい
Survivable Cache Warming という、機能のせいで DB インスタンスを再起動してもキャッシュが残るので
空にするには逐一 DB を作り直さないといけない。
docs.aws.amazon.com
Pulumi を使えば Infrastructure as Code の本来の目的が果たせると思う
IaC は DevOps の中で重要な立ち位置に居て、インフラ専門部隊だけではなく、バックエンドの開発者も柔軟に構成が変更できるためには
今までの Terraform や Ansible では敷居が高かった(ツール独自のループの書き方とか色々)。だけど Pulumi や aws cdk の登場により好きな言語でリソース管理ができるならその敷居はもっと下がって無駄なコストを減らせると思う(リソースの変更を願いせずとも PR 出せばいいとか)
Pulumi
好きな言語で Infrastructure as Code を実現できるフレームワーク?ツール?
AWS だけではなく、GCP、Azure、Kubernetes、その他 SaaS にも対応してる。
対応言語は Go, Python, Node.js, .NET.Core
仕組みは Terraform とほぼ一緒。
Pulumi の良いところ
Terraform や Ansible といった独自言語(HCL)、もしくはツールに対しての知識は不要で好きな言語で IaC を実現できるのでインフラ専属じゃない人でも気軽にリソースを弄ることができる。(Terraform のバージョン追従みたいので辛い思いしなくていいし、loop の書き方をいちいち調べなくても良い)
また、Pulumi は Go にも対応してるので aws-cdk にはないメリット。
そして、Pulumi の provider は Terraform の provider から生成されてるので tf2pulumi とかいうマイグレツールもある。
github.com
Aurora Cluster を作ってみる
CLI のインストールや、state ファイルの指定などは公式ドキュメントへ。
全貌
func createAuroraSecurityGroup(ctx *pulumi.Context) (*ec2.SecurityGroup, error) { name := fmt.Sprintf("aurora-%s-%s", env, prj) args := &ec2.SecurityGroupArgs{ VpcId: pulumi.String("vpc-0ccbc720526afbcff"), Ingress: ec2.SecurityGroupIngressArray{ ec2.SecurityGroupIngressArgs{ CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")}, FromPort: pulumi.Int(3306), ToPort: pulumi.Int(3306), Protocol: pulumi.String("TCP"), }, }, Egress: ec2.SecurityGroupEgressArray{ ec2.SecurityGroupEgressArgs{ CidrBlocks: pulumi.StringArray{pulumi.String("0.0.0.0/0")}, FromPort: pulumi.Int(0), ToPort: pulumi.Int(0), Protocol: pulumi.String("-1"), }, }, Name: pulumi.String(name), Tags: tags, } return ec2.NewSecurityGroup(ctx, name, args) } func createAuroraSubnetGroup(ctx *pulumi.Context) (*rds.SubnetGroup, error) { args := &rds.SubnetGroupArgs{ Name: pulumi.String(fmt.Sprintf("%s", prj)), SubnetIds: pulumi.StringArray{pulumi.String("subnet-082199a2239986516"), pulumi.String("subnet-00e5e8eda3f4cdef1")}, Tags: tags, } return rds.NewSubnetGroup(ctx, fmt.Sprintf("%s", prj), args) } func createAuroraClusterParameterGroup(ctx *pulumi.Context) (*rds.ClusterParameterGroup, error) { timeZone := rds.ClusterParameterGroupParameterArgs{ Name: pulumi.String("time_zone"), Value: pulumi.String("Asia/Tokyo"), } args := &rds.ClusterParameterGroupArgs{ Family: pulumi.String("aurora-mysql5.7"), Parameters: rds.ClusterParameterGroupParameterArray{timeZone}, Tags: tags, } return rds.NewClusterParameterGroup(ctx, fmt.Sprintf("%s-%s", env, prj), args) } func createAuroraCluster(ctx *pulumi.Context, clusterParameterGroupName, subnetGroupName pulumi.StringPtrInput, securityGroupID pulumi.IDOutput) (*rds.Cluster, error) { name := fmt.Sprintf("%s-%s-cluster", env, prj) args := &rds.ClusterArgs{ ApplyImmediately: pulumi.Bool(true), ClusterIdentifier: pulumi.String(name), DbClusterParameterGroupName: clusterParameterGroupName, DbSubnetGroupName: subnetGroupName, Engine: pulumi.String("aurora-mysql"), EngineMode: pulumi.String("provisioned"), EngineVersion: pulumi.String("5.7.mysql_aurora.2.07.2"), MasterPassword: pulumi.String(c.Require("aurora_master_password")), MasterUsername: pulumi.String(c.Require("aurora_master_username")), Tags: tags, VpcSecurityGroupIds: pulumi.StringArray{securityGroupID}, } return rds.NewCluster(ctx, name, args) } func createAuroraClusterInstance(ctx *pulumi.Context, cluster *rds.Cluster) error { count := c.RequireInt("aurora_instances") for i := 0; i < count; i++ { name := fmt.Sprintf("%s-%s-instance-%d", env, prj, i) args := &rds.ClusterInstanceArgs{ ApplyImmediately: pulumi.Bool(true), ClusterIdentifier: cluster.ID(), Engine: pulumi.String("aurora-mysql"), EngineVersion: pulumi.String("5.7.mysql_aurora.2.07.2"), Identifier: pulumi.String(name), InstanceClass: pulumi.String(c.Require("aurora_instance_class")), PerformanceInsightsEnabled: pulumi.Bool(false), // 2020/05/26 5.7.mysql_aurora.2.07.2 is not supported PubliclyAccessible: pulumi.Bool(false), Tags: tags, } _, err := rds.NewClusterInstance(ctx, name, args) if err != nil { return err } } return nil }
Tips
使ってみた感じ
configuration variable
外部から注入する変数みたいなもので
$ pulumi config set env stg
env 変数を作って、コードで取得する際は c.Require("env") とするだけ
この変数もスタック(dev 用とか、stg 用とかで分けれる)ごとに分かれる。
configuration variable を取得する際にエラーハンドリングをしなくても落ちてくれる
Diagnostics: pulumi:pulumi:Stack (test-pulumi-stg): panic: fatal: A failure has occurred: missing required configuration variable 'test-pulumi:project'; run `pulumi config` to set
env = c.Require("env") if env == "" { return fmt.Errorf("'env' variable not defined") }
こんなことしなくて OK
既存のリソースに対しての変更もちゃんとできる
pulumi で作ったリソースのコードにタグを追加した結果 ↓
Previewing update (stg): Type Name Plan Info pulumi:pulumi:Stack test-pulumi-stg 1 message ~ └─ aws:ecs:Cluster stg-test update [diff: ~tags] Diagnostics: pulumi:pulumi:Stack (test-pulumi-stg): &{CustomResourceState:{ResourceState:{urn:{OutputState:0xc000207c00} providers:map[] aliases:[] name:stg-test transformations:[]} id:{OutputState:0xc000207b90}} Arn:{OutputState:0xc0002078f0} CapacityProviders:{OutputState:0xc000207960} DefaultCapacityProviderStrategies:{OutputState:0xc0002079d0} Name:{OutputState:0xc000207a40} Settings:{OutputState:0xc000207ab0} Tags:{OutputState:0xc000207b20}}
エラー内容も結構分かりやすい
Previewing update (stg): Type Name Plan Info pulumi:pulumi:Stack test-pulumi-stg └─ aws:ec2:SecurityGroup aurora-stg-test 3 errors Diagnostics: aws:ec2:SecurityGroup (aurora-stg-test): error: aws:ec2/securityGroup:SecurityGroup resource 'aurora-stg-test' has a problem: "ingress.0.from_port": required field is not set error: aws:ec2/securityGroup:SecurityGroup resource 'aurora-stg-test' has a problem: "ingress.0.to_port": required field is not set error: aws:ec2/securityGroup:SecurityGroup resource 'aurora-stg-test' has a problem: "ingress.0.protocol": required field is not set
手で消したリソースを pulumi にも反映させる方法
$ pulumi state delete urn:pulumi:stg::test-pulumi::aws:rds/cluster:Cluster::stg-test-cluster warning: This command will edit your stack's state directly. Confirm? Yes Multiple resources with the given URN exist, please select the one to edit: "tf-20200525154551533900000001" (Pending Deletion) Resource deleted successfully
俺的監視ベストプラクティス
入門監視を読んで、社内のドキュメントに書いたものをブログにも書く。
今回のは VM を対象。
監視俺的ベストプラクティス(VM編)
前提
- 監視ツールは1つに絞らなくていい
- クリティカルなものはサービス影響が出るものだけ
- 障害時に参考になりそうなものはグラフ化する
- できるだけユーザーに近いところを監視する
- アラートには絶対メモをつける
- 影響、解決方法
warning と critical の使い分け
❌ warning を @here で通知する
warning 見たって対応しないでしょ? アラート部屋に通知だけして、メンションは無しが良さそう
❌メールで通知受ける
Slack で良いじゃん?
メールでのアラートをやめたほうがいい5つの理由 | PagerDuty by Digital Stacks
CPU 使用率と、ディスク容量について
❌ Load Average をトリガーにする
LA は CPU の使用率を見るものとしては正しいが負荷がどうとかで見るのは間違い。 実行中のプロセスが多ければ必然的に LA も高くなる。 参考:https://www.techscore.com/blog/2017/12/08/how_is_load_average_calculated/
ディスク容量の監視
80% ≥ warning にするとあとで良いやってなる。 じゃあ 95% ≥ critical はどうでしょう。あと 5% でその VM は障害になるかもしれない!
DB は例外で考えても良いかも DELETE + ALTER TABLE で容量を確保する際に、ALTER でコピー分の容量も必要なため 80 ≥ critical ぐらいが良さそう (スレーブで一時的に停止できるなら、停止してからボリュームの拡張でも良さそう)
自動復旧について
例えばプロセスが落ちたトリガーのフックに、プロセスの再起動を仕込んだりする。 人間の手が不要で勝手にプロセスが立ち上がってくれる。
MySQL が OOM で該当プロセスが死んだ場合は注意 OOM は SIGKILL を発行するが、もし MySQL の設定が sync_binlog = 0 や doublewrite ≠ 1 の場合はプロセスは起動できてもレプリが死んでたりするので MySQL に関しては自動復旧しないことをオススメする。
アラートメッセージは大事
何故これを監視して、どう影響を与えて、どうやって復旧するのか
をアラートメッセージに書いとくのが重要。アラートを見た人(サービスの担当者が見ても対応ができる)
監視すべきメトリクス
1. レスポンスタイム 95 パーセンタイルで 5秒超え
コメント例
95パーセンタイルでレスポンスタイムが5秒を超えてる。 レスポンスタイムの影響は大きいよ。 何が原因かは調べてください。
平均値でレスポンスタイムを計測するのは間違い
2. ステータスコード 5xx が n分間に m%
コメント例
Apache(Tomcat)が HTTP 5xx を 1分間で 2% 以上レスポンスを返してる。 DB や Memcached、負荷等、何かが要因になってる可能性有り。
3. プロセス死活監視
例えば API サーバーの1台のプロセスが死んだだけでサービスにクリティカルな影響を与えるとは限らないため、API サーバーのうち n/m が死んだらクリティカルにするっていうのも良い。もしくは Critical ではなく Warning で出すとか。ちなみに、Mackerel だとできない。
4. ディスク容量
≠ DB warning ≥ 90 critical ≥ 95
コメント例
空き容量が残り 5% しかない 対応しないと障害になる可能性有り 主な対応方法は不要なファイルを削除していく、コマンド例は下記を参照 まずはルートから辿っていく
du -sh /* | sort -hr | head -n 10
更にそのディレクトリを辿ってくdu -sh /directory/* | sort -hr | head -n 10
= DB warning ≥ 70 critical ≥ 80
ただ EC2 にしろ、OpenStack(Cinder)にしろ容量の拡張ができるので DB も 95 クリティカルでも良いかもしれない。
コメント例
DB の空き容量が残り 20% しかない DELETE + ALERT TABLE ではコピー分の容量も必要なため 80 >= critical にしてる メンテ無しでやるなら レコードを削除して
ALTER TABLE t1 ENGINE=InnoDB;
メンテが挟めるなら 適当にテーブル作って、select insert -> rename -> drop 詳細 https://bit.ly/31WIZLg
5. MySQL の監視
5.1 レプリ遅延
critical ≥ 3 ※ Mackerel でも取れるけど、Mackerel の計測間隔は最低1分なのでここを許容できるかどうか
コメント例
レプリ遅延が3秒以上遅れてる。 この閾値は3分間の平均値が3回連続でトリガーされる値で、継続的にレプリ遅延が発生中。 データの取得に不整合がある可能性有り。 今すぐできることはあらず。 解消されない場合、メンテを入れることも視野に入れてください。
5.2 スロークエリ
warning ≥ 10 (critical にしないのは、レスポンスタイムで賄えるから) ※ Mackerel でも取れるけど、Mackerel の計測間隔は最低1分なのでここを許容できるかどうか
コメント例
スロークエリが直近1分間で10クエリ以上有り。 Table Locks Waited も多い場合、ロックの競合が起きてる可能性有り。クエリの見直しや、他の要因も考えられる。
5.3 コネクション数の割合
warning ≥ 70 critical ≥ 80
コメント例
Max Connections の使用率が 70% / 80% を超えた。 これが高負荷時でなら問題はなく閾値の調整を。 通常時に起きている場合は Max Connections の引き上げを検討して。 もし、Max Connections に当たると新規コネクションが貼れなくなるので普通に障害になる。
SPA は SSR を使わなくても Google 検索で1位取れる
巷では SPA ( not SSR ) は SEO に不利とか、SEO 専門の人がそう言ってたりする。
Google で「SPA SEO」とかで検索すると、トップページはできるだけ SSR にしたほうがいい とか見られる。
けど現に SPA ( not SSR ) で作った Webサービスは2つのキーワードで SEO 1位を取ってる。
uploader.xzy.pw
一部に SSR を使ったりせずに本当にピュア?な SPA で出来てます。
Google のクローラーは昨年末に Chrome のバージョンが上がり、SPA のレンダリングができるようになったけど、
Yahoo 検索は心配かと想いきや、Yahoo 検索でも1位取れてる。
あんまり気にしなくても良いかもしれない。
まだ S3 + CloudFront で消耗してるの?Cloudflare + Backblaze の方が安いよ
Web サービスを提供してたり、アセットの配布で S3 + CloudFront を使ってる人は多いと思います。
が、この度 Backblaze が S3 Compatible API をリリースしたので Cloudflare と組み合わせればマイグレーションのコストを少なく(AWS SDK も使えるし、AWS CLI も使える)Backblaze へ移行できる。
blog.cloudflare.com
www.backblaze.com
コストの比較
10TB のファイルを毎月 10TB を送信し、1000万回 GETを想定すると
S3 + CloudFront
S3
ストレージ:0.025/GB * 10,000GB = $250
送信:$0
CloudFront
転送量:0.114/GB * 10,000GB = $1,140
リクエスト:0.0090/1万 * 1000 = $9
合計
$250 + $1,140 = $1,399 / 月
注意
Backblaze の S3 Compatible API はリリースされた間もないことも有り
Server Side Encryption と CORS に対応していない。
www.backblaze.com
これを Backblaze へ移行しようと思ったけど、CORS も SSE も対応してなかったのでできなかった。
AWS EC2 m6g, c6g, r6g は本当に速くなったのか
m系だけで比較 UnixBench
AMI
m6g.large: amzn2-ami-hvm-2.0.20200406.0-arm64-gp2
m5.large: amzn2-ami-hvm-2.0.20200406.0-x86_64-gp2
# yum update -y # yum groupinstall '@Development Tools' # wget https://github.com/kdlucas/byte-unixbench/archive/master.zip # unzip master.zip
m5.large
2 CPUs in system; running 1 parallel copy of tests Dhrystone 2 using register variables 38850809.4 lps (10.0 s, 7 samples) Double-Precision Whetstone 4363.1 MWIPS (9.1 s, 7 samples) Execl Throughput 4709.3 lps (30.0 s, 2 samples) File Copy 1024 bufsize 2000 maxblocks 594108.7 KBps (30.0 s, 2 samples) File Copy 256 bufsize 500 maxblocks 154550.5 KBps (30.0 s, 2 samples) File Copy 4096 bufsize 8000 maxblocks 1970248.9 KBps (30.0 s, 2 samples) Pipe Throughput 747070.6 lps (10.0 s, 7 samples) Pipe-based Context Switching 66250.9 lps (10.0 s, 7 samples) Process Creation 11919.4 lps (30.0 s, 2 samples) Shell Scripts (1 concurrent) 7624.2 lpm (60.0 s, 2 samples) Shell Scripts (8 concurrent) 1214.2 lpm (60.0 s, 2 samples) System Call Overhead 412715.6 lps (10.0 s, 7 samples) System Benchmarks Index Values BASELINE RESULT INDEX Dhrystone 2 using register variables 116700.0 38850809.4 3329.1 Double-Precision Whetstone 55.0 4363.1 793.3 Execl Throughput 43.0 4709.3 1095.2 File Copy 1024 bufsize 2000 maxblocks 3960.0 594108.7 1500.3 File Copy 256 bufsize 500 maxblocks 1655.0 154550.5 933.8 File Copy 4096 bufsize 8000 maxblocks 5800.0 1970248.9 3397.0 Pipe Throughput 12440.0 747070.6 600.5 Pipe-based Context Switching 4000.0 66250.9 165.6 Process Creation 126.0 11919.4 946.0 Shell Scripts (1 concurrent) 42.4 7624.2 1798.2 Shell Scripts (8 concurrent) 6.0 1214.2 2023.7 System Call Overhead 15000.0 412715.6 275.1 ======== System Benchmarks Index Score 1021.9 ------------------------------------------------------------------------ Benchmark Run: 月 5月 18 2020 09:31:17 - 09:59:20 2 CPUs in system; running 2 parallel copies of tests Dhrystone 2 using register variables 55968401.9 lps (10.0 s, 7 samples) Double-Precision Whetstone 7327.2 MWIPS (9.3 s, 7 samples) Execl Throughput 6701.5 lps (30.0 s, 2 samples) File Copy 1024 bufsize 2000 maxblocks 789430.5 KBps (30.0 s, 2 samples) File Copy 256 bufsize 500 maxblocks 204553.5 KBps (30.0 s, 2 samples) File Copy 4096 bufsize 8000 maxblocks 2679537.8 KBps (30.0 s, 2 samples) Pipe Throughput 1012333.1 lps (10.0 s, 7 samples) Pipe-based Context Switching 282192.2 lps (10.0 s, 7 samples) Process Creation 19656.0 lps (30.0 s, 2 samples) Shell Scripts (1 concurrent) 8858.5 lpm (60.0 s, 2 samples) Shell Scripts (8 concurrent) 1224.7 lpm (60.0 s, 2 samples) System Call Overhead 540338.4 lps (10.0 s, 7 samples) System Benchmarks Index Values BASELINE RESULT INDEX Dhrystone 2 using register variables 116700.0 55968401.9 4795.9 Double-Precision Whetstone 55.0 7327.2 1332.2 Execl Throughput 43.0 6701.5 1558.5 File Copy 1024 bufsize 2000 maxblocks 3960.0 789430.5 1993.5 File Copy 256 bufsize 500 maxblocks 1655.0 204553.5 1236.0 File Copy 4096 bufsize 8000 maxblocks 5800.0 2679537.8 4619.9 Pipe Throughput 12440.0 1012333.1 813.8 Pipe-based Context Switching 4000.0 282192.2 705.5 Process Creation 126.0 19656.0 1560.0 Shell Scripts (1 concurrent) 42.4 8858.5 2089.3 Shell Scripts (8 concurrent) 6.0 1224.7 2041.2 System Call Overhead 15000.0 540338.4 360.2 ======== System Benchmarks Index Score 1523.2
m6g.large
2 CPUs in system; running 1 parallel copy of tests Dhrystone 2 using register variables 40582507.5 lps (10.0 s, 7 samples) Double-Precision Whetstone 5912.0 MWIPS (9.6 s, 7 samples) Execl Throughput 7006.5 lps (30.0 s, 2 samples) File Copy 1024 bufsize 2000 maxblocks 1041850.6 KBps (30.0 s, 2 samples) File Copy 256 bufsize 500 maxblocks 291910.6 KBps (30.0 s, 2 samples) File Copy 4096 bufsize 8000 maxblocks 2952450.1 KBps (30.0 s, 2 samples) Pipe Throughput 1790930.7 lps (10.0 s, 7 samples) Pipe-based Context Switching 134169.8 lps (10.0 s, 7 samples) Process Creation 11434.1 lps (30.0 s, 2 samples) Shell Scripts (1 concurrent) 8528.6 lpm (60.0 s, 2 samples) Shell Scripts (8 concurrent) 1658.2 lpm (60.0 s, 2 samples) System Call Overhead 1733100.7 lps (10.0 s, 7 samples) System Benchmarks Index Values BASELINE RESULT INDEX Dhrystone 2 using register variables 116700.0 40582507.5 3477.5 Double-Precision Whetstone 55.0 5912.0 1074.9 Execl Throughput 43.0 7006.5 1629.4 File Copy 1024 bufsize 2000 maxblocks 3960.0 1041850.6 2630.9 File Copy 256 bufsize 500 maxblocks 1655.0 291910.6 1763.8 File Copy 4096 bufsize 8000 maxblocks 5800.0 2952450.1 5090.4 Pipe Throughput 12440.0 1790930.7 1439.7 Pipe-based Context Switching 4000.0 134169.8 335.4 Process Creation 126.0 11434.1 907.5 Shell Scripts (1 concurrent) 42.4 8528.6 2011.5 Shell Scripts (8 concurrent) 6.0 1658.2 2763.6 System Call Overhead 15000.0 1733100.7 1155.4 ======== System Benchmarks Index Score 1649.2 ------------------------------------------------------------------------ Benchmark Run: 月 5月 18 2020 09:35:22 - 10:03:18 2 CPUs in system; running 2 parallel copies of tests Dhrystone 2 using register variables 81093723.6 lps (10.0 s, 7 samples) Double-Precision Whetstone 11816.9 MWIPS (9.6 s, 7 samples) Execl Throughput 11173.2 lps (30.0 s, 2 samples) File Copy 1024 bufsize 2000 maxblocks 1311901.0 KBps (30.0 s, 2 samples) File Copy 256 bufsize 500 maxblocks 405826.8 KBps (30.0 s, 2 samples) File Copy 4096 bufsize 8000 maxblocks 3190465.5 KBps (30.0 s, 2 samples) Pipe Throughput 3586570.9 lps (10.0 s, 7 samples) Pipe-based Context Switching 638429.7 lps (10.0 s, 7 samples) Process Creation 22963.7 lps (30.0 s, 2 samples) Shell Scripts (1 concurrent) 12549.6 lpm (60.0 s, 2 samples) Shell Scripts (8 concurrent) 1706.7 lpm (60.0 s, 2 samples) System Call Overhead 2563990.0 lps (10.0 s, 7 samples) System Benchmarks Index Values BASELINE RESULT INDEX Dhrystone 2 using register variables 116700.0 81093723.6 6948.9 Double-Precision Whetstone 55.0 11816.9 2148.5 Execl Throughput 43.0 11173.2 2598.4 File Copy 1024 bufsize 2000 maxblocks 3960.0 1311901.0 3312.9 File Copy 256 bufsize 500 maxblocks 1655.0 405826.8 2452.1 File Copy 4096 bufsize 8000 maxblocks 5800.0 3190465.5 5500.8 Pipe Throughput 12440.0 3586570.9 2883.1 Pipe-based Context Switching 4000.0 638429.7 1596.1 Process Creation 126.0 22963.7 1822.5 Shell Scripts (1 concurrent) 42.4 12549.6 2959.8 Shell Scripts (8 concurrent) 6.0 1706.7 2844.5 System Call Overhead 15000.0 2563990.0 1709.3 ======== System Benchmarks Index Score 2775.8
価格も m5.large より安いしパフォーマンスも良いなら、m6g 使います(ARM ベースで問題ないなら)
InnoDB Cluster のアップグレードをやる
8.0.19 -> 8.0.20
1. MySQL Shell を最新にする
2. MySQL Router を最新にする
3. メタデータを最新にする
MySQL db01:33060+ ssl JS > dba.upgradeMetadata() NOTE: Installed metadata at 'db02.luis.local:3306' is up to date (version 2.0.0). Metadata state is consistent and a restore is not necessary.
今回は必要なかった。
メタデータの互換性がないアップグレードで、MySQL Shell のアップグレードを忘れると、InnoDB Cluster の操作ができなくなるので注意
4. MySQL Server を最新にする
普通にアップグレードすれば OK
スレーブから、最後にプライマリー
もし、アップグレード後の検証が必要なら set persist group_replication_start_on_boot=false; をしてから停止させたほうが吉
MySQL db02:33060+ ssl JS > c.status() { "clusterName": "main", "defaultReplicaSet": { "name": "default", "primary": "db03.luis.local:3306", "ssl": "REQUIRED", "status": "OK", "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.", "topology": { "db01.luis.local:3306": { "address": "db01.luis.local:3306", "mode": "R/O", "readReplicas": {}, "replicationLag": null, "role": "HA", "status": "ONLINE", "version": "8.0.20" }, "db02.luis.local:3306": { "address": "db02.luis.local:3306", "mode": "R/O", "readReplicas": {}, "replicationLag": null, "role": "HA", "status": "ONLINE", "version": "8.0.20" }, "db03.luis.local:3306": { "address": "db03.luis.local:3306", "mode": "R/W", "readReplicas": {}, "replicationLag": null, "role": "HA", "status": "ONLINE", "version": "8.0.20" } }, "topologyMode": "Single-Primary" }, "groupInformationSourceMember": "db03.luis.local:3306" }
xfs + lvm 拡張
拡張子した分のパーティションを作る
fdisk -l /dev/sda n p
ボリュームグループを作る
vgcreate /dev/sda3
拡張
vgextend cl /dev/sda3 lvextend -l +100%FREE /dev/cl/root xfs_growfs /
CircleCI で GitHub のラベルを見て自動でタグをプッシュする
流れ
1. PR を作る、ラベルで major, minor, patch のいずれをつける
2. master へマージする。
3. CircleCI がラベルを見て新しいタグをプッシュする
CircleCI
GHE 環境なのでところどころ読み直してください。
雰囲気で伝わってほしい。
push_tag: <<: *build steps: - checkout - add_ssh_keys - run: name: push new version tag command: | PR_NUMBER=$( git log ${CIRCLE_SHA1} --oneline -1 | awk '{print $5}' | sed -s 's/#//' ) label=$( curl -s -u u:p https://hoge/pulls/${PR_NUMBER} | jq -r '.labels[0].name' ) latest_version=$( curl -s -u u:p https://hoge/tags | jq -r '.[0].name' ) new_version=$( bash .circleci/push_tag.sh "${label}" "${latest_version}" ) git tag ${new_version} git push --tags
push_tag.sh
新しいタグ(バージョン)を作って、GHE にプッシュする
#!/usr/bin/env bash set -eu readonly semantic_version="${1}" readonly latest_version="${2}" # v2.15.1 -> 2.15.1 version=$( echo "${latest_version}" | sed -e 's/v//g' ) # 2.15.1 -> [ 2, 15, 1 ] version_split=( ${version//./ } ) new_major_version="${version_split[0]}" new_minor_version="${version_split[1]}" new_patch_version="${version_split[2]}" case "${semantic_version}" in "major" ) new_major_version=$(( new_major_version + 1 )) ;; "minor" ) new_minor_version=$(( new_minor_version + 1 )) ;; "patch" ) new_patch_version=$(( new_patch_version + 1 )) ;; * ) echo "please add a label 'major', 'minor', 'patch'" exit 1 ;; esac new_version=$( echo "v${new_major_version}.${new_minor_version}.${new_patch_version}" ) echo "${new_version}"
luis@ubuntu ~selected/.circleci (fix/datadog●●)$ bash push_tag.sh patch v2.15.1 v2.15.2 luis@ubuntu ~selected/.circleci (fix/datadog●●)$ bash push_tag.sh minor v2.15.1 v2.16.1 luis@ubuntu ~selected/.circleci (fix/datadog●●)$ bash push_tag.sh major v2.15.1 v3.15.1
問題点
- 複数のラベルがあって、最初に major, minor, patch がないとエラーになる。
- patch を2つ上げたいけど、1つしか上げられない
気が向いたら直す。
これでタグのプッシュのし忘れとか無くなって(・∀・)イイネ!!