メッセージブローカー『NATS』のパフォーマンスを計測する

はじめまして。オプティムのエンジニアのなかのです。私は普段はCloud IoT OSの企画などを担当しています。開発やインフラ設計などをすることもありますが、こちらはおまけですね。

このテックブログで私が取り扱うテーマは、インフラ系の、ほどほどにニッチなところを攻めようかと思っています。今回はメッセージブローカーの1つであるNATSの、ベンチマークに関する記事を書いてみました。

メッセージングとは

メッセージングとは、複数のサービス(アプリケーション)間で情報の交換を行うためのスキームです。交換される情報をメッセージと呼びます。メッセージングのアーキテクチャは複数ありますが、一般的にはPub/Sub型のメッセージングモデルが利用されます。このモデルを採用するプロダクトは数多く、Azure EventHubsやGoogle Cloud Pub/Subのようなマネージドサービスから、Apache Kafka、ActiveMQ、RabbitMQなどのOSSもあります。

Pub/Sub型は略称で、正式にはPublish/Subscribe型(発行-購読型)といいます。このPub/Sub型メッセージングモデルの中でもアーキテクチャは様々存在し、下記図はそのうちのメッセージブローカーが存在するモデルの構成イメージとなります。矢印はメッセージの流れる向きを表しており、リクエストの向きではないことに注意してください。

メッセージを発行する側をパブリッシャー(Publisher)、メッセージを受信する側をサブスクライバー(Subscriber)と呼びます。メッセージブローカー(Message Broker)とはパブリッシャーとサブスクライバーの間を受け持つサービスで、メッセージの中継を行います。パブリッシャーが発行したメッセージは、メッセージブローカーを経由して、サブスクライバーにメッセージが伝送されます。

サブスクライバーはメッセージブローカーに流れるすべてのメッセージを受信するのではなく、サブスクリプションと言う情報を登録し、受信したいメッセージを指定します。この制御を購読(サブスクライブ)といいます。したがって、パブリッシャーはどこにメッセージを流すか、サブスクライバーはどこのメッセージを受信するか、を指定できる要素が必要です。チャネル、トピック、サブジェクトなど、プロダクトにより名称は様々ですが、メッセージを流すための論理空間を指定する要素をメッセージブローカーは有しています。

また、メッセージの交換において、メッセージの伝達に関する信頼性というものがあります。メッセージが相手に確実に届く必要があるのかどうかは、ユースケースごとに様々です。高頻度で送られるゆえ、多少の欠損を認めるようなケースも少なくありません。このようなユースケースの違いに対応する要素として、メッセージ伝達の確実性を定める要素があります。この信頼性はしばし、Quality of Service(QoS)と呼ばれます。

QoSは多くのケースで、以下の3つのレベルで語られます。

  • 最大1回送る(At Most Once)

    欠損を許容するケースです。

  • 少なくとも1回送る(At Least Once)

    これは欠損を認めないケースで、相手から受領確認がない場合メッセージを再送します。したがって、同じメッセージを複数回受信する、つまりメッセージが重複するケースがありえます。

  • 確実に1回だけ送る (Exactly Once)

    これは欠損を認めず、かつメッセージの重複も認めないケースです。

これらのQoSはパブリッシャー側、サブスクライバー側それぞれで定義します。また、サブスクライバー側では、サブスクリプションごとにQoSを指定できるケースもあります。メッセージングを利用するシーンでは、そのユースケース、その要求仕様に最適なQoSを選択して利用することになります。

NATSとは

nats.io

github.com

今回取り扱うNATSは、軽量で高性能を謳うメッセージブローカーです。今はKubernetesなどのプロジェクトをホストするCloud Native Computing Foundation(CNCF)にincubating projectとしてホストされており、クラウドネイティブなメッセージングの基盤として有望視されています。

NATSは他のメッセージブローカーとは違い、At Most Onceなデリバリに特化しています。At Most Onceに特化したため、メッセージの再送処理などが不要となり、そのためメッセージを貯め込む必要がなくなり、結果として軽量で高性能となっています。

NATSをクラスタリングする場合、フルメッシュクラスタが推奨されています。したがって、すべてのメッセージが、クラスタを構成するすべてのNATSサーバに流れる構成となります。

NATSにAt Least Onceなデリバリをもたせる手段として、NATS Streamingという別立てのサービスがあります。NATS StreamingはNATSが提供するメッセージング基盤の上で、At Least Onceなメッセージング基盤を提供します。

github.com

NATSのパフォーマンスを測る

パフォーマンスの計測方法

NATSは公式でベンチマークツールが提供されています。またその利用方法も下記ページで言及されており、手順は明瞭です。ドキュメントに従い、nats-benchツールでパフォーマンス検証を行います。

nats.io

パフォーマンスに影響する要素

NATSの構成上、ベンチマーク結果に影響しうる要素は以下が考えられます。

  • クラスタを構成するNATSサーバの台数
  • クラスタを構成するVMのマシンスペック
  • クラスタのネットワーク帯域
  • パブリッシャー数
  • サブスクライバー数
  • パブリッシャーが稼働するVMのマシンスペック
  • サブスクライバーが稼働するVMのマシンスペック
  • 発行されるメッセージの数
  • 発行されるメッセージのサイズ

nats-benchツールでは、パブリッシャー数、サブスクライバー数、メッセージサイズなど、クライアント側で変えられる要素を自由に設定できるようになっています。NATSクラスタ側は容易には変えられませんので、Infrastructure as Code的に、必要に応じてクラスタを再構築しやすくしておく必要があります。

AWSやAzureのようなクラウドでベンチマークを取る場合、これらのうちネットワーク帯域だけは採用するマシンスペックの性能に比例して容量が変わるように定められているため自由に設定することができません。そのため、CPUやメモリの点では処理能力に余裕があれども、ネットワーク帯域の性能が足りず、パフォーマンスが頭打ちになるケースが有りうることを想定しておく必要があります。

NATSのクラスタの監視

nats-benchはNATSクラスタと実際にやり取りした実績のみを出力します。そのため、nats-benchだけではVMのCPU使用率、メモリ使用率、ネットワークI/Oなどとの関連までは把握できません。パフォーマンスの検証する上ではこれらの推移も観測できるようにしておく必要があります。

具体的にはNATSクラスタが可動するVMのメトリクスを監視し、NATSクラスタのパフォーマンス検証の結果と突合させます。例えば、Datadogなどのモニタリングサービスを利用して実現します。

パフォーマンス検証環境

今回は、パフォーマンス検証をMicrosoft Azure上で行います。

上記の背景を考慮し、パフォーマンス計測環境はTerraformのAzure Providerを用いてVM等を構築して実施しました。以下は、NATSサーバが3台のクラスタを想定した場合の構成図です。

NATSのクラスタは、Azure Virtual Machine Scale Sets(VMSS)ではなく、Terraform のcountを利用してVMを複数作成するアプローチをとっています。

nats-benchはパブリッシャー数、サブスクライバー数を指定できますが、パブリッシャーごと、サブスクライバーごとにコネクションを張ります。そのため、NATSクラスタに対するロードバランサを用意し、nats-benchはロードバランサ経由で接続するようにし、コネクションが1つのサーバに集中しないようにしています。

パフォーマンス検証結果

パフォーマンスに影響しうる要素が多く、今回の内容ではすべてを取り上げることが難しいため、一例を掲載します。

以下は、

  • 3台構成のNATSクラスタ
  • VMサイズは Standard_DS1_v2
  • パブリッシャーは10台
  • サブスクライバーは10台
  • メッセージサイズは約100KB
  • 発行されるメッセージ数は500
  • nats-benchを動かすVMのサイズは Standard_DS5_v2

の条件のときのパフォーマンス測定結果となります。

$ nats-bench -s nats://10.0.2.5:4222 -np 10 -ns 10 -n 500 -ms 100000 foo
Starting benchmark [msgs=500, msgsize=100000, pubs=10, subs=10]
NATS Pub/Sub stats: 2,020 msgs/sec ~ 192.65 MB/sec
 Pub stats: 399 msgs/sec ~ 38.08 MB/sec
  [1] 111 msgs/sec ~ 10.62 MB/sec (50 msgs)
  [2] 91 msgs/sec ~ 8.72 MB/sec (50 msgs)
  [3] 80 msgs/sec ~ 7.66 MB/sec (50 msgs)
  [4] 80 msgs/sec ~ 7.64 MB/sec (50 msgs)
  [5] 76 msgs/sec ~ 7.31 MB/sec (50 msgs)
  [6] 74 msgs/sec ~ 7.08 MB/sec (50 msgs)
  [7] 74 msgs/sec ~ 7.08 MB/sec (50 msgs)
  [8] 71 msgs/sec ~ 6.86 MB/sec (50 msgs)
  [9] 64 msgs/sec ~ 6.18 MB/sec (50 msgs)
  [10] 39 msgs/sec ~ 3.81 MB/sec (50 msgs)
  min 39 | avg 76 | max 111 | stddev 17 msgs
 Sub stats: 1,844 msgs/sec ~ 175.88 MB/sec
  [1] 286 msgs/sec ~ 27.36 MB/sec (500 msgs)
  [2] 240 msgs/sec ~ 22.93 MB/sec (500 msgs)
  [3] 224 msgs/sec ~ 21.44 MB/sec (500 msgs)
  [4] 224 msgs/sec ~ 21.42 MB/sec (500 msgs)
  [5] 222 msgs/sec ~ 21.21 MB/sec (500 msgs)
  [6] 222 msgs/sec ~ 21.17 MB/sec (500 msgs)
  [7] 218 msgs/sec ~ 20.80 MB/sec (500 msgs)
  [8] 191 msgs/sec ~ 18.27 MB/sec (500 msgs)
  [9] 189 msgs/sec ~ 18.10 MB/sec (500 msgs)
  [10] 184 msgs/sec ~ 17.59 MB/sec (500 msgs)
  min 184 | avg 220 | max 286 | stddev 28 msgs

この例では、NATSクライアント(nats-bench)とNATSクラスタ間はおよそ秒間2000メッセージ、スループットにして200MB/sのメッセージをPub/Subできています。

ここでさらに、メッセージサイズを絞り、発行されるメッセージ数を増やしてみます。

$ nats-bench -s nats://10.0.2.5:4222 -np 10 -ns 10 -n 10000000 -ms 16 foo
Starting benchmark [msgs=10000000, msgsize=16, pubs=10, subs=10]
NATS Pub/Sub stats: 5,039,925 msgs/sec ~ 76.90 MB/sec
 Pub stats: 458,239 msgs/sec ~ 6.99 MB/sec
  [1] 65,831 msgs/sec ~ 1.00 MB/sec (1000000 msgs)
  [2] 60,897 msgs/sec ~ 951.53 KB/sec (1000000 msgs)
  [3] 60,384 msgs/sec ~ 943.51 KB/sec (1000000 msgs)
  [4] 56,518 msgs/sec ~ 883.10 KB/sec (1000000 msgs)
  [5] 55,987 msgs/sec ~ 874.80 KB/sec (1000000 msgs)
  [6] 53,472 msgs/sec ~ 835.51 KB/sec (1000000 msgs)
  [7] 51,465 msgs/sec ~ 804.15 KB/sec (1000000 msgs)
  [8] 46,619 msgs/sec ~ 728.43 KB/sec (1000000 msgs)
  [9] 46,259 msgs/sec ~ 722.81 KB/sec (1000000 msgs)
  [10] 45,824 msgs/sec ~ 716.01 KB/sec (1000000 msgs)
  min 45,824 | avg 54,325 | max 65,831 | stddev 6,522 msgs
 Sub stats: 4,582,114 msgs/sec ~ 69.92 MB/sec
  [1] 458,266 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [2] 458,275 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [3] 458,255 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [4] 458,216 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [5] 458,251 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [6] 458,270 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [7] 458,228 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [8] 458,269 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [9] 458,216 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  [10] 458,268 msgs/sec ~ 6.99 MB/sec (10000000 msgs)
  min 458,216 | avg 458,251 | max 458,275 | stddev 21 msgs

1つあたりのメッセージサイズが小さくなったことから、理論的には1秒あたりにさばけるメッセージ数が増加することが予想されましたが、結果として、NATSクライアント(nats-bench)とNATSクラスタ間はおよそ秒間500万メッセージをPub/Subできることが確認できました。

おわりに

今回は、パフォーマンスに影響しうるすべての要素を網羅したパターンでのベンチマークを取ることはしませんでしたので、興味のある方はぜひチャレンジしてみてください。

また今回は、NATS以外の、Apache KafkaやActive MQ、EMQ、NSQなどの他のメッセージブローカーとの比較も行っていません。Google検索などで探すと多少古くはありますがいくつかのパフォーマンス比較の記事は存在するので、興味があれば見てみてください。ただ最新のデータとは言い難いため、興味のある方は是非ご自身の手で検証してみてください。

オプティムでは、実際にプロダクトに利用するための技術の深掘りだけでなく、今後の見込みのある技術やプロダクト、新たな可能性を持った技術やプロダクトなどを広く調査し、実際に検証することも積極的に行っています。自分もやってみたい、興味のある、という方は、是非こちらをご覧ください。

www.optim.co.jp