---
title: "Zenohとは？Pub/Sub・Queryの実装とzenohdの使い方・MQTT/DDSとの違い"
url: "https://www.issoh.co.jp/tech/details/6210/"
published: 2025-04-09
updated: 2026-10-13
categories: ["プロトコル"]
publisher: "株式会社一創"
---

# Zenohとは？Pub/Sub・Queryの実装とzenohdの使い方・MQTT/DDSとの違い

Zenoh（ゼノ）は、Pub/Subによる配信と、保存された値や計算結果への問い合わせ（Query）を1つのプロトコルで扱う、Eclipse Foundationの分散通信ミドルウェアです。ブローカーなしで端末同士が直接つながり、通信の中継やストレージ・RESTプラグインの利用にはルーター（zenohd）を配置できます。相互に到達可能な接続先を明示すれば、別サブネットのpeer同士も直接接続できます。ここでは現行版1.10.1を実際に動かし、3つの動作モード、Pub/SubとQueryの最小コード、zenohdのRESTとストレージで値を出し入れする手順、MQTTやDDSとの違い、ROS 2での使い方までを実装目線で整理します。Pub/Sub・Query・RESTの検証に用いた環境は、macOS 26.6.2（x86\_64）・Python 3.9.6・eclipse-zenoh 1.10.1・zenohd 1.10.1で実行したものです。

## まとめ：Zenohを採用する前に押さえる要点

Zenohの現行版は[1.10.1（2026年9月7日公開）](https://github.com/eclipse-zenoh/zenoh/releases/tag/1.10.1)で、本体・Zenoh-Pico・Pythonなどの各言語バインディング・プラグイン・ストレージバックエンドが同じ版番号で並行リリースされています。1.10.1は1.10.0（2026年8月14日）の修正版で、ループバックアドレスでのゴシップ探索の不具合や、RESTプラグインのContent-Typeの扱いが直っています。ライセンスはApache-2.0とEPL-2.0のデュアルで、実装言語はRustです。

設計上の要点は3つです。第一に、アプリケーションは既定でpeerモードとして起動し、同一ネットワーク内の相手を自動探索して直接つながります。zenohdは必須ではなく、多数の拠点や機器の通信を中継・集約する場合や、ストレージやREST APIを動かす場合に置きます。別サブネットでも、到達できる相手を`connect`で指定すればpeer同士で直接つながります。第二に、Pub/Subに加えてQueryが同じキー空間の上に載るため、「流れてくる値を受け取る」処理と「今の値を取りに行く」処理を別のミドルウェアに分けずに済みます。第三に、ROS 2ではrmw\_zenoh\_cppが公式のRMW実装として提供されており、Kilted KaijuではTier 1に位置付けられています。

注意すべきは既定値です。マルチキャスト探索はサブネットを越えず、共有メモリ転送は送受信の双方で有効でないと使われず、PythonからエンコーディングなしでputしたデータはRESTで読むとBase64で返ってきます。以降でその具体を、設定ファイルの実値と実行結果に沿って説明します。

## Zenohの位置づけとキー式の考え方

ZenohはEclipse Foundationのプロジェクトとして開発されている通信ミドルウェアで、データの配信・保存・問い合わせを同じキー空間の上で扱う点に特徴があります。データはキーと値の組で表され、キーはUnixのファイルパスに似た階層表現になります。たとえば`myhome/kitchen/temp`のように書き、購読側は`myhome/kitchen/*`（1階層）や`myhome/kitchen/**`（0階層を含む複数階層）のワイルドカードでまとめて受け取れます。このキー式は購読だけでなく、問い合わせ（get）やストレージの保存範囲の指定にもそのまま使います。

クライアントライブラリはRustが本家で、その上に主要言語のバインディングが載る構成です。Python・C・C++・Java・Kotlin・TypeScript・Go・C#、および低リソース機器向けのZenoh-Picoが公式リポジトリとして公開されています。実装や機能追加はRust本体から進むため、最新機能を追う用途ではRust、検証やグルー処理ではPythonという使い分けになります。

## peer・client・routerの3モードとzenohdの役割

Zenohのプロセスは3つのモードのいずれかで動きます。どれを選ぶかで、探索の仕方と維持するセッション数が変わります。

| モード      | 接続の持ち方       | 探索                 | 主な用途             |
| -------- | ------------ | ------------------ | ---------------- |
| peer（既定） | 相手ごとに直接セッション | マルチキャスト＋ゴシップ       | 同一NW内のアプリ間通信     |
| client   | 常に1セッションのみ   | マルチキャスト＋connect指定  | 制約デバイス／セッション数の抑制 |
| router   | 他ノードの通信を中継   | connect指定でルーター間も連結 | サブネット越え／メッシュ構成   |

[1.10.1の既定設定ファイル（DEFAULT\_CONFIG.json5）](https://github.com/eclipse-zenoh/zenoh/blob/1.10.1/DEFAULT%5FCONFIG.json5)では`mode: "peer"`が既定で、peerモードのアプリはマルチキャストグループ224.0.0.224のUDPポート7446へスカウトメッセージを送り、見つけた相手へ自動接続します。待ち受け側のエンドポイントはrouterが`tcp/[::]:7447`、peerが`tcp/[::]:0`（空きポート）です。つまりZenohは「ブローカー必須」ではなく、ルーターを置かない構成が出発点になっています。

ルーターのzenohdは、macOSならHomebrew、Debian系ならEclipseのaptリポジトリ、またはGitHub Releasesの単体zip（zenohd本体とRESTプラグイン・ストレージマネージャーの3ファイル入り）で導入できます。設定はJSON5またはYAMLで、`-c`で渡します。

```
$ brew tap eclipse-zenoh/homebrew-zenoh
$ brew install zenoh
$ zenohd -c zenoh-myhome.json5
```

検証ではGitHub Releasesの`zenoh-1.10.1-x86_64-apple-darwin-standalone.zip`を展開して使い、`zenohd --version`は`zenohd v1.10.1 built with rustc 1.97.1`を返しました。プラグインは`zenohd`と同じフォルダに置けば読み込まれます（既定の探索先に実行ファイルの親フォルダが含まれています）。

## Pub/SubとQueryのPython実装

Zenohの理解で最初につまずきやすいのが、Pub/SubとQueryが同じキー空間に同居する点です。ここでは検証用にマルチキャスト探索を切り、TCPエンドポイントを明示した最小構成で両方を動かします。ライブラリは`python -m pip install eclipse-zenoh==1.10.1`で検証と同じ版を導入でき、PyPIの1.10.1はPython 3.8以上が対象です。

### Pub/Sub：配信側と購読側の直結構成

購読側は127.0.0.1:7450で待ち受け、`myhome/kitchen/**`を購読します。

```
import json, time, zenoh

conf = zenoh.Config.from_json5(json.dumps({
    "mode": "peer",
    "listen": {"endpoints": ["tcp/127.0.0.1:7450"]},
    "scouting": {"multicast": {"enabled": False}},
}))

def listener(sample):
    print(f"[SUB] {sample.key_expr} = {sample.payload.to_string()}", flush=True)

with zenoh.open(conf) as session:
    sub = session.declare_subscriber("myhome/kitchen/**", listener)
    time.sleep(6)
```

配信側は同じアドレスへconnectし、1秒間隔で3つの値を送ります。

```
import json, time, zenoh

conf = zenoh.Config.from_json5(json.dumps({
    "mode": "peer",
    "connect": {"endpoints": ["tcp/127.0.0.1:7450"]},
    "scouting": {"multicast": {"enabled": False}},
}))

with zenoh.open(conf) as session:
    pub = session.declare_publisher("myhome/kitchen/temp")
    for t in (21, 22, 23):
        pub.put(f"{t}")
        time.sleep(1)
```

購読側は6秒で終了するため、両方のコードを別ターミナルに準備し、購読側の起動直後に配信側を実行します。この手順で、次の出力が得られました。ルーターは起動していません。ワイルドカード`**`を宣言した購読が、具体キー`myhome/kitchen/temp`への配信を受け取っています。

```
[SUB] myhome/kitchen/temp = 21
[SUB] myhome/kitchen/temp = 22
[SUB] myhome/kitchen/temp = 23
```

### Query：queryableの宣言とgetによる応答取得

Queryは、キーに対する問い合わせに応答する側（queryable）と、問い合わせる側（get）で構成します。応答側は`declare_queryable`でキーを宣言し、受け取ったqueryに`reply`を返します。

```
import json, time, zenoh

conf = zenoh.Config.from_json5(json.dumps({
    "mode": "peer",
    "listen": {"endpoints": ["tcp/127.0.0.1:7460"]},
    "scouting": {"multicast": {"enabled": False}},
}))

def handler(query):
    query.reply(query.key_expr, "23")

with zenoh.open(conf) as session:
    q = session.declare_queryable("myhome/kitchen/temp", handler)
    time.sleep(6)
```

問い合わせ側は`session.get`の戻り値を反復し、応答を順に受け取ります。

```
import json, zenoh

conf = zenoh.Config.from_json5(json.dumps({
    "mode": "peer",
    "connect": {"endpoints": ["tcp/127.0.0.1:7460"]},
    "scouting": {"multicast": {"enabled": False}},
}))

with zenoh.open(conf) as session:
    for reply in session.get("myhome/kitchen/temp"):
        print(f"[GET] {reply.ok.key_expr} = {reply.ok.payload.to_string()}", flush=True)
```

```
[GET] myhome/kitchen/temp = 23
```

この応答側をアプリで書く代わりに、zenohdのストレージに任せることもできます。次の章で、ルーターが値を保持し、後から起動したプロセスが同じ`get`で現在値を取り出す構成を実際に組みます。

## zenohdのREST・ストレージ設定と値の読み書き

zenohdに`rest`と`storage_manager`の2つのプラグインを載せると、HTTPのPUT・GET・DELETEでZenohのキー空間を読み書きでき、配信された値をルーターがメモリ上に保持します。センサー値を配信しつつ後から起動したプロセスが現在値を取りに行く、という構成がデータベースなしで組めるのがこの形です。

### 設定ファイル：RESTの待ち受けとmemoryストレージ

次の設定で、`myhome/**`に届いた値をmemoryボリュームへ保存します。

```
{
  mode: "router",
  listen: { endpoints: ["tcp/127.0.0.1:7447"] },
  scouting: { multicast: { enabled: false } },
  plugins: {
    rest: { http_port: "127.0.0.1:18473" },
    storage_manager: {
      storages: {
        myhome: {
          key_expr: "myhome/**",
          volume: { id: "memory" },
        },
      },
    },
  },
}
```

`http_port`は数値だけ書くと全インターフェース（IPv6の`*`）で待ち受けます。検証機で同じポート番号を別のプロセスが127.0.0.1で使っていると、curlの要求がそちらに届いて404や501が返り、zenohdの不具合に見えます。上の例のように`"127.0.0.1:ポート"`の形で待ち受けアドレスを固定しておくと切り分けが早くなります。起動ログに`Successfully started plugin rest`と`Successfully started plugin storage_manager`が出ていればプラグインは読み込まれています。

### curlによるPUT・GET・DELETEの動作確認

RESTのパスがそのままZenohのキーになります。2つの値を書き込み、ワイルドカードで読み出しました。

```
$ curl -X PUT -H 'content-type:text/plain' -d '21.5' http://127.0.0.1:18473/myhome/kitchen/temp
$ curl -X PUT -H 'content-type:text/plain' -d '19.0' http://127.0.0.1:18473/myhome/bedroom/temp
$ curl 'http://127.0.0.1:18473/myhome/*/temp'
[{"key":"myhome/bedroom/temp","value":"19.0","encoding":"text/plain","timestamp":"7695905292587871520/79c938f2…"},
 {"key":"myhome/kitchen/temp","value":"21.5","encoding":"text/plain","timestamp":"7695905292251532640/79c938f2…"}]
```

応答はJSON配列で、キー・値・エンコーディング・タイムスタンプ（時刻とルーターのIDの組）が返ります。zshではURLの`*`がファイル名展開されて`no matches found`になるので、引用符で囲みます。`/@/*/router`を読むと、ルーターのロケーターや読み込み済みプラグインの一覧が返り、構成確認に使えます。

RESTからの書き込みはZenohの通常の配信として流れます。Pythonのclientで`myhome/**`を購読しておき、REST側でkitchenへ24.0をPUTし、bedroomへDELETEを送ると、購読側には次のように届きました。削除は`curl -X DELETE http://127.0.0.1:18473/myhome/bedroom/temp`で実行できます。DELETEは`SampleKind.DELETE`として購読者に伝わり、ストレージからもそのキーが消えます。

```
[SUB] SampleKind.PUT myhome/kitchen/temp = 24.0
[SUB] SampleKind.DELETE myhome/bedroom/temp = 
```

### Pythonから書いた値がBase64で返る落とし穴

次の検証は前節の更新・削除より前の状態（kitchen=21.5、bedroom=19.0）で行いました。再現するときは前節の2本のcurl PUTを再実行してから進めます。clientモードでルーターにつなぎ、`put`してから`get`すると、REST経由で書いた値と合わせてストレージの全件が返ります。後から起動したプロセスでも、ルーターが動いている限り同じ結果になります。

```
import json, zenoh

conf = zenoh.Config.from_json5(json.dumps({
    "mode": "client",
    "connect": {"endpoints": ["tcp/127.0.0.1:7447"]},
}))

with zenoh.open(conf) as session:
    session.put("myhome/living/temp", "22.0")
    for reply in session.get("myhome/**"):
        print(f"[GET] {reply.ok.key_expr} = {reply.ok.payload.to_string()}")
```

```
[GET] myhome/kitchen/temp = 21.5
[GET] myhome/living/temp = 22.0
[GET] myhome/bedroom/temp = 19.0
```

ところが同じキーをRESTで読むと、Pythonから書いた値だけが`"value":"MjIuMA==","encoding":"zenoh/bytes"`と返りました。`MjIuMA==`は「22.0」のBase64です。`put`にエンコーディングを渡さないと値はバイト列（`zenoh/bytes`）として扱われ、RESTプラグインはそれを文字列とみなさずBase64で返します。ブラウザや他システムからRESTで読む前提があるなら、書き込み側で`encoding=zenoh.Encoding.TEXT_PLAIN`（JSONなら`APPLICATION_JSON`）を指定します。指定後は`"value":"22.0","encoding":"text/plain"`で返ることを確認しました。

### memoryボリュームの揮発とバックエンドの選び方

memoryボリュームの内容はプロセスの中にしかありません。zenohdを再起動した直後に`myhome/**`を読むと、空配列`[]`が返りました。再起動をまたいで値を残すなら、ストレージバックエンドを差し替えます。

| バックエンド     | 保存先                 | 配布                       |
| ---------- | ------------------- | ------------------------ |
| memory     | zenohdのメモリ（再起動で消える） | storage\_managerに内蔵      |
| filesystem | ホストのファイル            | zenoh-backend-filesystem |
| RocksDB    | 組み込みKVストア           | zenoh-backend-rocksdb    |
| InfluxDB   | 時系列DB               | zenoh-backend-influxdb   |
| S3         | S3互換オブジェクトストレージ     | zenoh-backend-s3         |

memory以外の4つは[1.10系のリリース告知](https://zenoh.io/blog/2026-08-17-zenoh-1.10/)に個別の変更履歴が並ぶ別リポジトリで、いずれも1.10.1がGitHub Releasesで配布されています。ライブラリをzenohdの探索先（実行ファイルと同じフォルダや`~/.zenoh/lib`）に置き、`storage_manager`の`volumes`で読み込んでから、各ストレージの`volume`にそのIDを指定します。filesystemのIDは`fs`で、保存先の親フォルダは環境変数`ZENOH_BACKEND_FS_ROOT`（未設定なら`~/.zenoh/zenoh_backend_fs`）で決まります。センサーの最新値をRESTで見せるだけならmemory、再起動後も最新値を残すならfilesystemかRocksDB、履歴を時系列で残して集計するならInfluxDB、と保存の目的で選びます。組み合わせるのは、zenohdと同じ版番号でリリースされているものです。

## MQTT・DDSとの違いと使い分け

選定でよく比較されるのがMQTTとDDSです。通信モデルと探索の仕方が異なるため、置き換えではなく適材適所で考えるのが実務的です。

| 観点     | Zenoh               | MQTT     | DDS            |
| ------ | ------------------- | -------- | -------------- |
| 通信モデル  | Pub/Sub＋Query＋ストレージ | Pub/Sub  | Pub/Sub（データ中心） |
| 仲介プロセス | 任意（peerで直接も可）       | ブローカー必須  | 不要             |
| 探索     | マルチキャスト＋ゴシップ        | ブローカーへ接続 | マルチキャスト中心      |
| 広域接続   | ルーター経由で連結           | ブローカー中継  | 既定の探索では不得手     |

MQTTはブローカーを中心に据える構成が前提で、機器からクラウドへ集約する用途に向きます。ブローカーの選定や配信品質の設計はそれ自体が論点になるため、[MQTTブローカーとは？仕組み・主要ブローカー比較・構築手順を解説](/tech/details/3894/)と[MQTT QoSとは？0・1・2の違いと選び方をmosquitto・Pythonの実装例で解説](/tech/details/3611/)を参照してください。DDSはブローカー不要で低遅延ですが、既定のマルチキャスト探索はサブネットをまたぐ環境で扱いにくく、越えるにはDiscovery Serverなど別の仕組みを足すことになります。Zenohはこの中間で、近接ノードは直接、離れたノードはルーター経由という構成を1つの設定で切り替えられる点が差です。逆に言えば、既存のMQTTやDDSが安定して動いていて拠点をまたぐ要件も無いなら、Zenohへ移す実利はほとんどありません。

性能面では、Zenohの公式ブログが2023年3月に[Zenoh・MQTT・Kafka・DDSの比較結果](https://zenoh.io/blog/2023-03-21-zenoh-vs-mqtt-kafka-dds/)を掲載しており、同じ内容は論文[arXiv:2303.09419](https://arxiv.org/abs/2303.09419)にもまとまっています。測定はAMD Ryzen 7 5800X（4.0GHz固定）・32GiB・Ubuntu 20.04.3、複数マシン時は100Gb Ethernetで、片道レイテンシは64バイトのping-pongの往復時間の中央値を半分にした値です。

| 対象                     | レイテンシ（単一マシン） | レイテンシ（複数マシン） |
| ---------------------- | ------------ | ------------ |
| Zenoh P2P              | 10µs         | 16µs         |
| Zenoh（ルーター経由）          | 21µs         | 41µs         |
| Zenoh-Pico             | 5µs          | 13µs         |
| Cyclone DDS            | 8µs          | 37µs         |
| MQTT（Mosquitto 2.0.15） | 27µs         | 45µs         |
| Kafka                  | 73µs         | 81µs         |

スループットは単一マシンでZenoh P2Pが最大67Gbps、Cyclone DDSが約26Gbps、MQTTが約9Gbps、Kafkaが約4〜5Gbpsでした。ただし使われたZenohは0.7.0-rcで、現行1.10.1の値ではありません。評価は国立台湾大学の研究チームが実施し、その寄稿をZenoh公式ブログが掲載しています。数字は「ブローカーを経由しない構成ではMQTTより1桁近く速くなりうる」という傾向として読み、採否は自分のペイロードサイズとネットワークで測って決めるのが筋です。ルーター経由にするとP2Pの約2倍のレイテンシになっている点は、構成を決めるときの目安になります。

既存資産との接続にはブリッジが用意されています。zenoh-plugin-mqtt、zenoh-plugin-dds、zenoh-plugin-ros2ddsがいずれも本体と同じ1.10.1で並行リリースされており、MQTTクライアントやDDSベースのシステムをZenohへ接続できます。ただし、MQTTプラグイン自身がブローカーとして動くため、MQTTクライアントの接続先変更、または既存ブローカー側のブリッジ設定が必要です。ROS 2のノードをつなぐ用途では、zenoh-plugin-ddsのリポジトリ自身がzenoh-plugin-ros2ddsを使うよう案内しています。移行判断の前に、まずブリッジで併存させて評価するのが安全です。工場やフィールド機器のデータ収集基盤ごと見直す場合は、[AI/IoTソリューション](/service/ai/iot/)として通信方式の選定から相談を受けています。

## ROS 2連携（rmw\_zenoh）の設定

ROS 2は通信層をRMW（ROS Middleware）として差し替えられます。rmw\_zenoh\_cppはros2組織で開発されている公式のRMW実装で、[REP-2000](https://github.com/ros-infrastructure/rep/blob/master/rep-2000.rst)のKilted Kaiju（2025年5月〜2026年11月）のミドルウェア対応表ではTier 1、同表の対象プラットフォーム・アーキテクチャすべてに対応すると記載されています。ただし、実際のサポート水準はプラットフォーム側のTierにも制約されます。同じ表にあるZenohの1.0.4という数字は、REP-2000自身が注記するとおりディストリビューション初回リリース時点の下限値で、以後の更新は追われていません。既定のRMWはLyrical LuthでもRollingでもrmw\_fastrtps\_cppのままなので、使うには明示的に切り替えます。

```
$ sudo apt install ros-lyrical-rmw-zenoh-cpp
$ export RMW_IMPLEMENTATION=rmw_zenoh_cpp
$ ros2 run rmw_zenoh_cpp rmw_zenohd
```

ここで見落としやすいのが、rmw\_zenohのノード用セッション設定ではマルチキャスト探索が既定で無効になっている点です。ノードはZenohルーターのゴシップ機能を通じて他ノードの情報を受け取る設計のため、ルーター（rmw\_zenohd）を起動しないとノード同士が見つかりません。ルーターを立てずに同一ホスト内で試すなら、環境変数で探索設定を上書きします。

```
$ export ZENOH_ROUTER_CHECK_ATTEMPTS=-1
$ export ZENOH_CONFIG_OVERRIDE='scouting/multicast/enabled=true'
```

`ZENOH_ROUTER_CHECK_ATTEMPTS`は起動時のルーター確認回数で、0なら接続できるまで待ち続け、負値なら確認を飛ばし、未設定なら1回だけ確認します。ルーターが後から起動した場合は自動で接続されます。設定ファイル全体を差し替えたいときは`ZENOH_ROUTER_CONFIG_URI`と`ZENOH_SESSION_CONFIG_URI`で絶対パスを指定します。なお、RMWを切り替える前にはROS 2デーモンを停止しておかないと、別RMWで起動したデーモンからグラフ情報を取得してしまい`ros2 node list`などが正しく動きません。ROS 2そのものの構成は[ノードとDDS通信の構造とROS 2世代の選び方](/tech/details/16824/)、環境構築の手順は[Ubuntu 22.04へのHumbleインストールとcolconビルド](/tech/details/16826/)で確認できます。ROS 2を使わずにRust製のデータフロー基盤から別マシン間をZenohでつなぐ例は、[dora-rsとは？Rust製ロボット向けデータフロー基盤の使い方とROS 2との違い【1.0対応】](/tech/details/8827/)で扱っています。

## 制約デバイス向けZenoh-Picoとの使い分け

マイコンやRTOS上で動かす場合は、C実装のZenoh-Picoを使います。本体と同じ日に同じ版番号（現行1.10.1）でリリースされるため、組み合わせの管理がしやすい構成です。Zenoh-Picoは機能を絞った移植版なので、ストレージやプラグインを伴う処理はルーター側に寄せ、デバイス側はclientモードでルーターへ1本のセッションを張る設計が基本になります（既定はclientモードで、UDPマルチキャストを使うpeerモードも選べます）。Linuxが載るエッジ機器なら本体版、RTOSやベアメタルで動く機器ならPico、と切り分けるのが実際的です。

## 導入時につまずきやすい設定

既定値のままでは意図通りに動かない箇所が、実装時のつまずきどころになります。

探索の範囲がまず問題になります。マルチキャストのスカウトはTTLが1で、同一サブネット内に閉じます。別サブネットやクラウド越しのノードをつなぐには、`connect`のエンドポイントに接続先を明示するか、ゴシップ探索の入口となるルーターを経由させます。本記事のPub/Sub・Queryの検証コードがマルチキャストを切っても通信できたのは前者で、相手のpeerがlistenしているアドレスを`connect`で直接指定しています。

暗号化はリンク層のプロトコル選択で行います。既定設定ファイルがサポート済みとして挙げるリンクはtcp・udp・tls・quic・ws・unixsock-stream・vsockの7種で、`transport/link/protocols`に列挙したものだけを受け入れる形に絞れます。ただし[zenohクレートの既定フィーチャ](https://github.com/eclipse-zenoh/zenoh/blob/1.10.1/zenoh/Cargo.toml)に入るのはvsockを除く6種で、vsockはビルド時のフィーチャ指定が要ります。TLSは証明書に加えて`enable_mtls`による相互認証を設定でき、証明書の有効期限切れで自動的にリンクを切る挙動も選べます（listener側から切断させる用途ではmTLSが前提です）。

同一ホスト内の大きなペイロードでは共有メモリ転送が効きますが、ここには前提が2つあります。1つは、共有メモリは`shared-memory`フィーチャを有効にしてビルドしたバイナリでのみ使え、既定フィーチャには含まれないこと。そうでなければ設定の`shared_memory`セクションは何の効果も持ちません。もう1つは、1.10.1の既定で`enabled: true`になっているこの設定が「対応していることを相手に通知する」だけで、実際に使われるかは相手側の設定にも依存することです。購読側でも有効にしておかないとネットワーク経由へフォールバックするため、送信側だけ設定して速くならないという誤解が起きやすい箇所です。

ルーターの再起動やネットワークの組み替えで取りこぼした値を検知・再取得したい場合は、zenoh-extのAdvancedPublisherとAdvancedSubscriberを使います（1.1.0で追加された不安定版API）。1.10.0では挙動が変わり、回復を有効にしたAdvancedSubscriberは、`retention_period`（既定1時間）を超えて無通信のpublisherの状態を破棄するようになりました。1時間以上黙るpublisherの欠落は検知されなくなるので、送信間隔が長いセンサーでは値を延ばすか、Livelinessで生存を監視します。

## よくある質問

### ZenohとMQTTはどちらを選ぶべきですか？

機器からクラウドへ集約する構成で、既にブローカー運用が確立しているならMQTTを維持するのが妥当です。近接ノード同士の低遅延通信や、拠点をまたぐ動的な接続構成が要件ならZenohが向きます。両方が必要な場合は、MQTTブローカーとして動くzenoh-plugin-mqttにクライアントを接続して併存できます。既存ブローカーを残す場合は、そのブローカー側で対応するブリッジ接続を設定します。

### zenohd（Zenohルーター）は必須ですか？

必須ではありません。既定のpeerモードでは同一ネットワーク内のアプリ同士が直接つながります。ルーターは、拠点間の中継、clientモードの機器の集約、ストレージやRESTなどのプラグインの実行に使います。ただし、到達可能なpeerへ直接接続すればサブネットを越えて通信でき、clientの接続先もrouterに限定されません。一方、ROS 2のrmw\_zenohはノード側のマルチキャスト探索が既定で無効なため、そのままではルーターの起動が前提になります。

### ROS 2のどのディストリビューションで使えますか？

[rosdistro](https://github.com/ros/rosdistro)には humble・jazzy・kilted・lyrical・rolling の各ディストリビューション向けにrmw\_zenohがリリース登録されており（2026年10月13日時点の登録版はそれぞれ0.1.11-1／0.2.12-1／0.6.9-1／0.10.7-1／0.14.0-1）、`ros-<DISTRO>-rmw-zenoh-cpp`としてバイナリで導入できます。新規採用では、対応OS、サポート終了日、必要なrmw\_zenoh機能を確認してディストリビューションを選びます。継続運用する製品では、開発版のRollingより、要件を満たすサポート中の安定版を優先します。なおREP-2000でTier 1と明記されているのはKilted Kaijuで、REP-2000自体は2025年7月を最後に更新されておらず、Lyrical Luthの節はまだありません。

### zenohdのREST APIで値を読み書きできますか？

できます。zenohdでRESTプラグインを有効にすると、URLのパスがZenohのキーになり、PUTで書き込み、GETで読み出し、DELETEで削除できます。GETはワイルドカードを使え、結果はキー・値・エンコーディング・タイムスタンプのJSON配列で返ります。値を保持させるにはstorage\_managerでストレージを設定し、保持しない場合はその時点で応答するqueryableやストレージがあるキーだけが返ります。

### 通信をTLSで暗号化できますか？

できます。リンクプロトコルとしてtlsとquicが用意されており、設定の`transport/link/tls`で証明書と鍵を指定します。`enable_mtls`を有効にすればクライアント証明書による相互認証になり、受け入れるプロトコルを`protocols`でtlsとquicだけに制限すれば平文接続を塞げます。

## 関連記事

- [MQTTブローカーとは？仕組み・主要ブローカー比較・構築手順を解説](/tech/details/3894/)
- [MQTT QoSとは？0・1・2の違いと選び方をmosquitto・Pythonの実装例で解説](/tech/details/3611/)
- [ROS（Robot Operating System）とは？ノードとDDS通信の構造・ROS 2世代の選び方を実装者目線で解説【2026年8月時点】](/tech/details/16824/)
- [ROS 2の環境構築｜Ubuntu 22.04へのHumbleインストールとcolconビルド手順【2026年8月時点】](/tech/details/16826/)
- [dora-rsとは？Rust製ロボット向けデータフロー基盤の使い方とROS 2との違い【1.0対応】](/tech/details/8827/)

---

出典: [Zenohとは？Pub/Sub・Queryの実装とzenohdの使い方・MQTT/DDSとの違い](<https://www.issoh.co.jp/tech/details/6210/>)（株式会社一創）
