microservices間でデータ変更をReactiveに伝搬させる

microservices構成なものを運用していると、更新頻度が少ないデータなのに別のmicroservicesに都度リクエストをするということがよくあると思う。

例えば、Userのドメインを扱うサービスがいた場合、他のサービスはUserのデータを取得するためにUserサービスをHTTP(S)ないし、何らかのRPC的手段によって取得することになる。以下の図をイメージしてもらえるとよい。

UserサービスがUserドメインを担当する層なのでこのようになる。

データの更新頻度が少ないという現実

実際に運用してみると取得対象のデータ更新頻度が少ないというケースがあるので、Userサービスへのリクエストを減らしてキャッシュを活用したくなるところである。microservices間の通信を減らすこともできるし、対象のサービスにとってはデータストアへのクエリを減らすことにも繋がる。

というわけで以下のようにUserデータに依存したサービス毎にキャッシュを持たせる。

キャッシュストアについては、Redisやmemcached等を配置するのも良いし、サービスにインメモリに持たせるでも良い。コスト感を考えると後者の方が安上がりだが、サービスを跨いでキャッシュを共有しても良いという方針にするならば前者も選択肢に入ってくる。

キャッシュデータに関しては適切にTTLを設定し、expireした場合には対象にサービスに再度リクエストをしてキャッシュを更新するといった制御になる。

キャッシュを持つ分大きくリクエストを減らすことができるが、この方式の弱点はTTLの長さによってはキャッシュが長く滞留してしまい、キャッシュの更新に時間がかかってしまうということにある。

データ変更をトリガーに、依存サービスに変更データをばらまく

できることならばデータの変更をトリガーに、複数のmicroservicesに散らばっているキャッシュを更新したい。そこで、Server Push型ミドルウェアであるPlasmaを用いる。

openfresh/plasma

Plasmaの特性については、以前にエントリを参照してほしい。

gRPCとServer-Sent Eventsでサーバプッシュできるplasmaを公開しました - tehepero note(・ω<)

PlasmaはPublisherが複数のSubscriberに対して同じイベントデータをばらまくといった用途に向いている。今回の例でいうと、User ServiceがPublisherであり依存サービスがSubscriberとなる。この特性を利用して、Userデータの変更タイミングで依存サービスに対して新しいUserデータを伝搬させ、キャッシュを更新させる。

UserサービスはDBと共に、Plasma用RedisにデータをPublishするようにする。各microservicesがUserデータ変更イベントをSubscribeしていれば、データ変更後程なくして更新データをPlasma経由で受け取ることが出来る。受け取り次第キャッシュを更新すれば、キャッシュの良さを活かしつつも”ほぼ即時”に依存サービスへのリクエスト無しでキャッシュを更新することが可能。microservicesを跨いだ、Reactiveなデータ伝搬といった表現ができるかなと思っている。

このようなデータ伝搬の仕組みは、Plasmaのようなものを使わずとも用意せずにも可能ではある。例えば、各microservicesがキャッシュ更新用のエンドポイントを用意しておいて、Publisherがそこにリクエストをするといった形式だが、この場合は自分に依存しているサービスがどこにあるかを把握しておかなくてはならないのでService Discoveryが必要になってしまう。また、相互参照も避けた方がシンプル。

Plasmaを使わずにRedisのPubSubを使っても同じことが実現できる(Plasma-Redis間がそもそもPubSub)が、Plasmaの利点としてはgRPCやSSEでSubscribeが可能な点なので、RedisへのSubscirbe処理を色んなところで書くよりも、Plasmaのprotoから生成するgRPCのインタフェースに頼ったほうがいいし、SSEを利用すればWebフロントエンドからの利用も可能になるので何かと良いと考えている。

まとめ

PubSub型の特性を活かしたMicroservices間のデータ伝搬のパターンはWeb上文献でもちらほら散見される。microservicesは疎結合や障害の局所化を得やすい反面、どうしても通信は増えがちになるのでパフォーマンスをケアした仕組みというのがこれからもっと重要になるなーという感想。