もりはやメモφ(・ω・ )

インフラなエンジニアからSREへ

GlueとAthenaの備忘メモ

AWSのGlueとAthenaを勉強しており、現時点のメモを残しておきます。 (完全に自分用になっています)

やりたいこと

RDS上の特定のテーブルのスナップショットを、Athenaで検索できる状態でS3上にGlueで出力させる。 出力タイミングは日次&アドホック

glue

## add morihaya
year   = int(time.strftime('%Y',time.localtime()))
month  = int(time.strftime('%m',time.localtime()))
day    = int(time.strftime('%d',time.localtime()))
hour   = int(time.strftime('%H',time.localtime()))

dpath = "s3://aws-glue-morihaya" + "/" + "year=" + str(year) + "/" + "month=" + str(month) +  "/" + "day=" + str(day) + "/" + "hour=" + str(hour)

glueの残メモ

RDS -> parquet形式@S3だとsnappyで圧縮をしてくれるが、avroにすると無圧縮になる。。。

Athena

RDS PostgreSQLからRDS Aurora PostgreSQLへDMSでデータ同期してみる

ふと、AWS上のpostgresってどうやってデータ同期するんだろうと疑問に思ったので検証してみたメモです。

オンプレでネイティブなpostgresならデータ同期にはいくつかのパターンがあってざっくり以下だと認識しています。

  1. ver9以上で同期元と同期先が同じバージョンならストリーミングレプリケーション
  2. ver10以上ならロジカルレプリケーション
  3. ver9.4以上なら2ndQuadrantのpglogical extentionでロジカルレプリケーション
  4. バッチ方式で良いならpgdumpとかembulkとか

※他にattunityとかここのツール群とかは良く知らないので割愛

検証結果

結果を先に書くと、 RDS PostgreSQLからRDS Aurora PostgreSQLへDMSによるロジカルレプリケーションが可能 です。

注意事項として、ロジカルレプリケーションの制約である「プライマリキーが存在しないテーブルは対象外」ですので、利用するテーブルには注意しましょう。 中身はpglogicalだと思うのでドキュメントを読んでおくことをお勧めします。

必要な設定

設定はシンプルで以下の2点

  • RDS PostgreSQL用のパラメータグループで rds.logical_replication = 1 を設定
  • DMSをぽちぽち設定(特殊な設定はなし)

注意点として、rds.logical_replicationを1にした後は必ずインスタンスの再起動が必要です。show rds.logical_replicationで確認すると動的に変更されるのですが、ロジカルレプリケーションで必須のwal_level が再起動をしないと replica から logical に変更されません。 パラメータとしても適用はstaticになっています。 なお同様の内容をクラスメソッドさんでも発見しました、流石です。

検証開始

どうやるかを調べる

とりあえず最初にあげた方法を順に考えましたが、以下の通りオンプレ脳ではRDS(pg)->Aurora(pg)レプリケーションは達成できそうにありませんでした。

  1. ストリーミングレプリケーション -> 不可。バージョンも違うしサービスも違うしwalファイル操作できない
  2. ロジカルレプリケーション -> 不可。現状でRDS PostgreSQLにしろRDS Aurora PostgreSQLにしろver9.6まで。ver10は今後。
  3. pglogical extentionでロジカルレプリケーション -> 不可。バージョン的には問題ないが、自前でextentionは入れられない(だってマネージドだし...)
  4. バッチ方式 -> 可。できるのはわかっているが、レプリがしたい。。

そこで頭を切り替えます。AWSのデータ同期と言えばDMSでしょうということで、ドキュメントをつらつら読んでいきました。

ソースデータベースにはオンプレミス、または Amazon RDS か Amazon EC2、
ターゲットデータベースには Amazon RDS または Amazon EC2 のいずれかを使用できます。

なるほどRDSはソースにもターゲットにもできる様なので問題なしですね。さらに2016年と古いですがこんな記事も。

Amazon RDS for PostgreSQLがロジカルレプリケーションをサポートしました。

略

AWS Database Migration ServiceはAWSへのデータベースマイグレーションをサポートするサービスです。
ロジカルレプリケーションと一緒に使用する事により...

やったぜ!DMSを使えばRDS PostgreSQLからロジカルレプリケーションでRDS Aurora PostgreSQLへデータ同期できるとのことで、検証を行っていきます。

手を動かす

各サービス起動

はじめに検証環境として以下の3サービスを立ち上げます。

次にRDSのセキュリティグループでEC2のプライベートIPを許可します。port 5432に対して インバウンド172.31.0.0/16 と許可します(ちょっと無駄に広いですけど)。

DBへ接続確認

EC2へログインし、送信元、送信先のDBインスタンスに接続できることを確認します。毎回パスワードを入力するのが面倒なのでpgpassファイルを最初に作成します。

vi ~/.pgpass

morihaya-aurorapg.xxxxxx.us-east-1.rds.amazonaws.com:5432:morihaya:morihaya:password
morihaya.xxxxxxx.us-east-1.rds.amazonaws.com:5432:morihaya:morihaya:password

chmod 0600 ~/.pgpass
cat ~/.pgpass

続いてEC2上でpsqlコマンドを使える状態にします。postgres-serverを入れる必要はないのですが、大して重くないので入れてしまいました。contribはpgbenchで簡単な負荷テストをするためです。

yum install postgresql96-server postgresql96-contrib

ここまできたら接続テストをします。

# RDS(postgres)
psql -U morihaya -h morihaya.xxxx.us-east-1.rds.amazonaws.com

# RDS Aurora(postgres)
psql -U morihaya -h morihaya-aurorapg.xxxx.us-east-1.rds.amazonaws.com

無事つながりました。

DMS設定

続いてDMSを設定していきます。 DMSは レプリケーションインスタンス という専用EC2を起動し、その中で タスク を設定することでデータ同期を行う様です。(多分その専用EC2でattunityみたいなツールが動いているんでしょうね(想像))

ここも特に迷わずポチポチして作成が完了しました。 [テーブルマッピング]というセクションで同期するテーブル、しないテーブルを制御できるのも分かりやすくて良いです。 f:id:morihaya:20180430194747p:plain

最後にソースおよびターゲットそれぞれに テストの実行 ができるので、成功を確認して作成完了です。

※実はここでrds.logical_replication 変更後のRDS側の再起動を行っておらずプチハマりしました。以下のエラーをRDSのログでみて気づけましたが。。

04-30 08:27:37 UTC,9/36,0,ERROR,55000,"logical decoding requires wal_level >= logical",,,,,,"BEGIN;declare ""SQL_CUR0x....

初期データ投入

DMSによるデータ同期を行ってからデータ投入というケースはあまりなさそうなので、とりあえず初期データを送信元のRDS PostgreSQLに入れます。

EC2から以下を実行します。

pgbench -i -h morihaya.xxxx.us-east-1.rds.amazonaws.com -U morihaya

念のためカウントで確認します。

psql -h morihaya.xxxxx.us-east-1.rds.amazonaws.com -U morihaya -c 'SELECT count(*) from pgbench_accounts'
 count
--------
 100000
(1 row)

DMS同期開始

コンソールから開始を実行します。 再開 という方法もある様ですが、今回は初回のため 開始 です。

ステータスが完了になれば成功です。 f:id:morihaya:20180430200736p:plain

同期先のauroraにてデータの伝搬を確認します。

psql -h morihaya-aurorapg.xxxxx.us-east-1.rds.amazonaws.com -U morihaya -c 'SELECT count(*) from pgbench_accounts'
 count
--------
 100000
(1 row)

無事にレプリケーションが組めました。簡単!

主キー無しテーブルを試してみる

ここでちょっと興味が湧いたので、主キー無しのテーブルがどの様に伝搬されるかを確認します。

なおネイティブなPostgreSQL ver10のロジカルレプリケーションではDDLは伝搬しないのですが、それも含めてテストします。

送信元のRDS PostgreSQLpsqlで接続、以下を実行します。

psql -U morihaya -h morihaya.xxxx.us-east-1.rds.amazonaws.com

# table作成
create table morihaya ( date date );
insert into morihaya ( date ) values ('2018-04-30' );

# tableへ列追加
alter table morihaya add column time time;
insert into morihaya ( date, time ) values ('2018-04-30' , '18:00:00' );

# データ確認
morihaya=> select * from morihaya;\q
    date    |   time
------------+----------
 2018-04-30 |
 2018-04-30 | 18:00:00
(2 rows)

結果をaurora側でも確認します。

# psql -h morihaya-aurorapg.xxxxxx.us-east-1.rds.amazonaws.com -U morihaya -c 'SELECT * from morihaya'
    date    |   time
------------+----------
 2018-04-30 |
 2018-04-30 | 18:00:00
(2 rows)

DMSのタスク作成時のフィルタを % と全指定したこともあり、DDL,DML全てが送信先のAuroraに伝搬されていることを確認しました。素晴らしい。

そして肝心のUPDATEを同期元のRDS PostgreSQLで実行します。

> update morihaya set time='11:11:11';

> select * from morihaya;
    date    |   time
------------+----------
 2018-04-30 | 11:11:11
 2018-04-30 | 11:11:11
(2 rows)

同期先をみるとなんてことだ!伝搬されてませんね><

# psql -h morihaya-aurorapg.xxxxx.us-east-1.rds.amazonaws.com -U morihaya -c 'SELECT * from morihaya'
    date    |   time
------------+----------
 2018-04-30 |
 2018-04-30 | 18:00:00
(2 rows)

ではDELETEはどうでしょう。送信元で以下実施。

> delete from morihaya WHERE date='2018-04-30';
DELETE 2

> select * from morihaya;
 date | time
------+------
(0 rows)

送信先のauroraで確認すると、「マイガッ!」DELETEもダメですね。

# psql -h morihaya-aurorapg.xxxxx.us-east-1.rds.amazonaws.com -U morihaya -c 'SELECT * from morihaya'
    date    |   time
------------+----------
 2018-04-30 |
 2018-04-30 | 18:00:00
(2 rows)

整合性がなくなったのでテーブルを削除します。

> drop table morihaya;
DROP TABLE

送信先でも無事に削除されました。

# psql -h morihaya-aurorapg.xxxxx.us-east-1.rds.amazonaws.com -U morihaya -c 'SELECT * from morihaya'
ERROR:  relation "morihaya" does not exist
LINE 1: SELECT * from morihaya

主キー無しテーブルを試した結論

結論としては主キーの無いテーブルの場合以下の挙動になりました。サービスとしては使えませんが、動きとして知っておくとトラブル対応が捗るかもですね。(主キー無しテーブルを同期して変な動きをしてしまう未来が見える)

  • DML
    • INSERT -> 伝搬される
    • UPDATE,DELETE -> 伝搬されない
  • DDL -> 伝搬される

念のため主キーがあるテーブル確認

ここまでくると不安なのでサクッと主キーありテーブルの挙動を確認しました、もちろん正しく伝搬したので何の問題も無しでした!

送信元で以下を実施。

create table morihaya (id SERIAL primary key, date date , time time);

insert into morihaya ( date ) values ('2018-04-30' );
insert into morihaya ( date ) values ('2018-04-30' );
insert into morihaya ( date ) values ('2018-04-30' );

> select * from morihaya;
 id |    date    | time
----+------------+------
  1 | 2018-04-30 |
  2 | 2018-04-30 |
  3 | 2018-04-30 |
(3 rows)

 update morihaya set time='11:11:11';
UPDATE 3

 select * from morihaya;
 id |    date    |   time
----+------------+----------
  1 | 2018-04-30 | 11:11:11
  2 | 2018-04-30 | 11:11:11
  3 | 2018-04-30 | 11:11:11
(3 rows)

送信先でもご覧の通り!

# psql -h morihaya-aurorapg.xxxxx.us-east-1.rds.amazonaws.com -U morihaya -c 'SELECT * from morihaya'
 id |    date    |   time
----+------------+----------
  1 | 2018-04-30 | 11:11:11
  2 | 2018-04-30 | 11:11:11
  3 | 2018-04-30 | 11:11:11
(3 rows)

以上、DMSを使ってRDS PostgreSQLからRDS Aurora PostgreSQLへのデータ同期を試したメモでした。

(余談)RDS Aurora PostgreSQLはDMSの送信元にできるのか疑惑

実はRDS Aurora PostgreSQLのパラメタグループに rds.logical_replication = 1 に該当するパラメタが存在しないんですよね。さらにwal_levelを確認するとreplicaとなっています。これはもしかしてAurora Postgresを送信元にできないのじゃ無いかなって思ったりしましたが、本日は力尽きたので次に試したいと思います。

> show wal_level;
 wal_level
-----------
 replica
(1 row)

さらっとDMSの設定みるとソースに aurora と指定できるので問題なさそうかな。。。 f:id:morihaya:20180430204554p:plain

マイクロサービスアーキテクチャ本の感想(6章)

お正月の長期連休が遥か昔のように感じていますが、ぼちぼちやっていきましょう。さて O'Reillyのマイクロサービスアーキテクチャ本感想の第6回です。

6章「デプロイ」を読んで

6章は以前から興味はあったけれど実践はできていない"CI/CD"について語られています。

CIって何よ

継続的インテグレーション(Continuous Integration)とは、すなわち「すべてを互いに常に同期させることであり、実際には新たにチェックインされたコードが既存コードと適切に統合(インテグレーション)されるようにすること」とあります。要するにgit commitしたら適切にマージされるってことだと理解しました。ポイントは「適切に統合」というところで、コードコンパイルやテストが自動で行われ、高速なフィードバックを得られる状態であることを指します。

あなたがCIを行えているかをチェックしよう

Jez Humble氏の質問を引用し、本当にCIがチェックできているかを振り返ってみよう!のコーナー

  • 1日に一度はメインラインにチェックインしているか?
  • 変更を検証するテストスイートがあるか?
  • ビルドが壊れたとき、それを修正するのがチームの最優先事項か?

上記を全て満たしていなければそれは適切なCIを行えていない状態だぞと、具体的な記述こそありませんが、文脈から伝わってきます。

マイクロサービスにふさわしいCIビルド

モノリシックな1レポジトリ&1パイプラインではなく、マイクロサービス単位でレポジトリとビルドパイプラインが用意されている状態が理想であるとしています。

継続的デップローイ!!(CD)

CIに続いてCDの話です。継続的デリバリ(Continuous Delivery)はビルドパイプラインを段階的なステージにモデル化し、各種テストやビルドを経て本番環境へ自信を持ってデプロイするための概念であるとしています。※詳しくは継続的デリバリー 一般的な例として以下が挙げられています。

  1. コンパイル&高速テスト
  2. 低速テスト
  3. UAT(User Acceptance Testing)=ユーザ受け入れテスト
  4. 性能テスト
  5. 本番環境

例外は避けられない(いきなり理想のCI/CDなんて無理だよ)

初期の開発時は無理にサービスごとのパイプラインを作らず、多少モノリシックでも運用がしやすいCDでOKという話。 理由としては3章のSnapCIが例であったように、初期のアプリケーションはBC(コンテキスト境界)が変動しやすいため。

固有の成果物について

  • RubyのGem、JavaのJARやWARなど、各種プログラミング言語のパッケージマネージャを使うと便利ではあるが、サービスごとに技術スタックがバラバラだとデプロイ環境を管理することが大変になる
  • OSのRPMdebMSIなどを作成すればデプロイ管理は楽になるが、パッケージ作成の難易度が高いのが大変だ
    • 特にWindowsMSIが大変だが、Chocolatey NuGetが状況を変えるかもしれない
    • できればOSレイヤのパッケージでデプロイができれば、管理はシンプルになるだろう(そりゃそうだ)

カスタムイメージでデプロイを楽にしよう

Ansible(Chef,Puppet)などの構成管理ツールを使う際の問題として実行に時間がかかるという点があります。対策としてOSをある程度ビルドしてイメージを準備しておきましょうという話。

  • 第一段階としてはサービスを入れるだけの状態のOSイメージを用意
  • 第二段階としてはサービスも入っているOSイメージを用意
  • 加えてイミュータブルサーバという概念を実施する
    • あらゆる変更は必ずビルドパイプラインを通して行うということ
    • sshを無効化してしまう(なんて恐ろしいと思ってしまうけど)

ステージごとの環境差異はバランスを考えること

次章のテストに繋がる話として、ビルドパイプラインのステージごとに環境が異なり、適切なバランスを取りましょうという話。 例として本番は複数台でレプリケーションを行っているが、ソフトウェアライセンスの理由で開発環境はシングル構成だったため、レプリケーションに影響が出るテストがスルーされてしまって大惨事となったことが紹介されています。

  • 本番に近い環境にすることで
    • 費用が上がる(ソフトウェアライセンスやサーバ費)
    • デプロイの時間がかかる(台数が増える)
  • 本番とは異なる環境にすることで
    • 費用削減
    • デプロイ時間短縮

という天秤のバランスを上手く取る必要があります。 これは私にも経験がありまして、CentOS6のTHPというメモリ管理の仕組みが本番の大規模なメモリを積んだホストではCPUを完全に奪うという事象にやられたことがあります。開発環境の少ないメモリでは全く問題が起きなかっただけに、環境差異がある状況での発見は難しかったんですよね。。

ビルドは1回、環境差異は構成で吸収する

各環境毎にビルドするのはナンセンス。1回のビルドでテスト、ステージ、本番環境の全てを通すべきで、各環境の際は構成を別に管理することで吸収するのがオススメとのこと。 加えて機密情報をビルドに加えるのもアンチパターンであり、こちらも構成管理で吸収するべきとしています。 具体的には以下のような方法があるとのことです。

  • 環境ごとのプロパティファイル
  • インストールプロセスに渡すパラメタ

個人的にはGo言語製のOSSとか起動時のパラメタでほぼ全ての設定が完結するイメージがあります。(Consulとかtraefikとか)

サービスのホストへのマッピング

この節ではサービスをホストへどのようにマッピングするかを取り上げています。 1サービス/1ホストでは物理と変わりがありませんし、多すぎても耐障害性が。。ということで、以下のパターンの良し悪しが挙げられています。

複数サービス/ホスト

1つのホストに複数のサービスを展開する手法。

  • メリット
    • ホストを管理するというシンプルさがある
  • デメリット
    • 仮想化によるオーバーヘッド
    • CPUなどのリソースがシェアされるため、リソース監視が難しい

アプリケーションコンテナ

複数サービスを1つのアプリケーションコンテナとしてパッケージしてホストに展開する手法。

  • メリット
    • 言語ランタイムのオーバヘッドが削減できる(Javaサーブレットコンテナ内で複数のJavaサービスが起動)
  • デメリット
    • 技術選択が限定されてしまう

1サービス/1ホスト

読んで字のごとく1ホスト上に1つしかサービスを展開しない手法。 著者はこの構成こそ目指すべきものだとしています。

  • メリット
    • 構成がわかりやすくシンプルになる
    • SPOFの削減
    • デプロイがやりやすい(イメージベースデプロイ、イミュータブルサーバパターン)
  • デメリット
    • コストがかかる

ここで言うホストとは物理的な物を指すのではなく、VMでも良いです。とにかく1:1の関係でサービスとホストを構築することで、 マイクロサービスの疎結合の考え方を適用しろと理解しました。

PasSについても

最上級のPaaSとしてHerokuが挙げられており、その可能性と有用性を評価する一方で以下のような厳しいコメントでした。 とはいえ本書が執筆された2015年から3年がたった2018年の今では、状況が変わっているのかもしれません。 (PCS[Pivotal Container Service]とかIBM Cloud privateとか、色々出てきてはいますしね)

  • 使用できる技術が限定される(GAEがruby使えないとかそういう話)
  • 標準的なアプリケーションに最適化されがち
  • 内部の細かい制御(チューニング)ができない
  • ホスト型(herokuのような)が主流で、オンプレに展開するPaaSはまだまだ発展途上だ

自動化!!自動化!!自動化!!!

本章ではこれが一番言いたかったんじゃないかと思います。 自動化によりサービスのスケールにオペレーション工数が比例しない状況を作るべきで、 そのための最適な技術を選択して利用する必要があるとのこと。

また、初期は自動化のための作り込みが発生するのは仕方ない(RealEstate.com.auの事例も挙げつつ)が、 後になって確実に効いてくるから頑張れとエールをもらいました(気がします)。

デプロイ先の環境の変化(物理 -> 仮想)

デプロイ先の仮想化技術の選択肢も挙げられています。

  • 仮想化
  • コンテナ
    • Linuxコンテナ
    • Dockerコンテナ

デプロイはコマンド化しろ

節のタイトルは「デプロイのインターフェース」で、あらゆるプラットフォームに共通して必要なデプロイ方法が提示されます。 それは以下の3点を含むインターフェースであることです。

  1. 環境に依存しない統一されたインターフェースを用意し、かつ環境を指定できること
  2. サービス名を指定できること
  3. バージョンを明確に指定できること

上記を考慮すると必然的に以下の様なコマンドになるはずとしています。

deploy service=<サービス名> env=<環境名> ver=<バージョン番号>
※簡略化してます。そのまま引用ではありません。

さらに面白いのが、開発者であれば自分の環境へデプロイするだろうから以下のようになり、

deploy service=<サービス名> env=local ver=local

開発者がコミットすればCIツールが以下のようにバージョンを振り、

deploy service=<サービス名> env=ci ver=1.2.34

QAチームであれば常に最新のビルドをテストしたいから以下のようになるといった、具体的な説明もしています。

deploy service=<サービス名> env=qa ver=latest

環境定義は外に切り出せ

デプロイ方法を記載したコードと、各環境を定義したパラメタは別に管理するべしとあり、Terraformへの期待が記載されています。 HashicorpはVaultという機密情報管理のサービスも行っており、著者が期待したサービスの展開を行っているのだなぁとしみじみ思いました。

具体的には以下のような差異を管理できるようにします。

  • 本番と開発環境ではマシン性能を変える
  • 本番と開発環境では認証情報を変える
  • 本番と開発環境ではスケールを変える

6章の感想

デプロイを従来の手作業でハートフルな方法でやるのは極めてナンセンスであるというのがよくわかり、読みながら胸が痛みました(現状がそうではないのでorz)。 CI/CD環境を初期投資を惜しまずに構築し、自動化を進め、デプロイの速度・制度・開発側への移譲(セルフデプロイ)を高める必要がありますし、実現に向けたモチベーションと作業イメージを得ることができました。 また、パイプラインの粒度も可能な限り小さく、サービス単位とすべきであるという文を読み、本書が掲げるマイクロサービスという言葉の具体的なイメージを得ることができたとも感じています。(この章まではどちらかというと抽象的な話が多い印象でした)

マイクロサービスアーキテクチャ本の感想(5章)

そろそろ正月気分も抜けてきました。 O'Reillyのマイクロサービスアーキテクチャ本感想の第5回です。

5章「モノリスの分解」を読んで

4章の統合に対し、5章では既存のモノリシックなアプリケーションをどうやって分割していくかの話になります。

コンテキスト境界再び

モノリシックなアプリケーションを分割するためには、アプリケーションの接合部(seam)を探し出すこと、接合部としてはBC(コンテキスト境界)がわかりやすいのでおすすめとあり、BCを洗い出した後の流れが語られます。

  1. BC(コンテキスト境界)を特定する
  2. 各コンテキストを表すパッケージの作成
  3. 既存コードを各パッケージへ移動
  4. 取り残されたコードから新たにBCを特定 ※以後繰り返し

このフローによってコードベースを接合部に合わせて整理すると捗るそうです。

なぜ分解するのかを考えながら行うことが大事

モノリスを分解するということは、大理石を削る行為に似ているとし、勢いよく削ろうとすると全体が想定外に割れてしまうと警告があり、そのうえでコードベースをさらに分解する指針として以下が挙げられています。

  • 変化の速度
    • 更新が大量に走る処理はそれ自体が分割の対象になる
  • チーム構成
    • チームの地理的な要素(コンウェイの法則もあると思う)
  • セキュリティ
    • 転送データ、格納データ、監視などの観点で適切に保護ができるようになる
  • 技術
    • 技術特異性の話。新しいライブラリ、ミドルで最適化が可能になる

データベースは依存関係の塊です

structure101のようなツールで依存関係をグラフ化すると接合部を見つけるのが捗るぜ、そして大抵の場合はデータベースが最も依存関係の多いポイントだ(意訳)というノリで従来のRDBのあり方に切り込んでいきます。

  • 外部キー関係は削除しよう
    • 品目、帳簿といったテーブルは、品目管理サービス、帳簿管理サービス専用テーブルとして外部キーを削除する
    • 従来のRDBより性能(処理速度)は落ちるが許容できるか判断しよう
    • 品目テーブルと帳簿テーブルのデータ整合性が一致する必要があるか、サービス要件は技術者が考えることではないのでエスカレしよう
      • (確かに商品名が変わったとして、商品名変更前の帳簿には古い名前が載っていても良い気はする)
  • 共有静的データの取り扱い
    • 案1,各サービスで重複して持ってしまう(ハードコーディングするケースもよくあるとか)
    • 案2,サービス化してしまう
  • 共有データはサービス化すべし
    • データベースで暗黙的にモデル化されているドメイン概念
  • 共有テーブルは分割して各サービスに返す
    • (正規化が可能なテーブルだと思う)

この節は自分の携わるシステムのDBの思い浮かべながら読んだのですが、アプリケーション側というか、開発者への負担は相当な物になるなあという印象。

分割は段階的にやっていこう

サービスの分割と、データベース内のスキーマ分割のどちらから行うべきかという話です。適切な順番としては以下としています。

  1. DBのスキーマを分割
  2. アプリケーションを分割

理由はアプリケーションコードを一緒にしておくことで、コンシューマに影響を与えることなく変更を戻せるためです。(アプリケーション側にも変更は必要のはずで、DBへのクエリだけ変えると理解した)

トランザクションはマイクロサービスでは大変だ

分散システムでのトランザクションの担保は、2フェーズコミットやリトライ、ロールバックなど考慮すべき点が多いとし、結論としては以下3点に集約されます。

  1. 本当に単一のトランザクションで行う必要のある処理かを見直す
  2. 単一のトランザクションが必要な処理を分割させずに済む方法を探す
  3. 分割が必要ならトランザクション事態を表す概念を生成する(処理中のhoge、といったステータス)

読んでいて"CAP定理"の話だなと思いましたが、"CAP定理"という言葉は出てきませんでした。

KPIなどのレポート用データベースの取り扱い

よくあるユースケースとして、レポート用のデータベースは複数のデータを結合することが求められますが、どう扱うと良いのかという話です。

  • サービスを介してAPIで呼び出す
    • 一見よさそうだが、大量データ(全ユーザなどの)の取り扱いにRESTは向いていない課題がある
  • データポンプ
    • レポート用のDBへ、必要なデータを定期的にコピーする
    • S3を使ってJSONファイルで渡すといった事例もある
  • イベントデータポンプ
    • レポート用のDBへ、データ変更が発生するなどのイベントをトリガとしてデータをコピーする
  • バックアップデータポンプ
    • NetfrixのCassandraのデータをバックアップする事例から
    • S3とHadoopを利用する後のOSSAegisthus
      • Aegishusは2018/1/11時点ではメンテナンスモードで今後機能拡張されない様子
  • リアルタイム
    • fluentdやlogstashなどを利用したリアルタイムデータ転送(本では具体的なツール名はでていないけど)
    • 今後加速すると著者は予想している

変更コストと根本原因を意識する

  • 変更コスト
    • 最小の影響範囲で失敗できるポイントから変更していく
  • 根本原因
    • モノリシックになることは悪いことではない
    • サービス初期開発時は作りやすさからモノリシックになる、それはOK
    • 適切なタイミングで分割を行うことが大事

5章の感想

本章はDB分割のところを大変面白く読みました。コンテキスト境界を意識して大きな枠から分割していくイメージは言葉だけで分かった気になっている感覚があります。一方でDB分割は自分の業務に近いところでもあり、業務を想定しながら具体的な分割方法を検討する良い機会にもなり、そしてそれが非常に苦労する道のりだという知見が得られた章でした。

マイクロサービスアーキテクチャ本の感想(4章の後半)

まだまだ正月気分が続きますが、O'Reillyのマイクロサービスアーキテクチャ本感想の第4回です。

4章「統合」を読んでの続き

バージョニングについて

この節ではマイクロサービスにおけるバージョニングについて説明があります。マイクロサービスの内部としては分かりやすいバージョニングをしつつ、外部のマイクロサービスへはバージョンを意識させないことが大事とあります。(いつから新バージョンになったと錯覚していた!!)

  • セマンティックバージョニングを利用した"分かりやすい"バージョニング方法
    • x.y.zのxはメージャーバージョンを意味し、破壊的な変更を伴う
    • x.y.zのyはマイナーバージョンを意味し、機能追加だが互換性あり
    • x.y.zのzは不具合修正
    • (この辺はdockerみたいな日付(年)を採用するところもあるので考え方次第かも)
  • 破壊を伴うバージョンアップを行うためのエンドポイントの共存について
    • ブルーグリーンデプロイメントやカナリアデプロイで一時的に共存させるのはOK
    • "/api/v1/hoge"と"/api/v2/hoge"のようなわかりやすいURIを利用するケースもあるがお勧めできない
      • クライアントからマイクロサービスのAPIバージョンは不透明(バージョンはわからない)とするべき
    • 古いクライアント向けに古いバージョンを稼働させ続けるのも手だが、管理コストが跳ね上がる

ユーザインタフェースについて

PCだけでなく、スマホタブレット、スマートウォッチなどデバイスが多様化している現在でのUIの構成ポイントが語られます。

  • 様々なデバイスが存在し、それぞれの制約がある
    • ブラウザ、解像度
    • モバイルの場合通信は最低限であるべき
    • SMSを利用するケースも多い
    • 今後新たなデバイスが出てくる可能性も高い(VRやARもありますしね)
  • API合成は各マイクロサービスが提供するAPIを組み合わせてUIを作る手法
    • メリット
      • バイス単位に再利用できる
      • 部品ごとに利用して特定の画面パーツを自由にカスタマイズできる
    • デメリット
      • バイスを意識したレスポンス調整ができない
      • 合成したUIをだれが作るのか問題(APIまでは作るけど、その後はだれ?)
  • UI部品合成はマイクロサービスで小さな画面部品(UI)まで作成し、それを各デバイス向けに組み合わせる手法
    • メリット
      • マイクロサービスチームがUI部品の変更まで担当可能
    • デメリット
      • 各UIの一貫性をどうやって保つか問題(担当チームの趣味がでるかも)
      • HTTPだけじゃなくてネイティブアプリの場合どうにもならない問題
  • BFF(Backend For Frontend)は、フロントエンド(PCブラウザ、ネイティブアプリ、管理者向け画面)などの各フロントエンドごとにAPIコール用のバックエンドを設ける手法
    • 単一のAPIゲートウェイを置くのは悪手(モノリシックなAPIゲートウェイの誕生)
    • BFFとUIの間にAPI認証/認可のレイヤもはさむことができる
    • 気を付けるのは結合。ほかのレイヤの機能や、ほかのUIの機能がBFFに混入しないようにする
  • ベストな方法は各自探すこと
  • 大事なのは凝集性を維持すること

サードパーティソフトウェアとの統合

SaaSCMSCRMなどのサードパーティとの付き合い方についての解説。便利かつ多機能であるが故に、気が付くと沼のように沈み込んでしまう危険性がありますよという話。

  • ツールの手前にファサードサービスを設けて隠ぺいする
    • ファサードは自前で作成したサービスでAPI呼び出しなどを中継する役割
    • 各マイクロサービスから直接ツールのAPIをたたくと結合になって大変
    • サードパーティソフトウェアが一つの巨大な共有データベースになる危険性
  • 沼から抜け出す場合はストラングラーパターンで徐々に対応する

4章の感想

4章は分割したマイクロサービスをいかに統合して顧客サービスとして展開していくかを学ぶことができました。分割したそれぞれのマイクロサービスをどのように連携させ、UIをどう構成し、サードパーティとどのように連携するのか、実業務への落とし込みを考えるとタスクの洗い出しだけでも大変に違いありません。

もっとも衝撃的だったのは共有データベースがマイクロサービスとしては悪手であるということで、目からうろこでした。うすうす感じてはいた「とりあえずデータストアといえばRDBでしょ、という時代では無い」ことを言葉として認識できました。

サービス間連携についてはRPCとRESTについて多くの用語や概念を学べました。リチャードソン成熟度モデルやらHATEOASとかRxとか聞いたことがある程度でしたので、著者が進める文献へのリンクなどが載せられているところは親切で助かります。

マイクロサービスアーキテクチャ本の感想(4章の前半)

始まる前は長いと思っていたお正月休みも終わってしまいますね。 O'Reillyのマイクロサービスアーキテクチャ本感想の第3回です。

4章「統合」を読んで

4章はまず、そのボリュームに圧倒されます。実ページ数で47Pもあり、これは1章の13P、2章の17P、3章の10Pと較べて倍以上のページ数となっています。章タイトルは「統合」で、マイクロサービスとして分解した各サービスをどうやって組み合わせて実サービスにつなげていくかの話になります。時間と集中力の兼ね合いで4章は前半と後半に分けます。

マイクロサービス技術の一番大事なところが統合なんだぜ

「統合を間違えると大惨事」や「SOAの試みを苦しめてきた最大の落とし穴」といった脅し文句から4章は始まります。技術的な話の前に、選択しようとする技術から何を得たいのかを確認しており、それは以下であるとしています。

  • 破壊的な変更を回避すること
    • 疎結合で繰り返し言われた、他のサービスに影響を与えずに変更が可能であるという話
  • APIを非技術依存にする
  • コンシューマにとって単純なサービスにする
    • コンシューマに簡単に利用してもらいたいが、簡単にするためのクライアントライブラリは結合を生むというジレンマがある
  • 内部の実装詳細を隠す
    • 隠れモデルの変更は共有モデルやインタフェースに影響させないという話

僕の価値観が崩壊した「共有DBは大規模な共有APIであり脆弱である」という考え方

僕がこれまで携わったシステムでRDBを使用しないシステムは無く、その大抵のシステムに"共有DB"的なものが存在していましたが、共有DBは「4.3 共有データベース」でけちょんけちょんにされます。この項は衝撃的で実に面白い内容でしたし、項の終わりが「(データベース統合)はいかなる代償を払ってでも避けてください」というインパクトのあるものです。

疎結合の喪失

従来のRDBを用いたデータ参照は"一般的"であるとしながらも、DBへアクセスする全ての関係者とデータ構造全体を共有しているため、極めて結合状態にあり、スキーマ変更などの影響範囲が大きくてリスクが高くなるとしています。 加えて特定のDBミドルウェアへのアクセスには専用のドライバを使用するケースが大きく(JDBCのような?)、データストアしてRDBではなく非リレーショナルなデータストアミドルが適切であったと後から気づいたとしても、コンシューマ側への変更が多大であって、ミドルの変更は現実的ではなくなるという問題があります。

凝集性もグッバイ

DBを直接操作するようなケースでデータの変更ロジックが複数のコンシューマ側に存在しがちとなり、DB側の変更が複数のコンシューマの変更に繋がってしまう問題があります。複数のサービス内に同じテーブルのUPDATE文がありえてしまうという問題です。

データベース統合による弊害

データベース統合は各サービスがデータを共有しやすくなりますが、振る舞いの共有は何もせず、内部表現が直接公開されてしまうことで、破壊的変更が避けがたくなってしまうという問題があるため「(データベース統合)はいかなる代償を払ってでも避けてください」としています。

サービス間の連携は同期、非同期通信があるよ

同期通信と非同期通信に関する説明です。

  • 同期通信は処理の呼び出し、応答がくるまでブロックする
  • 非同期通信は呼び出して後のブロックを行わずに、以下の2パターンが存在する
    • リクエスト/レスポンスは同期通信でも行われるが、非同期でも利用可能で、処理の終わりをレスポンスで検知する
    • イベントベースは非同期通信のみで、他サービスに指示(リクエスト)を送らず、ただイベントが発生したことを通知し、他サービスがヨロシクやってくれるのを期待する

モデル化手法も知っておこう

ビジネスプロセスをモデル化する2つの手法について説明が入り、マイクロサービスであればコレオグラフィシステムを目指すべきとしています。

  • オーケストレーション手法は、モデル化されたフローチャートがそのままサービスのリレーションになる
    • わかりやすい
    • リクエスト/レスポンス
    • 特定のサービスが中央集権になりがち(貧弱な神がうまれる)
  • コレオグラフィ手法は、イベントベースで各サービスが正しく動くことを期待する
    • ビジネスプロセスが暗黙手な実装になり(わかりづらくなる)
    • イベント発行後に各サービスが適切に動作したかを監視する必要がある
    • 各サービスの変更に強くなる

リクエスト/レスポンスを実装するための技術

リクエストレスポンスの実装に適したRPCとRESTについて解説が入ります。

リモートプロシージャコール(RPC)

RPCはローカルで呼び出しを行い、リモートサーバで実行するテクニックで、メリットもあるとしながら、おすすめできないという結論になっています。

  • インターフェース定義に依存する、SOAP,Thrift,Protocol Buffers
    • インターフェースさえ同じであれば、異なる技術スタックを選択できるというメリット
  • Java RMIはインターフェース定義は不要だが同じ技術基盤を使用する必要があり、密結合が生じる
  • RPCは早く実装できる以外のメリットは少ない
  • ローカル処理とリモート処理が区別できなくなり、負荷を無視した処理を行いがち
  • RMIは密結合を起こしがちだが、Protocol BuffersやThriftならサーバとクライアントが別デプロイ可能となる

REST(REpresentational State Transfer)

Webからアイデアを得たアーキテクチャ。REST自体にも様々な形式がありリチャードソン成熟度モデルは一読すべしとあります。 ※英語力が低い僕としてはこちらの方が短くてありがたかった

リチャードソン成熟度はざっくりREST実装をどの程度成熟して使用できているかの評価モデルであり以下のような4レベルが定義されているとのこと。

  • レベル0は何も考えてない
  • レベル1はURIは使えている
  • レベル2はURIとHTTPは使えている
  • レベル3はURIとHTTPとHATEOASが使えている
    • HATEOAS(Hypermedia As The Engine Of Application State)は"アプリケーション状態エンジンとしてのハイパーメディア"の意味で、リンクを辿ることでコントロール達することができるような概念
      • 要するに何かを実行するために、どこを探せば良いかだけを知っておいて、実行する際は探して見つかったものを実行する

RESTはHTTPを利用することが以下の理由でメリットがあり一般的だそうです。

  • もともと動詞(GET,PUT,POST)を持っていて新たな概念を定義する必要が無い(SOAPが失敗したのはこの概念の新定義がイマイチだったとか)
  • HTTPプロトコルを制御する膨大なエコシステムを利用できる

RESTにおけるデータ形式JSONが人気ですがXMLにも利点があり、著者のSam Newman氏は自分は少数派としながらもXMLを推すそうです。

  • JSON
    • 形式が単純で利用が簡単
    • 人気
    • 欠点としてLinkコントロールの定義が無い
  • XML
    • Linkの問題が無い
    • 多くのサポートツールが優れている(XPath)

当然ながらHTTP上のRESTにも欠点はあり、それは以下としています。

  • クライアントスタブの作成は、RPCよりは大変
    • 優れた多くのHTTPクライアントライブラリは使用できるが、HATEOASまで実現しようとするとフルスクラッチになりがち
      • こっそりRPCを持ち込んだりしてしまう
      • 共有クライアントライブラリを作ったりしてしまう
  • HTTPの持つ動詞で、PUTやDELETEを実現するのは複雑になりがち
  • 大量トラフィックには向いているが、低遅延通信には向いていない
    • WebSocketの方が優れている

イベントベース連携をを実装するための技術

続いてコレオグラフィシステムで用いるイベントベース連携を実装するための技術についての解説で、はじめに考慮する点は以下の2点とあります。

  • マイクロサービスがイベントを発行する方法
  • コンシューマがイベント発行を検出する方法

これらの実装方法については以下があります。

  • 従来はRabbitMQなどのメッセージブローカが対応しており、構築と運用が大変なものの稼働させられればとても効果的な方法である
    • ただしシンプルに使うことが重要で、ベンダーはキューに機能を盛り込みがちなことは注意
  • HTTPを利用したATOMフィードを利用する方法
    • ただしRESTでも示したようにHTTPプロトコルであるための欠点(低遅延が苦手)はある
  • 現在JSONを使っていて問題なければそれでもOK(ここは文脈が読めなくて、JSONやめろと言っているようにも読めます。。。)

非同期アーキテクチャの複雑さ

これまでイベントベース連携すごい、非同期通信ナイスと持ち上げてきましたが、この項では実際の失敗談を交えながらそれらを実現するために生じる複雑さについて考える事を呼びかけられます。 筆者の教訓としては以下があったとしています。

  • ジョブの最大リトライ上限を設定
  • 不適切なメッセージの監視
  • 再現手法の準備
  • 最終的なメッセージ病院の準備(配達不能、失敗したメッセージの送り先のキュー)
  • 不適切なメッセージの参照および必要によってリトライするためのUIの準備
  • そもそもこの複雑なアーキテクチャで実装すべきかを真剣に検討するようになった
  • 相関IDを利用したプロセス境界をまたいだ適切な監視がマジで大事

覚えておきたい考え方など

ここで流れが変わり、2つの考え方とライブラリについて簡単な説明が入ります。

  • 状態マシンとしてのサービス
    • どんな(RPCでもRESTでも)実装を行うにしても、コンテキスト境界内の振る舞いに関連するロジックを、別のサービスに流出させては行けない
  • Rx(Reactive Extentions)
    • 複数の呼び出し結果をまとめ、それに対応して操作を実行するライブラリ
      • サイトによると「オブザーバブルシーケンスを使用した非同期でイベントベースなプログラムのためのライブラリ」とある
    • 主要な言語に対応している
    • サービスの呼び出しが増えて来ていて、特に1つの操作のために複数の呼び出しを行っている場合はRxを思い出すと吉かも

マイクロサービスではコード重複を認めよう、なぜなら

共有コードは結合を生みかねない。ゆえにカスタムテンプレートを利用して各サービスは都度それをコピーして開発し、コード重複は許容しようとしています。 DRY(Don't Repeat Yourself)は一般的にコード重複を避けるべしと理解されがちですが、正しくは「システムの振る舞いと知識の重複を回避する」ことを指すとのことで、

  • 一つのサービス内でのコード重複はDRYに
  • 全てのサービス間でのコード重複は許す

とし、経験則からサービス間の結合が及ぼす害は、コード重複の外よりも大きいためであるためとのことです。

クライアントライブラリについて

この項は項番が間違っているんじゃないかと思っています。「4.11 マイクロサービスの世界におけるDRYとコード再利用のリスク」に対して「4.11.1 クライアントライブラリ」となっているのですが、内容に関連性が無いためです。 この項ではクライアントライブラリについて、AWSSDKが良い手本であるとし、サーバAPI開発者がクライアントライブラリを作成すると、クライアントライブラリ側にサーバ側のコードが結合しがちであり、Netflixにおいても結合が発生し始めているともあります。大事なポイントとしては

  • クライアントコードを分離すること(サーバ側のコードから)
  • トランスポートプロトコルへの対処(サービスの検出や障害、目的のサービスに関連すること) ※ここは僕は理解できてないorz
  • クライアントライブラリのアップグレードのタイミングを、クライアントに管理させる

力尽きたのでここまでorz

やはり4章は長いですね。。。この感想文は1度さらっと読んでから、読み直しつつ書くというやり方をしていますが、今日は力尽きたのでここまでにします。次は「4.12 参照によるアクセス」です。

マイクロサービスアーキテクチャ本の感想(3章)

あけましておめでとうございます。雑煮とお酒と3歳児と遊ぶ年始を過ごしながらO'Reillyのマイクロサービスアーキテクチャ本感想の第2回です。

3章「サービスのモデル化方法」を読んで

3章では実際にマイクロサービスをどのように実践していくかの手始めとして、サービスの境界について説明がされています。

優れたサービスにするための疎結合と高凝集性

本章以前でも触れられていた疎結合と高凝集性について改めて語られています。絶対に忘れてはいけない非常に大切な2つの概念であると位置づけており、一言でいうと以下になります。

  • 疎結合とは、特定のサービスへの変更が他のサービスへ影響しない状態であること
  • 高凝集性とは、振る舞いが同じものを適切にまとめること

コンテキスト境界という考え方

続いてコンテキスト境界(Bounded Context)という言葉が出てきます。こちらもマイクロサービスの概念を語るうえでとても重要な用語でありエリック・エヴァンスのドメイン駆動設計にも触れながら説明がされています。僕はざっくり1マイクロサービス=1コンテキスト境界が理想という理解をしました。

  • ドメイン、事業全体を差す大きな枠(詳しくはDDDを読んだ方がよさそう)
  • コンテキスト境界、ドメイン内で異なるコンテキストごとにまとめたもの
  • コンテキスト境界内には以下の二つのモデルがある
    • 隠れモデル、他のコンテキストへ見せる必要がないもの
    • 共有モデル、他のコンテキストと共有するもの
  • インターフェース、共有モデルを実際に受け渡しするエンドポイント

外へ見せる必要の無いものを隠すことで疎結合の'疎'を実現しつつ、コンテキスト毎にまとめることで高凝集性を高めること、それがコンテキスト境界であるといったところでしょうか。

モジュールとサービス

続いてモジュールについての説明がされますが「あれっ?」となりました。第1章の「1.4 他の分解テクニック - 1.4.2 モジュール」の項で、モジュールも蜜結合になりがちだから気をつけろというような文脈があるのですが「3.3.2 モジュールとサービス」ではモジュール化しましょうとあります。読みなおすと"コンテキスト境界内では"モジュール化をするべきで、それは隠れモデルと共有モデルを体現したコードベースであることが望ましく、そのモジュールがそのまま一つのマイクロサービスの候補となります。

コンテキスト境界を定義する方法アレコレ

その後はどのように境界を定義するかの話が続きますが、GoCDにおける失敗談(早く分割し過ぎて定義を失敗し、一度モノリシックにして再び分割しなおした)で釘を刺しつつ以下が挙げられます。

  • ビジネス機能では該当サービスが何のデータを他サービスへ提供するかを考えるのは二の次で、とにかく機能を先に考えることが重要
    • データに着目して分割すると機能不足になりがちだから
  • コンテキスト境界内での分割も進めていくべし(共有モデルには影響しないように)
  • とはいえ技術的な観点からも注意して分解すること

3章の感想

3章はコンテキスト境界という概念を学ぶための章だったなという感想です。こういった高水準(技術よりではないという意味)な内容は、概念を定義するための用語がバンバン出てきて正直初見は辛いのですが、"コンテキスト境界"のように言葉として定義されることで理解が進むんですよね。分解の方法については読んでいる分には当たり前なんですが、実業務だと同じ失敗をしそうですが、本書を読んだことですぐに失敗に気づけることを願います。