@Konboi memo

主に技術に関してつらつらと。

テストをコケたままにしない理由

はじめに

現在担当しているモバイルアプリゲームではマスターデータのテストを行っています。

  • リレーション先のデータが存在しているか
  • 入力されているデータが想定しているデータか
  • シソーラスのチェック

などヒューマンエラーがおきやすい所、おきた所を防ぐためにマスターデータに対してもテストを行っています。

本題

表題だけみると当たり前のことですが、運用をしていると施策のタイミング変更などやんごとない理由で上記であげたテストがコケる不十分な状態のマスターデータをデプロイする必要が出てきます。 CIサーバーではテストを回すと常にそのテストがコケた状態になります。

それがチーム内で共有されているとしてもテストがコケた状態だと それ今はコケるテストだから という状態が続く可能性があります。

その状態が続くと同一テスト内で他のテストがコケた場合に気づきにくくなります。

じゃあどうする

なので自分の場合は、そのようなケースが出てきた時はテストコードを修正し該当のデータだけテストをスキップさせる という方法を取っています

まとめ

テストこけっぱなしにするの(・へ・)ヨクナイ!

本番のマスターのDBでhistoryを残さないために

本番のマスターのDBで直接SQLを叩くことはほとんどありません。 が、どうしてもやんごとない事情でSQLを直接叩かないといけない時がでてきます。

SQLの実行自体はいいのですが、他の人が誤ってhistoryから実行したら怖いですね。 そうならないように現在のプロジェクトでは master_mysql のようなコマンドを用意し

どうしても直接本番のマスターDBでSQLを実行しないと行けない場合はそれ経由で実行しています。

master_mysql の中身は

#!/bin/sh
MYSQL_HISTFILE=/dev/null mysql -uuser_name -hhostname -ppassword db_name "$@"

こんな感じになってます。 これで他の人がhistory経由でミスすることが多少は防げると思います。

あわせて読みたい

夏のドキッとしたmigrateの話

はじめに

現在担当しているプロジェクトではmigrateにGitDDLを使っております。 先日本番環境でそれなりにデータが入ったテーブルに対してALTERをかけました。 その際に起こったちょっとドキッとする話を今後の自分のためにも書いてこうと思います。

GitDDLについての記事はこちら: #5「GitDDLまじイノベーティブ」 tech.kayac.com Advent Calendar 2012

起こったこと

下記の用な変更を本番のDBに対して適用することになりました

CREATE TABLE atarashi_table (
  id integer(10) unsigned NOT NULL auto_increment,
  nanka text NULL,
  iroiro text NULL,
  PRIMARY KEY (id),
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;

ALTER TABLE oki_table ADD COLUMN tsuika_column integer(10) unsigned NOT NULL DEFAULT 0;

migrate自体はこの様なコマンドを用意して実行しています

./env-exec perl script/db/migrate

それなりに大きいテーブルだったので、stgでの確認も十分行い大丈夫だろうと実行

しかし現実は甘くはなかった

Connection Time Out…

とmigrate中にmysqlとのconnectionが切断された。 dbの設定で

mysql_connect_timeout=15
mysql_write_timeout=15
mysql_read_timeout=10

と設定していたので実行中にその時間を経過してしまったようである。 非常に焦る。

恐る恐る show tables をしてみると atarashi_table はできている ただ、この段階では oki_table には まだ tuika_column が追加されていない。

とりあえず落ち着いて現状を他のPGに共有。 すると先輩エンジニアから mysql のプロセスを見てみる。

mysql> SHOW PROCESSLIST;

するとcolumnを追加していると思われるプロセスが。 少し待つと oki_tabletsuika_column が追加されている事を確認。

とりあえずは一安心。

ただ migrate コマンド自体は途中で失敗したため git_ddl_version が更新されず

./env-exec perl script/db/migrate

を再度行おうとすると

CREATE TABLE atarashi_table (
  id integer(10) unsigned NOT NULL auto_increment,
  nanka text NULL,
  iroiro text NULL,
  PRIMARY KEY (id),
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;

ALTER TABLE oki_table ADD COLUMN tsuika_column integer(10) unsigned NOT NULL DEFAULT 0;

の差分がでて実行すると

atarashi_table already exists

と言われ もちろんだけれど実行できない。 なので無事migrateが終わっているstaging環境のgit_ddl_version のversionでupdateすることに

UPDATE git_ddl_version SET version = ‘hogehogeversion’

なんとか問題なく終了

今後の対策

migrateの実行ファイル内で connection time を長めに設定し直すか、migrateを実行する環境の設定ファイルで長めにするかのどちらかかと思っています。

まとめ

  • migrationの実行はいつもドキドキする
  • 何かあっても大丈夫なようにメンテナンスは余裕のある見積もりを。
  • それでも何か合った時はまず共有しましょう
  • やばいと思ったら深呼吸
  • mysql> SHOW PROCESSLIST;

今回はconnectionが切られたけどプロセスは生きていて大事には至らなくて本当によかった事案。 今後も起こりえる可能性があるので、未来の自分のためのエントリになります。

雑にindexの効果を調べた

メンターをしている後輩くんのソースをレビューしててindexが足りてなかった。 それがなんでダメなのか雑に調べた。

  • player_hoge
    • id
    • player_id
    • index_id
    • created_at
    • updated_at

みたいなテーブルがある。 使われ方はplayer_idとindex_idの2つでselectする機会が多い。

そのテーブルにplayer_id のみindexが貼ってあった。 こういう場合は player_id と index_id の2つにindexを貼ったほうが早くなる。 はず。

なので調べるように

  • index 貼ってない
  • player_id いindexを貼った場合
  • player_id, index_id に index を貼った場合

sqlはこんな感じ

DROP TABLE IF EXISTS `no_index`;

CREATE TABLE `no_index` (
  `id` INTEGER unsigned NOT NULL auto_increment,
  `player_id` INTEGER unsigned NOT NULL DEFAULT 0,
  `index_id` INTEGER unsigned NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;

DROP TABLE IF EXISTS `one_index`;

CREATE TABLE `one_index` (
  `id` INTEGER unsigned NOT NULL auto_increment,
  `player_id` INTEGER unsigned NOT NULL DEFAULT 0,
  `index_id` INTEGER unsigned NOT NULL DEFAULT 0,
  INDEX `player_id_idx` (`player_id`),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;

DROP TABLE IF EXISTS `both_index`;

CREATE TABLE `both_index` (
  `id` INTEGER unsigned NOT NULL auto_increment,
  `player_id` INTEGER unsigned NOT NULL DEFAULT 0,
  `index_id` INTEGER unsigned NOT NULL DEFAULT 0,
  INDEX `player_id_index_id_idx` (`player_id`, `index_id`),
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8mb4;

使ったデータはこんな感じで作りました。 で調査ようのscriptはこんな感じです

use DBI;
use DBD::mysql;
use Benchmark qw/timethese cmpthese/;

my $d = 'DBI:mysql:index_research';
my $u = 'root';
my $p = '';
my $dbh = DBI->connect($d, $u, $p);

my $result = timethese(10, {
    'player_id index_id use no index' => sub {
        for my $id (1..100) {
            my $player_id = $id * 20;
            my $sql = "SELECT * FROM no_index WHERE player_id = $player_id  AND index_id = $id";
            my $sth = $dbh->prepare($sql);
            $sth->execute;
            $sth->finish;
        }
    },
    'player_id index_id use player_id index' => sub {
        for my $id (1..100) {
            my $player_id = $id * 20;
            my $sql = "SELECT * FROM one_index WHERE player_id = $player_id  AND index_id = $id";
            my $sth = $dbh->prepare($sql);
            $sth->execute;
            $sth->finish;
        }
    },
    'player_id index_id use player_id index_id index' => sub {
        for my $id (1..100) {
            my $player_id = $id * 20;
            my $sql = "SELECT * FROM both_index WHERE player_id = $player_id  AND index_id = $id";
            my $sth = $dbh->prepare($sql);
            $sth->execute;
            $sth->finish;
        }
    },
});

cmpthese $result;

で結果が以下

player_id index_id use no index                 100.0/s                              --                                   -30%                                            -70%
player_id index_id use player_id index            143/s                             43%                                     --                                            -57%
player_id index_id use player_id index_id index   333/s                            233%                                   133%                                              --

やっぱりplayer_id と index_id 両方にindex貼ったほうが早かった。

じゃあ、 player_id だけ select して取る場合はどちらが早いのかと思ったのでそれも調べた

my $result_only_player_id = timethese(10, {
    'player_id use no index' => sub {
        for my $id (1..100) {
            my $player_id = $id * 20;
            my $sql = "SELECT * FROM no_index WHERE player_id = $player_id";
            my $sth = $dbh->prepare($sql);
            $sth->execute;
            $sth->finish;
        }
    },
    'player_id use player_id index' => sub {
        for my $id (1..100) {
            my $player_id = $id * 20;
            my $sql = "SELECT * FROM one_index WHERE player_id = $player_id";
            my $sth = $dbh->prepare($sql);
            $sth->execute;
            $sth->finish;
        }
    },
    'player_id use player_id index_id index' => sub {
        for my $id (1..100) {
            my $player_id = $id * 20;
            my $sql = "SELECT * FROM both_index WHERE player_id = $player_id";
            my $sth = $dbh->prepare($sql);
            $sth->execute;
            $sth->finish;
        }
    },
});

cmpthese $result_only_player_id;

結果がこちら

player_id use no index                 90.9/s                     --                          -55%                                   -64%
player_id use player_id index           200/s                   120%                            --                                   -20%
player_id use player_id index_id index  250/s                   175%                           25%                                     --

player_id だけの方が早いかと思ったけどそうでもなかった。 データ量がおこまで多くないと違うのかもしれない。

まとめ

index貼るときはどう使うかも考えて貼ったほうがいいですね。