CassandraにはnodetoolユーティリティというJMXを介してCassandraクラスタを管理するツールが付属しています。
nodetoolで取得できる情報をNode.js上で利用したいという事情があり、その方法を探していたところnode-jmx
というJMXと通信するためのパッケージを発見しました。
node-jmx
を利用してCassandraの情報を取得する方法についてわかったことをまとめます。
node-jmx について
インストール
node-jmx
の利用にはJDKが必要です。node-jmx
は内部でnode-java
を利用しているようですので、install時のトラブルはnode-java
のInstallationを参照したほうが良いでしょう。
node-gypを利用しているので、python2.7とmake, g++も予め用意しておく必要があります。
準備が整っていれば以下のコマンドでnode-jmx
が利用可能になります。
npm instal jmx
使い方
基本は公式ドキュメント通りですが、Clientから返ってくる値はJavaオブジェクトになっているようですので実際の値を取得する際には前処理が必要になります。
以下にHeapMemoryUsage
を取得するコードを載せました。HeapMemoryUsage
はjavax.management.openmbean.CompositeData
インタフェースのオブジェクトですので、get(String key)
で取得したい項目の数値を取得できます。ただし、node-java
を介した操作ですのでメソッド名はSyncを付けたgetSync(String key)
になります。(ちなみにnode-java
は非同期メソッドも提供しているようです。)取得した値はjava.lang.Number
インタフェースのオブジェクトですので、longValue()
等で数値が取得できます。(こちらはSyncを付けないようです。)
const jmx = require('jmx');
const client = jmx.createClient({
host: 'localhost',
port: 7199,
});
client.on('connect', () => {
client.getAttribute('java.lang:type=Memory', 'HeapMemoryUsage', data => {
const used = data.getSync('used');
console.log(used.longValue);
});
});
client.connect();
nodetool status をNode.js上で実装する
本題のCassandraクラスタの情報取得の方法についてまとめます。
手始めにnodetool status
が提供している機能をnode-jmx
を利用して実装してみたいと思います。
nodetool statusとは
クラスタに参加している各ノードの情報を収集、表示するコマンドです。( 参考 )
特に、ノードが機能しているかどうかを示すStatus (Up/Down) の項目はnodetool status
を実行する際に最も関心のある情報の一つではないかと思われます。
本家 nodetoolのstatusコマンドの実装
GitHubのものですが、本家Apache Cassandraのnodetool statusに関する主なソースコードを纏めておきます。
- org/apache/cassandra/tools/nodetool/Status.java
- org/apache/cassandra/tools/NodeProbe.java
- org/apache/cassandra/service/StorageService.java
- org/apache/cassandra/locator/EndpointSnitchInfo.java
(だいたいこの辺りだったと思いますが、過不足間違い等あったらすみません)
大まかな内容としては、nodetool status
で収集している情報はStorageServiceとEndpointSnitchInfoが管理しており、それぞれMBeanとして公開されています。
それぞれ以下のような情報を管理しています(ごく一部だけ載せてあります)
- StorageService:
- クラスタに参加しているノードのIPアドレスとHostIDのMap
- 健全(Up)なノード、疎通不可(Down)になっているノードのリスト
- クラスタに参加しようとしている or 抜けようとしているノードのリスト
- トークンのリストと担当ノードのIPアドレスのMap
など
- EndpointSnitchInfo:
- ノードが所属しているデータセンタ名
- ノードが所属しているラック名
nodetool status
ではこれらの情報を適当なフォーマットで表示してくれます。
余談ですが本家nodetoolの実装は少々雑と思わざるを得ない部分もあります。
例えばvnodeやトークンレンジの数が増えるほどresolveIp
オプション適用時のnodetool status
の応答時間が悪化する問題があり、その原因はDNSによる名前解決の際にトークンレンジ * vnode数
だけのループを行う非効率な処理が行われているためのようです。(実際ほんの数ノードのクラスタでもnodetool status
はめちゃくちゃ遅い)
今回のNode.jsでの実装はもうちょっと効率の良い実装にできたら良いなと考えています。
( 参考: ASF JIRA: improvement of nodetool status -r ) ← 忘れ去られているように見えますが果たして反映されるのでしょうか…
Node.jsでnodetool statusを実装してみた
いきなりですが実装したものをnpm packageとして作成し、公開してみました。
npmjs: cassandra-nodetool
GitHub: cassandra-nodetool
エッセンス
node-jmx
でStorageServiceやEndpointSnitchInfoを取得する際のMBean名は以下のとおりです。
- StorageService:
'org.apache.cassandra.db:type=StorageService'
- EndpointSnitchInfo:
'org.apache.cassandra.db:type=EndpointSnitchInfo'
ですので、例えばクラスタに参加しているノードのリストを取得するには以下のようなコードになります。
クラスタに参加しているノードのリストはStorageService
のLiveNodes
属性にありますので、これを取得しています。
// client.on('connect') イベントを受け取ったあとで
client.getAttribute('org.apache.cassandra.db:type=StorageService', 'LiveNodes', javaLiveNodes => {
const liveNodes = javaLiveNodes.toArraySync();
console.log(liveNodes);
});
Unreachableノード(疎通不可ノード)のリストを取得するにはUnreachableNodes
属性を取得すればOKです。
上でも少し触れましたがStorageServiceには他にも様々な情報を管理しており、属性の一覧を知りたい際にはソースコードを参照するかjconsoleで接続して直接探してみるのが良いかと思われます。
EndpointSnitchInfoからデータセンター名を取得する例も載せておきます。
EndpointSnitchInfoにはRack
やDatacenter
という属性があり、そちらでもデータセンター名やラック名を取得することが可能です。
ただしこちらから取得できるのは、JMXで接続したノードのデータセンター名やラック名のみです。
クラスタに参加している他のノードのラック情報等を取得したい際はgetRack
, getDatacenter
メソッドを利用します。
const endpoint = '127.0.0.1';
client.invoke('org.apache.cassandra.db:type=EndpointSnitchInfo', 'getDatacenter', [endpoint], javaDCName => {
console.log(javaDCName.toString());
});
最後に、StorageServiceのEndpointToHostId
属性などのようなjava.util.Map
オブジェクトをJavaScript Objectとして扱えるようにパースする処理が度々必要になりましたので、そのイディオムのようなものも載せておきたいと思います。(もっと良い方法があったら教えてください!)
const convertedObject = {};
const entrySets = javaMap.entrySetSync().toArraySync();
for(const entry of entrySets) {
convertedObject[entry.getKeySync()] = entry.getValueSync();
}
所感
node-jmx
とnode-java
がこの先もメンテナンスされていくのかは若干不安はありますが、これらのパッケージの利用でNode.jsからJMXを介した処理がとても簡単に実装できました。
当初child_processで本家nodetoolをシェルコマンドとして実行して結果をパースする方法を考えておりましたが、JMXを介して直接情報を取得する方法が分かりましたので、Cassandraの運用ツール開発にJava以外の選択肢が作れそうです。
本家nodetoolにはJSON/YAMLフォーマット出力に対応していないコマンドがあったり、上でも触れたとおりパフォーマンスや信頼性に満足できないところがあったりしたので、しばらくはNode.js向けの代替モジュールとして前述のパッケージ開発を続けていきたいと思います。
参考
nodetoolユーティリティ
nodetool status
GitHub: Apache Cassandra
ASF JIRA Cassandra: improvement of nodetool status -r
GitHub: node-jmx
GitHub: node-java
GitHub: cassandra-nodetool