分散システムデザインパターン読書メモ2

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

レプリカされたサービスではリクエストはどのレプリカでも処理することができた、
それに対してシャーディングされたサービスでは一部のリクエストを特定のレプリカのみが処理できる。 代表的な例として、データサイズが一台で処理するには大きすぎる場合に使われる。

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

  • シャーディングされたキャッシュは、完全なレプリカではできなかった数のキャッシュを行い、シャードを増やした分だけキャッシュヒット率をあげることができる

シャーディングされたキャッシュのレプリカ

  • シャーディングのレプリカはキャッシュサーバーの更新時のデプロイ時にも負荷の増大を防ぐことができる
  • シャーディングされた各ノードのレプリカを作ることは、耐障害性だけでなく、特定のノードに負荷が集中する「ホットシャーディング」に効果がある
  • ホットシャーディング時に、特定のノードのみレプリカを増やして負荷を分散させることができる

シャーディング関数をためしてみる

  • 一番シンプルな例はmod関数
  • 決定性: 同じ入力に対して常に同じ結果を返す
  • 均一性: 出力に対する出力の分散具合は均一
  • シャードのキーを決めるには、どのようなリクエストが来るのかの理解が必要
  • シャードへのルーティングについて次の2つが紹介されていた
    • アンバサダーパターンを使う方法、それぞれトレードオフの関係にある
      • ノード分のアンバサダーコンテナが必要になる、複雑度があがる
    • シャードルーターを使う方法がある
      • シンプルだが、負荷が増えることでスケールアウトが必要になる、レイテンシーがあがる
  • コンシステントハッシュでは、キー数/スケール後のキャッシュノード数のみキーの移動が発生する

スキャッタ・ギャザー

処理時間をスケールさせるためのデザインパターン 複数台で1つのリクエストを分割処理してマージすることによって処理を高速化させる

ルートによる分散とスキャッタ・ギャザー

  • ルートノードからのリクエストを処理して返すリーフノードと
  • 処理を分割してリーフにリクエストし・レスポンスをマージするルートノードの組み合わせ

リーフをシャーディングしたスキャッタギャザー

シャーディングされたデータを各リーフが自分の持つデータ範囲から検索を行いルートノードでマージする仕組み

適切なリーフの決め方

信頼性とスケーラビリティのためのスキャッタギャザーのスケール

ほとんどの処理を99%高速に返していても、残り1%で詰まっている場合は、遅い方に足を引っ張られる。 各リーフノードの対象外性をあげるために、過去の章でも登場したシャーディングのレプリカの作成は有効である。

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

  • etcd
  • 分散ロックでは、あらゆる処理時間がロックのTTLよりも短くなるようにすることが重要

Reactを書くときのパターンメモ

Render Props

  • propsに、RenderするReactElementを返す関数を渡す
  • メリット
    • 親が子のなかでレンダーする内容(孫)を動的に変更することができる
    • 親が子の中の孫のレンダー内容を指定しつつ、子と孫で値の共有ができるようになる
    • 参考例 mae.chab.in
    • オブジェクト指向デザインパターンでいうと、ストラテジーパターンに近い?
  • 下記のように子から何か(stateのようなもの)を受け取って孫を表示することで、孫の内容は動的にしつつも、子と孫で状態を共有することができる。
render() {
  retrun (
    <div>
      {this.props.render(this.state)}
    </div>
  )
}

分散システムデザインパターン読書メモ1

シングルノードパターン

1つのインスタンスの中でメインのコンテナと他のコンテナを組み合わせるときのデザインパターン

  • サイドカーパターン
    • メインのコンテナに機能を付与したり、改善したりするためのコンテナを組み合わせる
    • 例) gitリポジトリへのpushによって、コードをデプロイする機能を追加するコンテナ
      - gitリポジトリを定期的にpullして、アプリケーションのコンテナと共有したパスにgit pullしたコードを反映させる
      
  • アンバサダーパターン
    • メインのコンテナと他とのやり取りを仲介するようなコンテナを組み合わせる
    • 例) メインのコンテナに変更をいれることなく複数memcachedをシャーディングする
      • メインのコンテナはlocalhostmemcachedに接続する設定のまま、memcachedの代わりに接続先として追加したコンテナがいくつかある外部のmemcachedにシャーディングするようにする。例ではアンバサダーコンテナに twemproxy /を利用していた。
  • アダプターパターン
    • メインのコンテナのインターフェースを規格化した統一されたものにするためにコンテナを組み合わせる
    • 例) 監視用のメトリクスの出力(送信)(prometheus)
    • 例) ログデータの出力(fluentd)
      • メインのコンテナのログ出力形式は同じとは限らない。ログファイルへの出力だとしてもJSONや、LTSVなどフォーマットは異なる。それを一定のフォーマットに変換した上でログの集約や送信時のバッファリングの機能を追加することを可能にする

分散デザインパターンのマイクロサービスへの活用

パターンを使うことでシステムデザインがしやすくなる。同じパターンを開発者感で共有することでデバッグしやすくなる。 分散デザインパターンを使う・知ることでマイクロサービスにおけるデメリットを補うことができる。

  • メリット
    • 分割した個々のサービスごとにスケールの方法を選択することができる。
    • スコープが狭まることでピザ二枚分の小さな開発チームに保つことができることで、様々なオーバーヘッドを減らすことができる
  • デメリット
    • システムが疎結合になることで、問題発生時のデバッグの難易度があがる
    • システム間の連携や、適切なシステムの分割などシステムデザインの難易度があがる

マルチノードパターン

レプリカがロードバランスされたサービス

  • ステートレスなサービス
    • ステートレスな複数のサーバーとその前段にロードバランサーを配置する組み合わせ
    • 負荷の分散、耐障害性の向上
    • 可用性を高めるためには最低でも2つのレプリカが必要なる
    • 各レプリカにはロードバランサーに組み込めるかを判断するためのRedinessProbe(ヘルスチェックエンドポイント)を作っておく
  • セッションを保存するサービス
    • ステートレスなサービスに合わせて、特定のリクエストを同じマシンにルーティングを行う
    • 特定のマシンに送ることでキャッシュのヒット率を向上させることができる
    • 単なるIPアドレスによるハッシュではなくコンシステントハッシュを使うことでレプリカの増減時(scaleout,in)にマッピングの変更を最小限にすることができる。
    • コンシステントハッシュ?
  • アプリケーションレイヤでレプリカを扱うサービス
    • TCP/IPのレイヤーより高いレイヤー(httpレイヤー)でのロードバランスすることでより踏み込んだ制御が可能になる
  • キャッシュレイヤーの導入
    • アプリサーバーの前段にキャッシュレイヤーを設けてリクエスト処理の効率化を行う
    • varnishを使ったパターン
    • リクエストの流れ
    • 各レプリカのサイドカーとして配置することもできるが台数が増えるほどキャッシュ効率が悪くなる
    • レプリカレイヤーの前段に配置するのが自然
  • キャッシュレイヤーの拡張 -帯域制限とDOs攻撃に対する防御
    - varnishってhttpリバースプロキシだったのかキャッシュサーバーだと思ってた
    - throtleモジュールを使うことで、DoS攻撃を防御することができる
    
    • SSL終端
      • nginxを使う
      • SSL終端レイヤー => キャシュ => Appサーバー

sendagaya.rb #330 rake db:rollback時のソースコードリーディング

sendagaya.rb #330 では、 rails db:rollback のときのコードを読んだ。 もとは、 @sanfrecce_osaka がきになって調べてくれていた内容

こちらで用意するmigrationファイルのchange のなかのメソッド呼び出しを行うときに rake db:migrate では、ActiveRecord::ConnectionAdapters の各adapter(下記コードでいうと connection ) をレシーバーとして実行するのにたいして、rake db:rollback 時は、connectionrecorder に書き換えて changeメソッドのなかで実行されるメソッド呼び出しをrecorderに一旦記録させて すべて記録した後に、逆の動作に変更して replay で実行しているのが面白かった。

rake db:migrate 時のchange の実行

[823, 832] in /Users/jun-fukaya/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/activerecord-6.0.3.3/lib/active_record/migration.rb
   823:       @connection = conn
   824:       if respond_to?(:change)
   825:         if direction == :down
   826:           revert { change }
   827:         else
=> 828:           change
   829:         end
   830:       else
   831:         send(direction)
   832:       end
(byebug) puts @connection
#<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9df54c8098>
nil

rake db:rollback 時の change の内容を反転させるための connection の差し替え部分 recorder.replay(self) でrecorderで記録したchangeの処理内容逆にして実行している

[689, 708] in /Users/jun-fukaya/.rbenv/versions/2.7.0/lib/ruby/gems/2.7.0/gems/activerecord-6.0.3.3/lib/active_record/migration.rb
  689:     # This command can be nested.
   690:     def revert(*migration_classes)
   691:       run(*migration_classes.reverse, revert: true) unless migration_classes.empty?
   692:       if block_given?
   693:         if connection.respond_to? :revert
   694:           connection.revert { yield }
   695:         else
   696:           recorder = command_recorder
   697:           @connection = recorder
   698:           suppress_messages do
=> 699:             connection.revert { yield }
   700:           end
   701:           @connection = recorder.delegate
   702:           recorder.replay(self)
   703:         end
   704:       end
   705:     end
(byebug) puts connection
#<ActiveRecord::Migration::CommandRecorder:0x00007f82315ccfa0>
nil
(byebug) puts recorder
#<ActiveRecord::Migration::CommandRecorder:0x00007f82315ccfa0>
nil
(byebug) puts recorder.delegate
#<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f8231d1ce90>
nil
(byebug) puts @connection
#<ActiveRecord::Migration::CommandRecorder:0x00007f82315ccfa0>
nil

最近読んだ記事

初めての「技術ブログ」書き方のご紹介 – SORACOM公式ブログ

  • ブログの書くコツについて、何かテンプレートを作るのは良さそう
  • ブログ設定して燃え尽きるのは本末転倒というのはわかる
  • 1つのことにしぼる、 タイトルと対になる結論を書くとかも忘れがち

Amazon RDS/Auroraをクローンするシステムを作った話 - クックパッド開発者ブログ

  • クックパッドでは本番DBをマスク&クローンして各開発者がワンタッチで作れる仕組みを用意している
  • SRE系の仕事としてよく依頼される内容を効率化しようとしてるのは良い
  • 自動化大切
  • マスクする情報が完全な状態になっているかどうか不安が残るけどマスク列の設定ファイルが管理しやすくなってれば良さそう。

コンポジション vs 継承 – React

  • よくみるchildrenを内部でレンダーするやつ -<Parent><Child /><Parent />
  • あるコンポーネントに機能や表示を付け加えるのには便利そう
  • ある特化したコンポーネントを作るときに利用すると便利そう

Ruby M17N の設計と実装

gem pryを掘る(pry-rails編)

pry-railsで使えるコマンドについて調べてみる

pryのプラグインである、pry-railsを追加することで使えるコマンドについて調べてみた show-models あたりが一番便利そう

show-routes

rails routesを表示

show-models

modelの一覧、各modelの属性、各modelのリレーションを見ることができる

Category
  id: integer
  packing_list_id: integer
  name: string
  created_at: datetime
  updated_at: datetime
  description: text
  position: integer
  belongs_to :packing_list
  has_many :packing_items

show-model <モデル名>

show-modelsで表示される内容をmodel名を指定して見ることができる

show-middleware

rails middleware と同じでrack middlewareの一覧を表示してくれる

use Webpacker::DevServerProxy
use Rack::Sendfile
use ActionDispatch::Static
use ActionDispatch::Executor
use ActiveSupport::Cache::Strategy::LocalCache
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use ActionDispatch::RemoteIp
...

recognize-path url

urlで指定したパスを担当するcontroller Actionを表示してくれる。

find-routes

rails routesから keywordが含まれたルートを探してくれる

gem pryを掘る(watch)

Rubyデバッグに便利なことで有名なgem pry に調べてみる

watch

監視したい値を watch <値> のようにして登録すると 値に更新があったときに教えてくれる

[5] pry(main)> a = 1
=> 1
[6] pry(main)> watch a
Watching a
watch: a => 1
[7] pry(main)> a
=> 1
[8] pry(main)> a += 1
watch: a => 2
=> 2

何かを実行するたびに監視対象を実行して、結果を調べてくれているらしい 試しにメソッドの結果を監視したら、毎回監視対象のメソッドが実行されてた

[2] pry(main)> def count
[2] pry(main)*   puts 'count invoked'
[2] pry(main)*   1
[2] pry(main)* end
=> :count
[3] pry(main)> watch count
Watching count
count invoked
watch: count => 1
[4] pry(main)> 1 + 1
count invoked
=> 2
[5] pry(main)> count
count invoked
count invoked
=> 1

裏で更新されているようなものについて監視できるんだろうか? 気になるので、Threadを起動して並列で更新してみたが更新を教えてくれなかった。 どうもpry上でなにか実行したときに値を確認して変わってたら教えてくれるだけらしい。