lasciva blog

開発して得た知見やwebビジネスのストック

「Amazon Japan Tech Night #2」に参加してきた

amazonjapantechnight2.splashthat.com

Amazonのサービスを開発している方の話を聞けるのが珍しいので、参加してきました。
以下の目的で、四半期に1回ぐらいのペースでやっていく予定だそうです。

東京拠点の開発チームは主に4つあるそうです。

  • marketplace
  • INTech(小売)
  • Search
  • device(Alexa/Kindleなど)

発表内容以外の点で、他のミートアップと違うと感じたところは下記です。

  • NDA契約が必要
    • 確認したところ、スライドのシェア等は大丈夫で、懇親会等で知り得た機密情報とかが禁止という意味だそうです。
  • 閉会のときに抽選会があり、Kindle系の商品が貰える

発表

※ 客観的に記述したつもりですが、私の理解不足等で誤っている可能性もありますので、予めご了承ください。

Amazon Points

Amazon Pointsチームという、ポイント関連の開発を行ってる方々の発表でした。

登壇者

Matsuiさん

komodaさん

The Feature
  • Japan-first
  • 東京がメインのチーム
  • 顧客視点から見た機能
    • 商品ページのポイント表示
    • キャンペーン(本のまとめ買いなど、PrimeDay)
    • クレカのポイントバック
The Tech

f:id:hacking15dog:20190912121328j:plain

f:id:hacking15dog:20190912121340j:plain

デザイン

  • SOA, Microservices
  • 他のサービスと密接なcollaboration
  • 他システムと疎結合にすること
  • 求められるスキル
    • 他のサービスを含めコードを読むことが多いため、素早く理解すること
    • 大規模システムにおけるハイレベルな設計

インフラ面

  • (もちろん、)AWSフル活用
    • EC2, Lambda, S3, DynamoDB, SNS, SQS, Kinesis, SWF, Step Function, EMR, Redshift, Glue...
    • 公開されてない機能もあり
  • ほぼすべてのものがAmazon自身によって開発されたもの
    • AWSだけでない
    • OSSコミュニティと比較しても、社内の開発コミュニティは大きい
      • Web Framework, Build Tool, Monitoring, Alarming, ML...

Fulfilment By Amazon Preorder Enhancement

自己紹介

JP Seller Tech Team

  • 前職: KDDI
  • 2016年入社
  • チームは10人以上いる
Mission

Innovate on behalf of third-party sellers and to improve both cusromer and seller ecperiences across Amazon Marketplaces

最近の事例

FBAの予約注文の改善

f:id:hacking15dog:20190912121357j:plain

背景
  • 予約商品が売りづらい
    • 発売日の設定、変更がseller自身でできない

f:id:hacking15dog:20190912121405j:plain

解決方法

f:id:hacking15dog:20190912121421j:plain

f:id:hacking15dog:20190912121456j:plain

f:id:hacking15dog:20190912121501j:plain

  • SNSと SQSQueueでサーバレスなのでスケーラブル
  • Downstreamがサブスクライブして処理を行う
  • DeadLetterQueueに失敗したメッセージを突っ込む

f:id:hacking15dog:20190912121505j:plain

f:id:hacking15dog:20190912121510j:plain DynamoDBにrequestを保存することで、workflowで複数の通知を一つにまとめることができる

Customer Service By Amazon

自己紹介

JP Seller Tech Team

  • 日本に9年
  • 1年間は群馬にいた
CSBA Overview
  • sellerのCS代行サービス
    • 中国のsellerが日本で販売するときなど
Seller

f:id:hacking15dog:20190912122133j:plain

Notification

考慮したこと

  • セキュリティ
    • 個人情報などはNotificationサービスでは持たない
  • スケーラビリティ
    • Queueを使って非同期にしたり、サービスを分割して適切にリリースを調整できるように

f:id:hacking15dog:20190912122142j:plain

Tech1 Release Gating

リリース時の下のような課題に対して、QAゲートを使って解決

fn existingMethod() {
  ...
  if (releaseGate.useFeature("new_feature_42")) {
    newFeature.doStuff();
  }
  ...
}

Gradual Release

  • 何%か指定することができる
  • 本番環境でE2Eテストするために、QAだけに許可できる
  • 障害が起こった際に影響を最小限に抑えるために、徐々にリリースできる
Tech2 Service Mocking

たくさんの他のマイクロサービスに依存したり、環境が異なったりして辛い。
そこで、依存しているサービスのモックをフレームワークによって作成して解決。

f:id:hacking15dog:20190912122400j:plain

f:id:hacking15dog:20190912122405j:plain

Katana

Applied Scientist

f:id:hacking15dog:20190912120439j:plain

f:id:hacking15dog:20190912120503j:plain

f:id:hacking15dog:20190912120606j:plain

Katana はJapanese Text Tokenizerのこと。
既存のトークナイザーは辞書を持っているタイプのものが多く、以下の要件に対応できない。

  1. MultiDomain(Product,音声デバイスなど)
  2. (検索ワードなどの)新しい言葉などに対応しないといけない

f:id:hacking15dog:20190912120625j:plain

f:id:hacking15dog:20190912120647j:plain

懇親会

  • 基本的には英語を使っている
  • Javaを使ってるサービスが多く、社内専用のフレームワーク等もある
  • 中には、社内のサービスを扱うため専用の言語があったりもする
  • 攻めた開発やリリースができるような仕組みを整えられてる
    • リリース時に、段階的にリリースできる仕組み
    • 恐れずRollbackもできる仕組み
  • いわゆるSREチームはなく、アプリケーション担当が自分のサービスのインフラも見る

「マイクロサービスアーキテクチャ(Building Microservices)」を読んだ

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

目的、モチベーション

  • マイクロサービスのサービスに携わり始め、基礎を学びたかった
  • 英語のお勉強

全体の感想

モノリスとマイクロサービスの比較から始まり、どのようにマイクロサービスに移行していくか、設計や実際の運用する上での話などが一通り網羅されていて、全体感を掴めました。
一方で、個別の細かいレベルでのツールや技術の選定や導入、運用方法は別途学ぶ必要があると思います。
マイクロサービスの導入によって生まれる課題も想像以上にあるので、安易に流行ってるというだけで導入するのは危ないなと思いました。

マイクロサービスやってるバックエンドエンジニアには前提となる知識が網羅されていて、必読な感じの本に思えました。
マイクロサービスの経験のないチームで導入の検討をしているときは特に参考になると思います。

今回は英語版を読んだのですが、いつもの5倍前後ぐらい時間がかかった気がします。
効率がかなり悪かったので、今後は暇な時期か日本語版がないときだけにしようかと思います。

目次

概要、気になったところ

前提として、私はマイクロサービス歴は数ヶ月で、モノリスでの経験も中規模程度で、モノリスでかなり苦しんだ経験もないです。
その中で、気になったところを中心にまとめました。

1章 マイクロサービス

1.1 マイクロサービスとは
  • 単一の役割の責任を満たすぐらい小さいこと
    • 十分小さい状態とは、大き過ぎない状態のこと
    • 小さくし過ぎると、恩恵が最大化される一方でデメリットも最大化される
  • 自治的であること
    • 他のサービスを意識せずに独立してデプロイ等を行えるか
1.2 主な利点
  • 適材適所な技術を使える
    • 試験的な導入も影響範囲を狭めて試す
  • resilenceでエラーを局所化できる
  • 必要な部分だけスケールさせられる
  • デプロイが速く、安全に行える
  • 少人数のチームで開発効率を維持できる
  • リプレイスが容易
1.4 他の分解テクニック

共有ライブラリ

  • どのプラットフォームでも動くようにしないといけないので、言語をサービス間で同じにしないと辛い面もある
  • ライブラリの更新によって、デプロイの独立性が阻害されないように工夫が必要
  • ライブラリの種類によってはサービス間が密結合になってしまうかもしれない
1.5 銀の弾丸などない

マイクロサービスも良いところばかりでないので、考慮した上で導入すること。

2章 進化的アーキテクト

2.1 不正確な比較

よく建設の設計者に例えられるが、ソフトウェアエンジニアは歴史が浅く、何か明確になるような比較対象を求めがちだが、役割に誤解を招いて幅を狭めるのでよくない。

2.2 進化するアーキテクト像
  • もし例えるなら、市の設計者が適切である。
  • 市は外部の影響を受けるし、長期的な時間を見越してみんながハッピーになれるよう考える必要がある。
  • 地域や、インフラ整備なども似ている。
2.3 区画指定
  • zone間でのやり取りに気を配るべき。
  • zone内での技術等はzoneに任せるべき。
    • ただし、zone毎に最適化しすぎて様々な技術を使いすぎると、採用や人の異動を行いにくくなる
2.4 原則に基づいたアプローチ
  • ビジネス的な戦略を把握する
    • 考える形では、アーキテクトは関わらないことが多い。
  • 原則
    • 戦術を遂行するためのもの
  • 実践
    • 原則を実現するための方法
2.10 チームの構築

偉大なソフトウエアは偉大な人から生まれるので、人を育てるのは重要。マイクロサービスはモノリスよりもサービスのライフサイクルや成長を自主的に把握しやすいので、相性がいい。技術だけしか興味を持ててないのは、成長の半分にしか貢献できてない。

3章 サービスのモデル化方法

3.2 優れたサービスにするには

各サービスが他のサービスとの調整なしに、独立してデプロイでき、影響範囲を局所化するために、疎結合と高凝集性が重要。

3.3 境界づけられたコンテキスト

DDDなどを参考に、ドメインを切り分けていく。
一気にサービスを独立させるのではなく、まずはモノリスでモジュールレベルで分解して検討していく。
誤ってサービスを切り分けると開発コストがよりかかるため、モノリスで十分様子を見てからサービスを独立させる。

4章 統合

4.1 理想的な統合技術の探索

下記を満たすべき。

  • 破壊的変化を避ける
  • 特定の技術に限定されない、変更可能
  • 他サービスからシンプルに使えるように
  • 詳細の実装を隠蔽
4.3 共有データベース

以下の理由からアンチパターン

  • スキーマ変更するだけで、大きな影響を与える
  • サービス毎に適切な技術選定が行えない
  • 変更箇所が各サービスに渡る
4.5 オーケストレーションとコレオグラフィ
  • オーケストレーションだとシンプルだが、それぞれのレスポンスを束ねる神サービスができて、密結合になりがち
  • もう一方では、それぞれの処理を各サービスに委譲できて疎結合サービスになるが、全体で適切に処理が行われたか監視しないといけない。
4.13 バージョニング
  • 原則として、破壊的な変更はなるべく行わないこと。
  • Tolerant Readerを使って変更に強くする。
  • protobufでネームスペースを使うと、変更がないバージョン変更が辛いので、オススメしない。
4.15 サードパーティソフトウェアとの統合
  • カスタマイズ
    • ベンダーの提供するカスタマイズを行うには、スクラッチで作るよりもコストがかかりうる。
    • ベンダー側に合わさせるのではなく、ベンダーに合わせる。
    • アップグレードで動かなくなることも多々ある。
  • CMSの例
    • カスタマイズしようとして、秘伝のCSSなどができがち。
    • コンテンツの取得、更新をAPI経由で行い、フロントエンドは自前で対応するとよい。
  • CRMの例
    • CRMは社内の組織に大きく影響を及ぼし、後々に肥大化する。
    • CRMのサービスは何でも対応できて、巨大で一つに集約されてしまう。
    • facadeパターンで、ドメインの関心ごとにサービスをつくり、外部からはシンプルなインターフェースで使えるようにする。
  • レガシーなサービスとの共存
    • リプレイスや機能削除を行おうにも、どこが使われてるか不明で、進めにくい。
    • 他のパターンと同様に中間のサービスを用意して、直接参照しないようにする。
    • 徐々にリプレイスもでき、ビッグバンリリースを避けれる。

5章 モノリスの分割

全体としては以下の順に進める。

  1. コードの境界を定めて、パッケージなどで分割していく
  2. 分割できたら、一つ一つ徐々にサービス毎に切り出す
5.3 モノリスを分割する理由
  • 変更できるスピードが上がる
  • チームごとに責任を持って進めやすくなる
    • 例えば、ロンドンとハワイにチームが開発者が別れてるケースなど
  • セキュリティを個別に強化するなど、個別に必要な機能追加を行いやすい
  • サービスごとに適切な言語選定や新しい技術の導入が行いやすい
5.5 データベース
  • まず、DBへの参照や更新をそれぞれの境界ごとにリポジトリレイヤーに切り出す
  • 境界間で外部キーなどの依存関係がある場合は、SchemaSpyで依存関係を可視化する
5.7 例:外部キー関係の削除
  • DBは別々にして、API経由で参照する。
  • パフォーマンスが低下するかもしれないが、トレードオフなので、どれだけパフォーマンスが求められるか考慮する。
  • 外部キー制約が失われるが、別のサービスで監視するなどの工夫が必要。また、データを本当に削除してもよいかなどの挙動の取り決めも必要。
5.9 例:共有データ

別々のサービスが同じデータを参照する場合は、そのデータのサービスを別途切り分ける。

5.10 例:共有テーブル

列の多いテーブルはそれぞれのサービス毎に縦に分割する。

5.11 データベースリファクタリング

他の例はデータベース・リファクタリングを読むべし。

5.11.1 段階的な分割

段階的に移行すべし。

  • 初めにDBの分割を行ってから、アプリケーション側の分割を行うことで、リバートできるようにしておく
  • ただし、DB間のトランザクションを扱ったりする必要がある
5.12 トランザクション境界

DBが分割されると、整合性を保つのに工夫が必要。

  • あとでリトライして、結果整合性を担保する
  • ロールバックを実現するために、delete文を発行する
  • 分散トランザクション
    • 上述の方法だと、複雑なものに対応できなくなる。
    • 2段階コミット
      • 1段階目でそれぞれのDBでコミットできるか確認する
      • 2段階目で全てokなら実際に各DBで実行し、NGなら実行しない
      • 1つでも障害が起こると死んでしまうなどの欠点がある。
  • 複雑なので、本当に整合性を守らないといけないのかの検討をする
5.13 レポート
  • モノリスのDBを直接参照するのはデメリットがある
    • スキーマの変更に影響される
    • 本番環境のパフォーマンスに影響を及ぼしうる
    • 適切な技術選定ができない
  • マイクロサービスのAPIコールで実現するには工夫が必要
    • バッチ用のAPIを用意して、ポーリングし続けてCSV等の形式で共有データに出力されたものを参照するなどの方法がある
    • アプリケーションレイヤーで処理するには、データ量が多く危険な上、処理中に整合性が保てなくなる
  • 各サービスがデータをpushして一箇所で管理する方法の方がベター
    • 疎結合ではなくなるが、反するメリットの方が大きい
5.21 根本原因の理解
  • まず分割の重要性を理解した上で開発すること
  • 対応困難なレベルになる前に分割すること
  • 徐々に進めていけるものなので、恐れないこと

6章 デプロイ

サービス毎にリポジトリを分割すべき。

  • CIでのテストの短縮や、デプロイの時間短縮につながる
  • インフラは出来れば1サービス毎に1ホスト用意すべき
    • 複数のサービスが同一ホストにあると、依存関係ができる
    • 1サービスがメモリを喰ったりすると、他のサービスも道連れになってしまう
    • 監視が困難
  • 1ホストに対して1サービス提供するために、dockerなどの軽量なコンテナを使うべき
    • VMはオーバーヘッドが大きく、サービスの数が増えるとスケールしにくい
    • 各サービスに必要なミドルウェアなどはバージョン管理等のことを考えるとイメージとして保存しておくべきだが、容量が大きくなってしまう
  • コンテナ間の制御はk8sなどを使う

7章 テスト

テストは分類でき、何を目的にしてるのかを明確にする

ユニットテスト

  • 関数単位とかのレベル
  • 実行時間は短いので、どんどん書くべき

サービステスト

  • stubやmockを活用

E2Eテスト

  • 全体でちゃんと動くか保証できるので安心感がある
  • マイクロサービスでは実現するのにコストがかかる
  • 複数のサービスにまたがると、全部ビルドしないといけないので、実行時間がかなりかかる
  • どのサービスでE2Eのテストをカバーするのかの取り決めを行わないと、サービス間でテスト内容が重複したりする
  • 後述のCDT(Consumer Driven Test)を増やして、必要最低限に数を減らすこと
7.8 救いとなるコンシューマ駆動テスト
  • サービス間で期待するインターフェースを取り決めておき、それに従うようにテストを行う
  • PactPactoなどでは、JSONなどでインターフェースを定義しておけて、モックも提供してくれる
    • JSONなどの形式だと、サービスの言語に依存しないので嬉しいケースが多い
7.10 本番リリース後のテスト
  • デプロイとリリースのタイミングを分離することで、内部でテストを行うことができる
    • ブルーグリーンデプロイなどのこと
    • テストを行ってから、新しいサービスにリクエストを投げ始める
    • 古いサービスは少しの間は残しておいて、エラーが発生したら切り戻せるようにしておく
  • canary releasing
    • 新旧のサービスを共存させて、徐々に新サービスへのリクエストの割合を増やしていく
    • 新旧でレスポンスを計測して、パフォーマンスに問題がないかなどをウォッチできる

8章 監視

  • サービス間で共通の形式でログを出力すること
  • ログは一つのサーバに集約させて調査しやすいようにすること
  • 外部からのリクエストはどのサービスでも同一だと認識できるように共通IDをログに含めることで、調査できるようにすること

9章 セキュリティ

9.2 サービス間の認証と認可

大抵のパターンでは、ユーザ名・パスワードをセキュアに管理する必要があり、そこが厳密には難しい。
また、サービス間をHTTPSで通信を行う場合には、それぞれのサーバのSSL証明書を用意する必要がある。

9.2.1 境界内のすべてを許可する

一旦ネットワーク内に侵入されてしまうと、中間者攻撃を防げないので、あまりよろしくない。

9.2.2 HTTP(S)ベーシック認証

HTTPの場合は、ベーシック認証の情報がセキュアでないため、基本的にはHTTPSで行うべき。

9.4 徹底的な防御
  • ファイアウォール
  • ロギング
    • 攻撃されたあとの調査の材料になる
    • セキュアな情報は残さないこと
  • 侵入検知(および侵入防止)システム
    • 侵入者の検知のみならず、ファイアウォールとは異なり、内部での不審な動きも検知することができる
  • ネットワーク分離
    • AWSVPCのようなサービスで分離すること
  • OS
    • アプリケーションレイヤーが完璧に対策されていても、OSが古ければ脆弱性がある状態になるので、最新の状態に更新し続けること
9.6 節約する

不必要なデータを削除することは、データを盗まれる可能性を減らす上に節約につながる。

10章 コンウェイの法則とシステム設計

コンウェイの法則の事例等が紹介されていた。

11章 大規模なマイクロサービス

マイクロサービスで、扱うサーバやノードが増えれば増えるほど、問題の起こってる状態は増える。
GoogleNetflixでは、自らエラーを発生させてシステムが動き続けるかどうかの確認を行なっている。

11.5 アンチフラジャイルな組織

ハードウェアやネットワークに問題が起こる前提で、反脆さに強い分散システムをつくる必要がある。

  • タイムアウトを適切に設定すること
  • サーキットブレーカー
    • タイムアウトや5xxエラーが増えた時に、サービス間の通信を遮断して即座にエラーを返すようにする仕組み。
    • 遮断している間に、少しだけリクエストを送って回復したかを検知して、回復したは復旧する。
11.8 データベースのスケーリング
  • Read: replicaを増やす
    • ただし、結果整合性を許容することになる
  • Write: シャード
    • 集合を扱うとき、シャード毎の結果を結合しないといけない
    • シャードを増やすとき、リバランスを行わないといけない
  • Shared Database Infrastructure
    • 便利な反面、SPOFになりうるリスクがある
  • CQRS(Command-Query Responsibility Segregation)
    • TODO
11.10 オートスケーリング

トラフィックの増減に応じてインスタンス数をコントロールするだけでなく、最低値を設定して自動で復旧させるのも便利

11.11 CAP定理

「一貫性(Consistency)」「可用性(Availability)」「分断耐性(Partition-tolerance)」の3つを同時に完璧に満たすのは不可能だという定理。
分散システムにおいては、分断耐性を犠牲にすることはできない。そのため、通信の遅延が起こった際に、システムの要件に応じて一貫性と可用性のトレードオフのバランスを取ることが求められる。

11.13 動的サービスレジストリ

下記のサービスが紹介されていた。

  • ZooKeeper: 本番運用もされてることが多く信用性は高いが、ヘビーで理解が難しめ
  • Consul: 本番運用例はあまりないが、有名なコミュニティが開発してるらしく、一見の価値あり
  • Eureka: Netflixによるもので、上記の2つよりは用途を限定している

12章 まとめ

12.2 マイクロサービスを使用すべきでない場合
  • 境界を適切に区切るのに十分なドメイン知識がないとき
    • 間違って境界を設定すると、よりコストがかかる
  • サービスの数が増えれば増えるほど、マイクロサービス特有の監視などの課題は重くなっていくので、徐々に進めていくべき
12.3 最後に

マイクロサービスにすることは、決断ではなく旅のように長くコツコツやっていくつもりで対応すること

次のアクション

概要はわかった気にはなれたので、サービス運用しながら学んでいこうと思います。
サービスの切り分けや、サービス間のデータの整合性の保ち方などは特にもう少し深堀りたいなと思います。
マイクロサービスの課題も少しは掴めたので、k8sで何を解決しているのかをもう少し理解したいと思います。

マイクロサービスアーキテクチャ

マイクロサービスアーキテクチャ

「分散システムデザインパターン」を読んだ

分散システムデザインパターン ―コンテナを使ったスケーラブルなサービスの設計

分散システムデザインパターン ―コンテナを使ったスケーラブルなサービスの設計

目的、モチベーション

  • 分散システムの記事等を見たときに、サイドカー等の理解してないキーワードを散見するようになり、理解したかったため。
  • コンテナを使ったマイクロサービスを運用しており、理解を深めたかったため。
  • Faasなどのクラウドサービスの使い分けを把握したかったため。

全体の感想

シングルノードのときや、マルチノードのときなど幅広く取り扱われており、なんとなく単語レベルでは知ってるパターンの理解を深めることはできた。
インフラ寄りのデザインパターンがほとんどだが、アプリケーションにも関与していて開発しているときにも他の選択肢が増えて視野が広がった気がする。
マルチノードパターンでは、負荷対策などのためのスケール方法が多く記載されていて面白かった。
全体的に解決しようとしてる課題感は掴めたが、現場でSRE的な役割で運用してみないと。

目次

概要

1章 はじめに

分散システムが前提となった時代において、アルゴリズムオブジェクト指向などと同様にパターン化して公式化することが本書のゴール。

第Ⅰ部 シングルノードパターン

シングルノードパターンを使う理由

  • リソースの分離
  •  関心の分離・影響範囲の局所化
  • デプロイやロールバックを容易にする

2章 サイドカー

1台のマシン上で動く2つのコンテナから構成されるパターン。
ホスト名、ネットワーク、ディスクなど多くを共有する。

  1. サイドカーの例:レガシーサービスのHTTPS 対応
    • nginxなどのSSLのプロキシを挟んで、localhost上でアプリケーションを動かしてアクセスする。
    • アプリケーションがレガシーでHTTPSの対応が難しいときなど。
  2. サイドカーによる動的な設定
    • クラウドネイティブな場合、設定はAPIで更新する方が便利。
    • 既存のアプリケーションを拡張するよりも、サイドカーに設定を更新させるようにさせた方が容易なことがある。
  3. モジュール化されたアプリケーションコンテナ
    • コンポーネントのモジュール化と再利用性の例
    • プロセス情報を取得するためにAPIを提供する場合
      • 各アプリケーションで統一的に対応するのは言語が異なったりして困難。
      • サイドカーを利用して一貫したインターフェースで情報を提供できるようになる。

3章 アンバサダ

アプリケーションが外部サービスに接続する際に、プロキシとなるコンテナを経由させるコンテナグループを作るパターン。

1. サービスのシャーディングへのアンバサダの利用

Redis等でシャーディングさせてる際に、ノードを増やす度にアプリケーションをデプロイし直す必要がなくなり、ロジックを共通化できてアンバサダコンテナを独立してデプロイすることができる。

2. サービスブローカとしての利用

開発環境や、本番環境などで外部サービスのホストやポートの設定が異なる可能性が高いが、そのロジックをアンバサダコンテナに委譲できる。

3. 新システムの実験的運用やリクエスト分割への利用

バージョンアップなどで新システムを本番環境に導入する際にも、試験的に導入することができる。
例えばアンバサダの設定を変更して、既存のシステムを稼働させつつ、新システムにも同じリクエストを裏側で送って負荷を確認することができる。

4章 アダプタ

コンテナグループ外に対して、外部インターフェイスを提供する際にアダプタコンテナを経由して提供するパターン。

  1. 監視
    • 監視ツール用のインターフェースに変換することで、アプリケーションの責務を分けることができる。
    • 監視コンテナを提供する開発者と、アプリケーションの開発者を切り分けることができる。
  2. ロギング
    • 監視と異なり、ログはレベルごとにファイルが異なったり、標準出力してるだけのことがあり、フォーマットがバラバラ。
    • アダプタパターンで、モジュール化して再利用して、フォーマットを統一することができる。

第Ⅱ部 マルチノードパターン

6章 シャーディングされたサービス

1. シャーディングされたキャッシュ

シャーディングされてる場合は、一つのノードが死んだ場合に特定のユーザへのレスポンスが遅くなるリスクがあるため、キャッシュのレプリカを用意したほうが高可用になる。

2. シャーディング関数を試してみる

キーの選択
異なる国からリクエストがある場合、言語ごとにキャッシュを切り替える必要があるため、単にIPアドレスを元にハッシュを生成するのは良くない。

コンシステントハッシュ関数
ノードの数を増やすと再シャーディングする必要がある。このとき、シンプルなハッシュ関数だと再シャーディングのコストが高くなる可能性があるが、コンシステントハッシュ関数を使うと効率的に再シャーディングできる。

7章 スキャッタ・ギャザー

バックエンド内で、複数のノードを使って処理を分散させて高速化させるパターン。ただし複数のノードを使うことによるデメリットもある。

  • ノードごとのオーバヘッドがあるので、並列性を上げても必ずしも処理速度が上がるとは限らない
  • 落ちこぼれ問題(一部のノードでレスポンスが遅くなって全体のレスポンスが遅くなる問題)のため、並列度を上げても処理速度が上がるとは限らない。
  • 一つのノードで障害が起こると全体が止まるので、シャーディング毎にレプリカは必須。(バージョンアップを行う際などでノードは必ず止めないといけないタイミングがある。)

9章 オーナーシップの選出

マスタの選出の実装は困難なため、 etcdApache ZooKeeperConsulなどのコンセンサスアルゴリズムを利用する。

第Ⅲ部 バッチ処理パターン

Apache Kafkaを使ったPub/Subのパターンや、MapReduceフレームワークでの協調する手法が紹介されていた。

分散システムデザインパターン ―コンテナを使ったスケーラブルなサービスの設計

分散システムデザインパターン ―コンテナを使ったスケーラブルなサービスの設計

「SQL実践入門」を読んだ

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

目的、モチベーション

  • パフォーマンス改善する際に考える材料として、実行計画周りの原理を理解したい

全体の感想

実行計画周りは思ってたより記載されていなかったです。
WINDOW関数などについても言及されており、効率的なSQLを学びたい初心者と中級者の間ぐらいの方にはオススメです。

メモ

効率的なSQLの書き方もたくさん記載されていましたが、あくまでも原理を理解することが今回は目的だったため、割愛してます。

第1章 DBMSアーキテクチャ──この世にただ飯はあるか

1.1 DBMSアーキテクチャ概要

f:id:hacking15dog:20190901150537p:plain Database Management Systemsより

  • クエリ評価エンジン
    • SQLを解釈して実行計画を行う
  • バッファマネージャ
    • メモリ領域の使い方を管理する
  • ディスク容量マネージャ
    • 永続的にデータを保存できるように管理し、読み込みや書き込みを制御する
  • トランザクションマネージャとロックマネージャ
  • リカバリマネージャ
1.2 DBMSとバッファ

I/Oアクセスを避けて、いかにメモリ上で処理を行うかが、パフォーマンス改善においてはキーになる。

1.3 DBMSと実行計画

データへのアクセス方法はどう決まるのか

  1. パーサ(parser)
    • SQL文を要素に分解してDBMSが処理しやすい形式に変換
  2. オプティマイザ(optimizer)
    • インデックス、データの分散や偏り度合い、DBMSの内部パラメータなどの条件から実行計画を作成して、それらのコストを計算して、実行計画を決定する
  3. カタログマネージャ(catalog manager)
    • テーブルやインデックスの統計情報をオプティマイザに提供する
    • 遅延が発生すると適切な計画が練れない一方で更新コストも高いため、適切な設定が重要
  4. プラン評価(plan evaluation)

第3章 SQLにおける条件分岐──文から式へ

3.1 UNIONを使った冗長な表現

UNIONを使うのは、条件分岐という手続き型の発想から脱していないことに原因があることが多い。
SELECT中のCASEで分岐したほうがテーブルのスキャン回数が減るのでパフォーマンスがよい。

3.3 それでもUNIONが必要なのです

CASEによってINDEXスキャンができなくなるときなどは、UNIONを使ったほうがパフォーマンスが良いケースがある。

第6章 結合──結合を制する者はSQLを制す

6.2 結合のアルゴリズムとパフォーマンス

Nested Loops
駆動表とするテーブルを1行ずるループしながら、もう一方の内部表となるテーブルをスキャンして結合条件に合致するものを検索する方法。
アクセスされる行数は、レコード数の積になる。

内部表の結合キーの列にインデックスが存在すると、ループがスキップできるようになりパフォーマンスが改善される。
このとき、内部表が大きい方がスキップされる行数が多くなり、よりパフォーマンスが良くなることが期待できる。

Hash
小さいテーブルからメモリ上でハッシュテーブルをつくり、もう一つの大きいテーブルをスキャンしてハッシュ値が存在するか調べる。
メモリ内に収まらないとTEMP落ちが発生してパフォーマンスが悪化するので注意。

Sort Merge
対象テーブルをソートして、一致する結合キーがあれば結果に含める。
そのため、片方のテーブルのみハッシュテーブルをつくるHashよりもメモリを消費する。 INDEXなどでソートしなくて良い場合には時間とリソースを節約できるため有効だが、基本的には上述の他のアルゴリズムが優先となる。

6.3 結合が遅いなと感じたら

ケース別の最適な結合アルゴリズム

種類 メリット デメリット
Nested Loops
  • 「小さな駆動表」+「内部表のインデックス」の条件下で高速
  • メモリやディスクの消費が少なくOLTPに適している
  • 非等値結合でも使用できる
  • 大規模テーブル同士の結合に不向き
  • 内部表のインデックスが使えなかったり、内部表の選択率が高いと低速
  • Hash
  • 大規模テーブル同士の結合に適している
  • メモリ消費量が多くOLTPに不向き
  • メモリ不足の場合はTEMP落ちが発生する
  • 等値結合のみで使用可能
  • Sort Merge
  • 大規模テーブル同士の結合に適している
  • 非等値結合でも使用できる
  • メモリ消費量が多くOLTPに不向き
  • メモリ不足の場合はTEMP落ちが発生する
  • データがソート済みでなければ効率的でない
  • そもそも実行計画の制御は可能なのか?
    MySQLは結合アルゴリズムがNested Loops系しかない。

    揺れるよ揺れる,実行計画は揺れるよ
    データ量が変更されることで実行計画が変化され、急にパフォーマンスが悪化することがある。
    WINDOW関数や非正規化などで、結合をなるべく避ける。

    第10章 インデックスを使いこなす──秀才の弱点

    10.4 インデックスが使用できない場合どう対処するか

    インデックスオンリースキャンによる対処
    通常ならテーブルのフルスキャンが発生するようなケースにおいても、選択した列を網羅するINDEXを貼ることで、テーブルスキャンを避けることができる手法。

    CREATE INDEX IdAndName ON users (id, name);
    SELECT id, name from users;
    

    次のアクション

    思ったより深ぼれなかったので、SQLパフォーマンス詳解あたりを読んでみようと思います。

    SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

    SQL実践入門──高速でわかりやすいクエリの書き方 (WEB+DB PRESS plus)

    Rubyスタイルガイドを読んだ

    RoboCopのRubyスタイルガイドからforkされたスタイルガイドを発見しました。

    github.com

    結構前からあるみたいなので、今更感はあります。。

    CookpadやMoneyforward、Airbnbなど色んな企業のコーディングスタイルガイドがありますが、
    それらと比べて、細かくボリュームもありブレもなくなりそうで良さそうに思いました。
    また、宗教戦争が起こりそうなところは、どちらもアリでプロジェクトの中での決めが重要というスタンスなのも良いなと思いました。
    いくつか知らないものもあったので、メモ代わりにまとめます。

    &&=

    ruby-style-guide/README.ja.md at japanese · fortissimo1997/ruby-style-guide · GitHub

    # bad
    if something
      something = something.downcase
    end
    
    # bad
    something = something ? something.downcase : nil
    
    # ok
    something = something.downcase if something
    
    # good
    something = something && something.downcase
    
    # better
    something &&= something.downcase
    

    使ったことなかったですが、somethingのnilチェックがスマートに行えます。

    Array#reverse_each

    ruby-style-guide/README.ja.md at japanese · fortissimo1997/ruby-style-guide · GitHub

    # bad
    array.reverse.each { ... }
    
    # good
    array.reverse_each { ... }
    

    パフォーマンスが良いそうです。

    Exceptionのメッセージ

    ruby-style-guide/README.ja.md at japanese · fortissimo1997/ruby-style-guide · GitHub

    # bad
    raise SomeException.new('message')
    # `raise SomeException.new('message'), backtrace`とする書き方が存在しないことに注意しましょう。
    
    # good
    raise SomeException, 'message'
    # `raise SomeException, 'message', backtrace`の用法と一貫性があります
    

    Kernel#raiseは下記のように2つの呼び方があり、後者に統一しようとのことのようです。

    # [https://docs.ruby-lang.org/ja/2.6.0/method/Kernel/m/raise.html:title]参照
    raise(message) -> ()
    raise(error_type, message = nil, backtrace = caller(0)) -> ()
    

    Hash#values_at

    ruby-style-guide/README.ja.md at japanese · fortissimo1997/ruby-style-guide · GitHub

    # bad
    email = data['email']
    username = data['nickname']
    
    # good
    email, username = data.values_at('email', 'nickname')
    

    Hashから複数の値を取り出す際には、 values_atを使った方がスマートに書けます。

    Object#__send__

    link

    メソッド名の衝突を避けるために、 sendよりも __send__を使った方がよいというルールです。
    例えばEmailクラスで独自のsendメソッドを定義した際に、衝突を回避できます。
    (そもそもあまり send使いたくないですが。)