kakakakakku blog

Weekly Tech Blog: Keep on Learning!

計画する技術

今日は社内勉強会で「計画する技術」というタイトルで発表をした.

前から少し「計画」のところに課題感があって,そのあたりの知識を組織に広めて欲しいというオーダーもあったため,僕が日々考えていることを言語化して,発表することにした.僕は今までに様々な「計画」の経験があり,実際に今の組織でも難易度の高いプロジェクトを何度も計画し,遂行してきたため,「計画」に対する信頼残高は増えているのではないかと思う.

発表資料

意識したこと

今回,発表資料を作るときに意識したことが2個あった.他にも話したいことはたくさんあったけど,組織マネジメントの話はまた別でしようと思って,あくまで「計画」に特化した話にした.

  • 明日からすぐに使える話をする
  • 開発プロセスに依存しない話をする

1. 明日からすぐに使える話をする

原理原則すぎる話や,難しい法則の話は控えるようにした.そういう話をしてしまうと,その場では「おー,そういう法則があるのか」と,わかった気になるけど,いざ自分で咀嚼しようとすると「で,どうしたら良いんだろう?」となってしまう可能性が高いと思ったから.例えば「不確実性コーン」の話とか,「バッファ計算」の話とか,「PMBOK」の話とか.

2. 開発プロセスに依存しない話をする

今の組織の開発プロセスは,ウォーターフォールでもないし,スクラムでもないし,カンバンでもなく,また何かしらの「型を崩した」ものでもなく,独特なものになっていると思う(個人的には悪い面もあるとは思う).そのときに「スクラムではこれは必須だから」という話をしても,他人事のように聞こえてしまう可能性が高いと思って,特定の開発プロセスに依存しない話をした.あくまでこれは即効性を意識したためで,抜本的に開発プロセスを見直す機会があるのであれば,ゼロから立て直したいと思う.

よく読む本

前に「カンバン勉強会」を実施したときの資料

kakakakakku.hatenablog.com

参考になる記事

PHP の Elasticsearch クライアント Elastica で Amazon ES に接続する

PHP から Elasticsearch を操作するために Elastica というライブラリを使っている.今まで EC2 で運用していた Elasticsearch を Amazon ES に移行する話があり,Elastica から Amazon ES を操作するときに悩むことが多かったため,まとめておこうと思う.

github.com

ちなみに,僕は使ったことはないけれど,Elastic 社から公式に提供されている elasticsearch-php というライブラリもある.ただ,Elastic 社の公式ライブラリだと,積極的に Amazon ES 対応をすることはないだろうし,Amazon ES を使う場合は,サードパーティライブラリの Elastica の方が良い気がする.

github.com

Amazon ES に接続する方法

Amazon ES に接続する方法を復習しておくと,大きく3種類ある.

  • リソースベース
  • IP ベース
  • IAM ユーザーとロールベース

docs.aws.amazon.com

今回のように,アプリケーションから Amazon ES に接続する場合は IAM を使う.IAM Role を使うのがベスト.必要に応じて IP 設定を変更して運用することも考えられるが,Amazon ES で IP 設定を変更すると,ノードが全台作り直されてしまうため,クラスタのデータ規模によっては1時間程度待たされることもあるので,実質不可能だと思うし,そもそも,アーキテクチャ的に微妙すぎる.

また,IP ベースの場合は未署名の匿名リクエストも受け付けることができるが,IAM を使う場合,署名付きリクエスト(AWS 署名バージョン 4)を送る必要がある.よって,ライブラリが署名付きリクエストに対応している必要がある.

docs.aws.amazon.com

docs.aws.amazon.com

Elastica から Amazon ES に接続する

github.com

Issue を見ると,Elastica は既に署名付きリクエストの対応がされているが,実装例などのドキュメントが全くなくて,よくわからなかった.困ったので Gitter で聞いてみたら,すぐ教えてもらうことができて助かった.簡単過ぎるけど,今回の構成をまとめた.

f:id:kakku22:20170407002340j:plain

ライブラリをインストールする

署名付きリクエストを送るためには,Elastica 3.1.1 以上が必要になる.ただし,このままで実行すると,以下のエラーが出る.

Class 'GuzzleHttp\Psr7\Uri' not found

Elastica の composer.json を見ると,suggest プロパティに以下の設定がある.suggest とは,基本的には必要ないけど,入れておくと便利な依存パッケージのことを意味している.Ruby の Gemfile にはそういう概念がないため,今まで知らなかった.

{
    (中略)
    "require-dev": {
        "aws/aws-sdk-php": "~3.0",
        "guzzlehttp/guzzle": "~6.0"
    },
    "suggest": {
        "aws/aws-sdk-php": "Allow using IAM authentication with Amazon ElasticSearch Service",
        "egeloen/http-adapter": "Allow using httpadapter transport",
        "guzzlehttp/guzzle": "Allow using guzzle 6 as the http transport",
        "monolog/monolog": "Logging request"
    },
    (中略)    
}

よって,Amazon ES に IAM で接続する場合は,以下のような composer.json になる(バージョン設定はご自由に).

{
    (中略)
    "require": {
        "ruflin/Elastica": "~3.2",
        "aws/aws-sdk-php": "~3.25",
        "guzzlehttp/guzzle": "~6.2"
    },
    (中略)
}

まだ aws/aws-sdk-php v2 を使っていると動かないため,合わせて aws/aws-sdk-php をバージョンアップする必要がある.

Elastica のクライアント実装

以下のように transportAwsAuthV4 を設定して,aws_region を設定すると,Amazon ES に IAM で接続できるようになる.

$client = new Elastica\Client(
    array(
        'host'       => 'xxx.ap-northeast-1.es.amazonaws.com',
        'port'       => 80,
        'transport'  => ['type' => 'AwsAuthV4', 'postWithRequestBody' => true],
        'aws_region' => 'ap-northeast-1'
    )
);

もし IAM Role ではなく,クレデンシャルを埋め込む場合(非推奨な実装)は,以下のようになる.

$client = new Elastica\Client(
    array(
        'host'                  => 'xxx.ap-northeast-1.es.amazonaws.com',
        'port'                  => 80,
        'transport'             => ['type' => 'AwsAuthV4', 'postWithRequestBody' => true],
        'aws_access_key_id'     => 'yyy',
        'aws_secret_access_key' => 'xxx',
        'aws_region'            => 'ap-northeast-1'
    )
);

まとめ

  • Elastica は Amazon ES の接続をサポートしている
  • ドキュメントが詳しくないため,困ったら Gitter で聞いてみると良い
  • 必ず IAM Role を使おう

参考情報 : Amazon ES を運用するときに知っておくと便利なこと

kakakakakku.hatenablog.com

mysqldiff を使って継続的に MySQL のデータベーススキーマの差分をチェックする

最近,環境ごとのデータベーススキーマの差分をチェックする機会があった.プロダクション環境とステージング環境ならまだしも,開発環境だと検証のために追加したインデックスがそのままになっていたり,開発が途中で止まってしまって日の目を見ることがなかったテーブルが残っていたり,そういうことって比較的あるのではないかなと思う.特に今の環境だと,マイグレーションの仕組みが整っていないという課題もあり,より一層,データベーススキーマに差分が出やすくなってしまっている.

今回は MySQL から公式に提供されている mysqldiff というツールを使ってデータベーススキーマの差分をチェックした.

mysqldiff をインストールする

mysqldiff は MySQL Utilities という MySQL の管理ツールパッケージの中に同梱されている.現在だと v1.6 が最新になっている.

yum で簡単にインストールすることができる(リポジトリは適宜指定すること).

$ sudo yum install -y mysql-utilities

$ which mysqldiff
/usr/bin/mysqldiff

$ mysqldiff --version
MySQL Utilities mysqldiff version 1.6.5
License type: GPLv2

mysqldiff を使う

基本的には以下のように使う.データベースだけではなく,テーブルを指定して限定的な差分をチェックすることもできる.

  • --server1 … 比較するデータベースの接続設定
  • --server2 … 比較するデータベースの接続設定
  • --force … デフォルトだと,差分が1件出ると止まってしまうため,最後まで流すために指定する
  • --skip-table-options … テーブルのオプションに差分があっても無視するために指定する
  • --changes-for=server2 … デフォルトだと server1 を修正する形で認識されるため,server1 を起点として server2 を修正する形にする
$ mysqldiff --server1=root@localhost --server2=root@localhost database_a:database_b --force --skip-table-options
$ mysqldiff --server1=root@localhost --server2=root@localhost database_a.users:database_b.users --force --skip-table-options

サンプルとして users テーブルの差分をチェックしてみた.以下の例では email_UNIQUE 制約が存在しないことをチェックできている.

$ mysqldiff --server1=root@localhost --server2=root@localhost database_a.users:database_b.users --force --skip-table-options --changes-for=server2
# server1 on localhost: ... connected.
# server2 on localhost: ... connected.
# Comparing database_a.users to database_b.users           [FAIL]
# Transformation for --changes-for=server2:
#

--- database_a.users
+++ database_b.users
(中略)
   `email` varchar(255) DEFAULT NULL,
(中略)
   `updated_at` int(10) unsigned DEFAULT NULL,
   `deleted_at` int(10) unsigned DEFAULT NULL,
   PRIMARY KEY (`id`),
-  UNIQUE KEY `email_UNIQUE` (`email`),
# Compare failed. One or more differences found.

完全に一致している場合は,以下のように [PASS] と表示される.

$ mysqldiff --server1=root@localhost --server2=root@localhost database_a.users:database_b.users --force --skip-table-options --changes-for=server2
# server1 on localhost: ... connected.
# server2 on localhost: ... connected.
# Comparing database_a.users to database_b.users    [PASS]
# Success. All objects are the same.

差分を SQL 形式で出力する

--difftype=sql を指定すると,データベーススキーマを一致させるために必要な SQL を出力することができる.

$ mysqldiff --server1=root@localhost --server2=root@localhost database_a.users:database_b.users --force --skip-table-options --changes-for=server2 --difftype=sql
# server1 on localhost: ... connected.
# server2 on localhost: ... connected.
# Comparing database_a.users to database_b.users           [FAIL]
# Transformation for --changes-for=server2:
#

ALTER TABLE `database_b`.`users`
  ADD UNIQUE INDEX email_UNIQUE (email);

# Compare failed. One or more differences found.

微妙なところ : AUTO_INCREMENT だけを無視することができない

比較対象のデータベースが TRUNCATE されてなく,AUTO_INCREMENT を保持している場合,デフォルトだと差分として検知されてしまう.そのために --skip-table-options を使っているが,これだと AUTO_INCREMENT だけでなく ENGINECHARSET の差分も無視してしまう.ここは少し微妙だなと思った.

--- database_a.users
+++ database_b.users
(中略)
-) ENGINE=InnoDB AUTO_INCREMENT=123 DEFAULT CHARSET=utf8
+) ENGINE=InnoDB DEFAULT CHARSET=utf8
# Compare failed. One or more differences found.

微妙なところ : --difftype=sql は完璧じゃない

今回試したケースだと,MySQL の PARTITION の定義が抜けている場合に,データベーススキーマの差分としては検知できるものの,--difftype=sql では検知されなかった.現状だと --difftype=sql は完璧じゃなく,参考程度に使うのが良いと思う.

CI として継続的に差分をチェックする

日々開発を進めている中で,データベーススキーマに差分が生まれてしまうことはあると思う.よって今回は mysqldiff を使ったデータベーススキーマの差分チェックを CI として継続的に実行できるように自動化した.よって,意図しない差分が出た場合は,容易に気付くことができるようになった.

補足 : mysqldump --no-data を使う

mysqldiff の紹介をメインに書いたため省略してしまったけど,実際に使う場合は環境ごとにネットワークが分離されていることも多いと思う.まさに今回もそのような環境下で試していたものの,mysqldump --no-data でデータベーススキーマを取得して,S3 にアップロードすることによって,CI を実行するサーバに各環境のデータベーススキーマを集約することができる.補足として書いておく.

まとめ

  • mysqldiff を使うと,MySQL のデータベーススキーマの差分をチェックすることができる
  • オプションが多くあるため,ドキュメントを確認すること
  • AUTO_INCREMENT の差分が出る場合は --skip-table-options オプションを使うが,CHARSET などの差分も無視されてしまう
  • --difftype=sql は完璧じゃないので,参考程度に使う
  • 一回実行して終えるのではなく,CI として継続的に差分をチェックするべき

Redash を Backup & Restore する

サービスメトリクスを可視化するために Redash を導入しようと考えていて,可視化の部分だけじゃなく,運用面の調査もしていた.

まず Redash をどうやってバックアップするんだろう?と思って調べてみたら,公式ドキュメントに手順がまとまっていた.一言で言うと,Redash の内部で使っている PostgreSQL のバックアップを取得しておけば良いとのことだった.ただし,公式ドキュメントの手順だとバックアップを保管する部分の記載がなかったため,今回は S3 で保管できるように追加で対応をした.

Re:dash → Redash

余談で,正式名称は Re:dash → Redash に変更されているんだけど,案外知らない人が多いように思う.覚えておきましょ!

github.com

構成

シンプルに以下の構成にした.AWS の場合は AMI 経由でインスタンスを立ててしまうのが最も簡単だと思う.

f:id:kakku22:20170327230844j:plain

1. Redash Backup

まず,redash データベースのサイズを確認する.PostgreSQL の pg_database テーブルから取得できる.以下は Redash を起動して少し設定をした状態で確認した.

postgres=# select t1.datname AS db_name, pg_size_pretty(pg_database_size(t1.datname)) as db_size
postgres-# from pg_database t1
postgres-# where t1.datname = 'redash';
 db_name | db_size
---------+---------
 redash  | 7794 kB
(1 row)

次に pg_dump でバックアップを取得する.

$ sudo -u redash pg_dump redash | gzip > redash_backup.gz

簡単!

2. S3 Upload

日次バックアップを取得して S3 に保管するために,少し追加で準備をする.

まず Redash インスタンスに AWS CLI をインストールする.Amazon Linux ではなく Ubuntu だからインストールする必要がある.

$ sudo pip install --upgrade awscli

次に S3 バケットを作成して,IAM Role を EC2 にアタッチする.Action はもっと絞っても良いと思う.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1234567890123",
            "Effect": "Allow",
            "Action": [
                "s3:*"
            ],
            "Resource": [
                "arn:aws:s3:::xxx",
                "arn:aws:s3:::xxx/*"
            ]
        }
    ]
}

先月の新機能リリースで,既存の EC2 に IAM Role をアタッチできるようになったのは最高に便利だと思う.既に何度も助けられてる!

次に以下のようなスクリプトを用意して,crontab でスケジュール実行をすれば S3 に定期的に保管できる.ハウスキーピングなどは別途考える必要があると思う.

#!/bin/sh

sudo pip install --upgrade awscli

sudo -u redash pg_dump redash | gzip > redash_backup_$(date +%Y%m%d_%H%M%S).gz

backup="$(ls -1t | grep redash_backup | head -n 1)"

S3_BUCKET_NAME='xxx'

aws s3 cp ${backup} s3://${S3_BUCKET_NAME}/

3. Redash Restore

公式ドキュメントの手順通りにコマンドを実行したら正常にリストアすることができた.バックアップファイル名とパスワードは適宜変更してもらえればと!

$ sudo -u postgres -i

$ dropdb redash
$ createdb -T template0 redash
$ gunzip -c redash_backup_xxx_yyy.gz | psql redash

$ psql -c "ALTER ROLE redash_reader WITH PASSWORD 'xxx';"

$ psql -c "grant select(id,name,type) ON data_sources to redash_reader;" redash
$ psql -c "grant select(id,name) ON users to redash_reader;" redash
$ psql -c "grant select on events, queries, dashboards, widgets, visualizations, query_results to redash_reader;" redash

まとめ

  • 正式名称は Re:dash ではなく Redash に変更されている
  • Redash を運用するときは定期的にバックアップを取得しよう
  • リストアも一度は確認しておこう

知っておくと便利な git clone --depth と git diff --indent-heuristic

コミット履歴が無駄に多く,黒歴史のある Git リポジトリで開発をする場合,初回の git clone が非常に遅いという問題がある.コミット数に依存せずに素早く落とせる方法を探していて,最近(今さら...!) git clone--depth オプションのことを知った.用途によっては非常に便利なので,まとめておこうと思う.

前提

現時点で公開されている最新バージョンの Git 2.12.1 を前提にしている.Git は今もまだ機能が増えているため,定期的にバージョンアップしておくと良いと思う.Mac なら brew upgrade git でサクッと最新バージョンになる.

$ git --version
git version 2.12.1

git clone --depth とは

git clone--depth オプションを使うと,指定したコミット数で刈り取ることができる.以下の例では --depth 1 で,master ブランチにある最新のコミットだけを落としている.コミット履歴が大量にある Git リポジトリでも,素早く落とすことができるため便利.

ちなみに Git のドキュメントを読むと,--depth オプションを使った git clone のことを「シャロークローン (shallow clone)」と呼んでいた.合わせて覚えておくと良さそう.

$ git clone --depth 1 git@github.com:kakakakakku/togoo.git
$ cd togoo
$ git log --oneline | cat
80ff421 Added listing column "created_at"

shallow clone をしても pull / push に制約は無かった

shallow clone をすると pull / push ができないと書いてある記事が多かったが,少なくとも Git 2.12.1 では,普通に pull / push / fetch をすることができた.ようするに --depth 1git clone して,開発をして,プルリクエストを作ることができる.

よって,最初はある程度の深さ(100 など)で shallow clone をして開発を始めて,コミット履歴が必要になったり,git blame 警察をするときになってから,過去のコミットを取得し直せば良いということになる.

追加で過去のコミットを取得する

では,過去のコミットを取得したくなったときにどうすれば良いかと言うと,git fetch--depth オプションを使って深さを指定するか,--unshallow オプションで全てを取得することになる.

# 深さを指定して取得する場合
$  git fetch --depth 5

# 全てを取得する場合
$ git fetch --unshallow

git diff --indent-heuristic とは

せっかく Git の話を書いているので,案外知られていないように思う「便利な git diff オプション」の話も書いておこうと思う.

git diff で,差分結果が微妙にズレていて気持ち悪いなと感じた人は多いのでは?例えば,以下のようなサンプルコードで再現することができる.追加したのは最初の3行なのに...!という感じでツライ.

$ git diff
--- a/hoge.rb
+++ b/hoge.rb
@@ -1,3 +1,7 @@
 [1, 2, 3].each {|i|
+  puts i
+}
+
+[1, 2, 3].each {|i|
   puts i + 1
 }

この問題を解決するために Git 2.9 で git diff --compaction-heuristic というオプションが追加されている.ただし,このオプションは不完全で,Git のデフォルト設定としては採用されなかった.そして Git 2.11 で,さらに改善された git diff --indent-heuristic というオプションが追加された.--indent-heuristic は全てのケースで改善がされているらしく,今後のバージョンで Git のデフォルト設定になる予定があると公式ブログにも書いてあった.

The new option goes under the name --indent-heuristic (and diff.indentHeuristic), and will likely become the default in a future version of Git.

github.com

--indent-heuristic オプションを使うと,以下のように意図した差分結果になる.最高!

$ git diff --indent-heuristic
--- a/hoge.rb
+++ b/hoge.rb
@@ -1,3 +1,7 @@
+[1, 2, 3].each {|i|
+  puts i
+}
+
 [1, 2, 3].each {|i|
   puts i + 1
 }

--indent-heuristic オプションをデフォルトにする

~/.gitconfig に以下の設定をすれば,git diff のデフォルト設定が --indent-heuristic になる.個人的には必須な設定!

[diff]
  indentHeuristic = true

まとめ

  • Git は定期的にバージョンアップする
  • 巨大な Git リポジトリをサクッと落とす場合は git clone --depth を使う
  • 最新バージョンの Git なら shallow clone をしても pull / push ができる
  • ~/.gitconfigdiff.indentHeuristic を有効化すれば git diff が便利になる

関連記事

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com

kakakakakku.hatenablog.com