エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

Amazon SQSに置き換えてパフォーマンスとスケーラビリティを得た話

エムスリー Advent Calendar 2019 5日目の記事です。

やがて君になる、完結しましたね… エンジニアリンググループの山口 (@no_clock) です。

今日は、クラウド電子カルテ「エムスリーデジカル」で、SQSを使ってパフォーマンスとスケーラビリティを得た話です。

前提: 利用施設数の増加と、あまりスケールしないシステム

2015年10月にスタートした本サービスは、2019年8月に利用施設数1,100件を突破しました。2017年7月時点で120件でしたから、2年で約10倍の増加です。

f:id:t-yamag:20191203155246p:plain

AWS上で稼働しており、ECSでのオートスケールも取り入れています。中心となるRuby on Railsのアプリケーションなどは、日中大きくスケールアウトしています。

ただ、中心部以外は、スケールしない部分も多く残っています。利用施設数の増加に伴って、それらをスケールさせる必要が生じてきました。嬉しい悲鳴です。

ここ半年、これをスケールするよう大小様々に改善しています。今回は、直近実施した「診療データクローラの廃止」について取り上げます。

課題: 診療データクローラ

診療データクローラとは何か

保険証を提示して受けられる「保険診療」の世界には、「xxx管理料(n点)は、 特定の条件で 月1回 だけ請求できる」といった規定が存在します(診療報酬制度について - 厚生労働省)。

ここで、その「xxx管理料(n点)」が請求できるかどうか、電子カルテへ入力された診療データに応じてリアルタイムにチェックする仕組みを考えてみます。

入力されたその日の診療データはもちろんですが、 月1回 という規定のため今月分の診療データも合わせてチェックが必要になります。しかし、毎回1ヶ月分をスキャンしていると速度が出せません。

そこで、速度を出すために、必要な情報だけをサマリとして集約する「診療データクローラ」を用意します。「今月分の診療データ」にそのサマリデータを用いることで、チェックの高速化を実現します。

f:id:t-yamag:20191203212218p:plain
クローラ付近のざっくり構成図

クローラの遅延

この「診療データクローラ」は、単独で動作していました。最後にスキャンした診療データのIDを記憶しておいて、次回クロール時はそれ以降のデータをDBからスキャンする、というものです(データが削除されるケースもあるが今回の説明では割愛)。

f:id:t-yamag:20191203212238p:plain
クローラの動作イメージ

このスキャン処理が、4年の歳月を経てじわじわと遅くなっていました。主な原因は2つです。

  1. 1施設ずつシーケンシャルに処理をしていた。利用施設数に比例して遅くなった。
  2. インデックスの貼り方が適切でなかったため、スキャン範囲が広くなるケースがあった*1

当初は10秒で完了していたスキャン処理が、末期には100倍近い900秒(15分)掛かっていました。 これにより、15分という短い時間ではあるものの、サマリデータ(今月分の診療データ)が古いままチェックしてしまう、という問題が生じていました。

クローラを続けるか、やめるか

この問題を解決すべく、下記2つの案を考えました。

  1. クローラの改良。パラレルに動かす、インデックスを貼り直すなどする。
    • 利点: 工数が小さい、リスクが小さい、他コンポーネントとの依存関係が少なく保守性が高い
  2. キュー+ワーカーへの置換。診療データを登録するアプリ側でエンキューさせ、そのキューを処理する方法とする。
    • 利点: クロールしない(DB負荷軽減)、スケーラビリティが高い

チームメンバーと議論しつつ、次の理由でキュー+ワーカー案を採用しました。

  • 今後も利用施設数の増加が見込めるため、スケールのしやすさは重要
  • クローラ改良案はスケールさせていくとDBボトルネックで辛くなりそう

また、ワーカーについては常時起動するプロセスとして用意することにしました(Lambdaとするアイディアもありましたが、アプリもインフラも考慮すべき点が多く、実戦投入まで長くなる懸念があったため常時起動プロセスとしました)。

解決: 診療データ『キュー』

ここまで来ると話は簡単で、Amazon SQSを使ってキューを捌くワーカーを作るだけです。「じゃあこれで、アプリもインフラもまるっとやっちゃいます」とチームメンバーに告げて、黙々と実装を進めました。

f:id:t-yamag:20191203212309p:plain
新しい構成のざっくり図

なお、実装にあたっては公式のAmazon SQS 開発者ガイドを参照しつつ、下記の設計で行いました。

  • アプリケーションでの冪等性の担保(診療データ削除の考慮)
  • メッセージグループIDの指定と、FIFOキューの使用(同一患者のサマリデータを複数ワーカーで更新させない)
  • デッドレターキューの使用と、CloudWatch Alarmによるアラート
  • VPCエンドポイントを用いたアクセス制御
  • TerraformによるInfrastructure as Code

結果: パフォーマンスとスケーラビリティ

つい2週間ほど前に本番環境に適用しましたが、順調に稼働しています。

処理実績は従来と同じですが(※診療データの作成ペースは変わらないため)、診療データの登録からサマリデータが出来上がるまでのタイムラグが劇的に改善できました。

  処理実績 診療データ登録からサマリ作成までの時間
旧 クローラ 300件/分 平均760秒 (中央値761秒、90%tile 1,356秒)
新 キュー 300件/分 平均3.11秒 (中央値3.68秒、90%tile 7.14秒)

また、ワーカーノード数を増減させることで簡単にスケール出来るようにもなりました。Amazon SQSのFIFOキューは デフォルトで 3,000件/秒 (180,000件/分) 捌けますから、DBが音を上げないうちは大丈夫そうです。

おまけ: We are hiring!

エムスリーは、合理的でありさえすれば「良さそうなのでやっちゃいます」の一言でガンガン進められる自由度の高さが魅力の1つです。

技術力を活かして、社会に役立つプロダクトを一緒に作っていきませんか? カジュアル面談でより詳しくお話ししましょう!

jobs.m3.com

*1: SELECT * FROM TREATMENT(診療データ) WHERE INSTITUTION_ID(施設ID) == 3 AND ID > 100 でインデックスは ID にしか貼っていない