AndroidスマホでNostrP2Pのサーバが動いた

先日以下のような記事をQiitaに書いたのですが、サーバをずっと動かすとなるとそれが可能なマシンを用意できるか、持っているかが問題だよなー、とか考えてました。

qiita.com


で、そういえば、AndroidスマホならTermuxというAndroid端末内に独自Linux環境を構築するアプリがあるので、使わなくなったAndroidスマホなんかを有効活用して、サーバ動かしておくというのは悪くないのでは、と思い試してみたところ、見事動きました!

(そもそもAndroidLinuxの一種なので、アプリがユーザに見せるシェルにおいてだけ、chrootファイルシステムのルートの位置を変更して、あれこれ必要なファイルを配置してあげれば、独自のLinuxユーザランドが作れるよね、というのがTermux、という認識)


まずは、下のサイトなどを参考にしつつ、Fdroidアプリを経由してTermuxをインストール。

www.miraclejob.com

wiki.termux.com

 

Termuxがインストールできたら以下のようにコマンドを実行すればサーバ起動までたどり着けます。

$ apt update
$ apt upgrade
$ apt install -y golang git
$ git clone https://github.com/ryogrid/nostrp2p
$ cd nostrp2p
$ go build -o nostrp2p main.go
$ ./nostrp2p server -l 0.0.0.0:20000 -p <npub形式の公開鍵> -b ryogrid.net:8888

あとは、スマホIPアドレスを調べて、クライアントに指定すればOK。*1

ちゃんと、ブートストラップサーバにも接続できていました。

なお、サーバを止めるときは Ctrl + c でサーバプロセスを終了させて、exitコマンドでシェルを終了、からのTemuxを普通のアプリと同じように終了させるだけ。
Termuxで構築した環境が不要になった場合は、Termuxをアンインストールすれば、環境とその中で作ったファイル含めて削除してくれるはず。

以下、スクショ。

 


クライアントのサーバアドレス設定

(今回はWindows用のネイティブクライアントを使いました)



投稿してみたところ

 

ブートストラップサーバ(兼Trial用サーバ)への投稿データの転送(自分用サーバがブロードキャスト)も正常に行われ、Trial用サーバに接続したWebクライアントでもちゃんと表示されています。

 

いやー。Termuxすごいなー。

 

*1:WiFiで接続していたら、設定画面のネットワークのところから辿っていけば割り当てられているIPアドレスを確認できます。IPアドレスがちょこちょこ変わって困るという場合はtailscaleなどのVPNアプリを導入してVPN上の固定アドレスを使えばOK

ピュアP2P分散マイクロブログシステムの第一歩

オーバレイでメッセージをブロードキャストできるだけのものができた!
https://github.com/ryogrid/buzzoon/releases/tag/v0.0.3

試し方(サーバ起動)

ryogrid.net:9999
ryogrid.net:8888
に既にオーバレイネットワークに参加しているサーバが2匹いるのでどちらかに接続。

バイナリはWin環境なら buzzoon.exe、Linux環境なら buzzon_linuxMac環境ならbuzzoon_mac

ローカルのサーバは下のように起動

$ .\buzzoon.exe server -l 0.0.0.0:20000 -p 1985 -n ryo -b ryogrid.net:9999
  • -l オプションはバインドするローカルアドレス
  • -p オプションは本来は公開鍵を指定するのだけど署名の検証とか未実装なので、他の人と被らなそうな10進の整数を指定すればおけ
    • これはオーバレイNW上でのノードIDとして使われる
  • -n オプションはニックネームの設定。Nostrでいう所のkind-0相当をまだ実装していないので、これで指定したニックネームでメッセージが送信されるようにしてある
  • -b オプションはオーバレイネットワークに既に参加しているサーバのアドレスを指定。とりあえず ryogrid.netの2ノードのうちのどちらかを指定すればOK

メッセージの送信

上の例だと、http://127.0.0.1:20001/postEvent にRESTのエンドポイントが立っているのでそれを叩く。
(ポートは-lオプションで指定したものに+1)
curlで叩くなら下のような感じ。

$ curl -X POST -H "Content-Type: application/json" -d '{"Content":"こんばんみ"}' http://127.0.0.1:20001/postEvent
レスポンス↓
{
  "Status": "SUCCESS"
}

受信したメッセージはサーバのstdoutに出る。

https://i.gyazo.com/61e5acbb137639d1dc4371ff8e6703b2.png

良かったら試してみてね!

私の考える最強のNostrプロトコル拡張(考え中)

 分散システムに明るいと自称している私なので、Nostr *1なアプリケーション(ここではマイクロブログSNSとする)をスケール可能(全体のユーザ数が増加しても使えるものであり続ける)にするためにはどうすればよいかと、思考実験レベルで考えてみている。
 これならいける!というところにはたどり着いていないけれど、アイデアとか、糸口になるかなーみたいな気づきはあったのでここに記す。

Nostrの特徴として維持されないといけないこと

大事。

  • 中央集権的なサーバに頼らない
  • リレーサーバは気軽に建てられて運営をやめてもユーザに迷惑がかからない
    • (ボランティアベースになるというところはひとまず仕方がない、ということにしておく)
  • リレーサーバに要求されるマシンスペックや通信リソースがべらぼうな量にならない
  • クライアントはそれなりに遅延なくデータを取得できる
    • (ピュアP2Pなアーキ等だと遅くなりがちであるということを意識してのところ)

スケールさせる上での課題は何か?

前提知識

  • (少なくとも現状の仕様では、)フォローされているユーザAとフォローしているユーザBは共通のリレーサーバへの接続が少なくとも1つないと、BはAの投稿を取得することができない
    • 接続するリレーサーバを変更した際などに、フォローしてるユーザを見失う、フォロワーを置き去りにしてしまう、ということが起きる
    • 現状ではユーザ数がさして多くなく、定着したユーザの同質性からコンセンサスを成立させられているので、例えばJPなユーザは特定のいくつかのリレーサーバに接続していればOK、というようなユーザ側の工夫でどうにかなっているところがある
  • ユーザは自身の投稿やメタデータの冗長性を確保するため、また上のポチで記述したようにフォロワーが接続しているリレーサーバにデータを送信する必要があるため、少なくとも3つから5つ程度のリレーサーバには自身の投稿のデータやメタデータを送信している

課題

  • ① 1つのリレーサーバがさばけるクライアント数には当然限界が存在するので、現在存在するリレーサーバの台数だけでは、ユーザの増加に対応しきれない
    • => (抜本的にアーキを変更して、クラサバ一体型のピュアP2Pにでもしない限り、)リレーサーバを増やす必要はあるはず。それ以外の対策については後述
  • ②上のポチで記述した通りリレーサーバが増えた場合、多数のリレーサーバが存在する中で、ユーザは接続すべきリレーサーバを適切に選択することができない
    • 前提知識のところでコンセンサスがあるのでどうにかなっていると書いたが、ユーザ数が増加して、各々のペルソナも多様になった場合、同じような方法ではどうにもならなくなる
    • 各クライアントはフォロイー、フォロワーが少なくとも1つ(冗長性を考えれば2-3程度)のリレーサーバを共通に持つようにマッチングがとれている必要があるが、そのようなリレーサーバ群をユーザが知る手段は(コンセンサスが成立していない場合、)存在しない

どのような手がありそうか?(≒拡張の余地がありそうなところ)

  • 最低限、リレーサーバの連携は必要なはず。また、リレーサーバのソフトウェアはもっとインテリジェントになる必要がある
  • 可能であれば、スケールさせるための仕組みにクライアントを協力させることも考えられる
  • クライアント間の連携も通信コスト的に見合うならやってもよさそう
    • とはいえ、クライアント間でのP2Pでの直接通信まで踏み込むのは筋悪そうなので考えない

 
以下に、上の思いつきを詳細化していく。
が、まずはそもそもクライアント側の要件は何であるかを確認する。
 

クライアント側の情報取得に関する要件

  • クライアントは最低限フォローしているユーザの投稿のデータは受信できる必要がある
    • (どれだけの期間分受信できればワークするかは想定するユーザの使い方によって変わるので明確化は難しい)
  • リレーサーバに持たせている各種メタデータも(自前で持っておくこともしたほうがいいし、仕様として整理が必要そうな気はするものの、)受信できる必要がある
    • (複数の端末やクライアントで同時にアプリケーションを利用可能であるという利便性は捨てられない、と思う)

クライアントの動作環境等に関連する要件

  • スマホ程度のスペックでも動作し、通信量に制約のあるモバイルネットワークでも無理なく使える
    • パケ死やバッテリ消費やばい問題が起きない)
  • なお、スマホ等のモバイルデバイスでの通信に着目すると、恒常的に大量の通信を要することはクリティカルだが、(私の知る範囲では、)Websocketのコネクション数が多い事自体は、数十程度に収まっていれば、問題とならない・・・はず(要エビデンス

 
要件を踏まえて、以下。  
 

具体的に必要な仕様拡張他

確実なもの(課題①に対応する内容)

  • クライアントが、利用するデバイスや環境、ユーザの求める機能性に合わせて必要なデータ量を絞れるようになる
    • Amethyst(Androidスマホ用クライアント)等が持っているリレーサーバことの通信内容の設定ができればおおむね良いとは思うが、Likeなどの通知の類もTPOに応じて切ることができればさらに良い
  • クライアントがイベントのサブスクライブをしている際に、イベント情報を1つ1つ個別に送ると、各々に重複する情報があって無駄が多いケースがあるようなので、一つのメッセージに複数の同種のイベント情報をまとめられるように仕様拡張する
    • サーバ側の処理負荷は増すかもしれないが、サーバ・クライアントともに通信量は削減されるはず

イデアレベルのもの(課題②に対応する内容)

  • リレーサーバが連携する前提で、分散ハッシュテーブル(DHT)の仕組みを使って、公開鍵の値からあるユーザを担当するリレーサーバを決めるようにする*2
    • あるユーザのデータを保持しているリレーサーバを、全てのリレーサーバが lookup することができるようになるため、当該ユーザのデータを取得したいユーザ(クライアント)は、初回は適当なリレーサーバにサブスクライブ要求を投げ、要求を受け付けたリレーサーバはその要求をHTTPでのリダイレクトのような形で担当リレーサーバに回し、リダイレクトされたクライアントは以後、リダイレクト先の担当リレーサーバにサブスクライブのリクエストを行う
      • イベントデータを投げる場合も基本的には同様
    • 冗長性の確保のため、担当リレーサーバは1つであるものの、それをマスターとして、他に(ここではひとまず)2つ、のスレーブ担当のリレーサーバがDHTの仕組みの中で、共通のルールに基づき定められる形とし、それらのアドレス情報はデータを投げつけるクライアント、サブスクライブするクライアントのいずれにも、リダイレクトが行われた際に返却されるようにする
    • データを投げつける際はマスターだけでなくスレーブ全てに投げるものとする
    • サブスクライブするクライアントはマスターと、スレーブのうちの1つで、合わせて2つのリレーサーバに接続するものとする
      • どちらかかがダウンしたりサービスを停止したら、もう一つのスレーブに接続する
    • マスターを主体として、常にスレーブは規定台数維持され、足りなくなれば新たに追加し、そのアドレスをクライアントに通知する
    • マスターがいなくなった場合は一番古株のスレーブがマスターに成り代わり、新たなスレーブを1つ決定し、マスターが切り替わったことと、新たなスレーブのアドレスをクライアントに通知する

DHTで担当サーバを決めるアーキの良いところと良くないところ

  • 良いところ
    • 中央集権的なサーバが不要
    • 元々、リレーサーバが突然いなくなることが考慮されている
    • 今回考えているアプリケーションは割といい加減なアプリケーションなので*3メタデータを除いて、イベントデータが多少消えるなどしても大きな支障はない
      • DHTでデータを格納するというと、分散Key-Valueストアと似たようなものと見なせないこともないが、いわゆるデータストアほど信頼性や、データの一貫性にセンシティブになる必要は無い
  • 良くないところ
    • この手のものはうまく実装しないと期待通りに動かない場合が往々にしてある()
    • クライアント観点では、フォローしているユーザにおおむね比例して接続すべきリレーサーバが増える
      • 1つのリレーサーバは複数のユーザを担当することになるので、完全に比例するかと言えばそうではない
    • ただし、接続数が増えはするが、受信しなければならないデータの量は基本的には変わらない(はず)

なんとなく思っていること

  • Twittetrでも"クラスタ"などという言葉があるが、完全に、もしくはある程度、ソーシャルネットワークの中には分断が起きている
  • この分断に着目し、ユーザ数の増加に真正面から対応するのではなく、分断されている"クラスタ"の中でのやりとりであれば大きな遅延なく行えることをターゲットとし、リレーサーバ・クライアントが連携して、上述の担当サーバが決まるようにできれば、フォローしてるユーザ数に(おおむね)比例してリレーサーバへの接続数が増えるようなことは避けられるのではないか、とか
  • "クラスタ" うんぬんの話と同様に、フォロワーが非常に多いユーザは同じリレーサーバにまとめる、とかすれば良いのかも?
  • 上の方で "リレーサーバのソフトウェアはもっとインテリジェントになる必要がある" と書いたが、そのあたりはこのセクションで記述した内容あたりにつながっていたりする
    • リレーサーバがクライアントに言われた通りデータを格納し返すというだけでなく、扱っているデータの中の閲覧が許される部分の内容を分析して、他のリレーサーバと情報を共有するといったことをするようにならないと、ここで書いたようなことは実現できないはずである

 
 
ひとまず、以上!

*1:プロトコル仕様はここで管理されている

*2:リレーサーバは運用開始時に適当に自身のDHT上でのIDを決定する。例えば、グローバルIPアドレスをハッシュするとかで良い

*3:現在でもポストがちゃんと届いているかと言うと怪しいが、そういうものだと思ってユーザは受容しているように見える

考察: Nostrプロトコルはオーバレイネットワークを構成するためのプロトコルなのではないか?

Nostrプロトコルって何やねんという方はまず以下の記事などを参照いただけると良いかと。

qiita.com qiita.com

さて、読者の方々がNostrプロトコルの概要は理解している前提で以下、掲題に関して。

 数日前にNostr(プロトコル上で構成されたTwitterライクなマイクロブログ)で follow している、あるユーザが、Nostr(プロトコル)はリレーサーバを信頼してないという点でP2Pと同じである、リレーサーバはただのハブである、といった主旨のことを投稿していて、それを読んでハッ!っとひらめいたことがあった。
 それは、Nostrプロトコルというのは、本質的にはクライアントをノードとしてpeer-to-peerでやりとりできるオーバレイネットワーク(以降、オーバレイと略記)に近いものを構成するプロトコルなのではないか、という見方である。

  • オーバレイでのアドレスは公開鍵
  • クライアントがオーバレイ上でのノードに対応づく
  • 公開鍵を指定することでP2Pでの通信が可能
    • ただし受信されるのは受信側が送信元からのメッセージを待ち受けているタイミング
    • 送信時に待ち受けている状態であれば、そのタイミングで受信される
      • 受信側が待ち受けしない(=要していない)送信元からのメッセージは受信されない
    • 一度送信したメッセージは送信時に相手がオフラインでも、ネットワークに復帰したタイミングで受信できる
  • 他のノード群に、ゴシップブロトコル*1等による他のノードとの連携を要する手法を用いずに、比較的高速にメッセージのブロードキャスト/マルチキャストが可能
    • なお、この通信においても、受信側のノードは、送信時にオフラインでも、オンラインに戻った際にメッセージを受信できる

 で、上のような通信特性を実現しているのが、オーバレイの下のレイヤにいるリレーサーバである。
 クライアントから受信したメッセージ*2は、必要があれば他のクライアントにブロードキャスト/マルチキャスト可能であるし、メッセージングミドルウェア(なんちゃらMQといった名前を持つ類のもの)のように、あるクライアントから受け取ったメッセージは、他方のクライアントがリレーサーバに接続して、メッセージの受信をサブスクライブした時点で送信してくれる*3
 オーバレイとして見ると、明確に当てはまらないのは自身や他のノードが、メタデータ(フォローしているユーザの情報、接続しているリレーサーバの情報)を、(脚注の条件を満たすもののうちの)任意のノード*4について、ネットワークから取り出せる点。 この点は、冒頭で"オーバレイネットワークに近いもの"と書いているように、素直にオーバレイネットワークを構成するもの、と言っていない理由の一つ*5

 こう整理すると、クライアントの仕事(必要な処理の総量)がやたらと多い*6のも納得がいく。
 他には、例えば、リレーサーバに保持させておいたメタデータが消えてしまったり、内容が欠けてしまったりするのも、オーバレイのレイヤから見るとネットワークが提供する謎機能を使ってる以上仕方なく、オーバレイ上のノード(自身およびその他ノード)によるフォローが必要なのでは?という話になりそうである。
 実際、手動にはなるが、Webサービスとしてメタデータが飛んだ場合の復元を可能とするためのサービスが有志により提供されているが、当該サービスは内部的にNostrプロトコルを扱っており、それは一種のクライアントで、オーバレイのレイヤでいえばノードである。

*1:あくまで一例。もっと効率的なプロトコルを利用可能なアーキテクチャをとっている分散システムも多数ある

*2:Nostrプロトコルでは、"イベント"と呼ばれる

*3:メッセージングミドルウェアのように到達保証まではしてくれないが

*4:少なくとも取得対象のメタデータの持ち主が当該情報を書き込む先の1つに設定していて、取得する側も当該情報の取得先の一つとして設定しているリレーサーバが存在する

*5:他のノードの助けを借りずに、時差ありでメッセージを受信可能な点や、ブロードキャストができる点もおかしいだろうという話はあるが、それぐらいは特殊なオーバレイである、という整理にしてしまってもよいかな、と

*6:少なくともTwitterライクなマイクロブログのアプリケーションなどでは、ユーザのクライアントに対する設定にもよるが、多数のリレーサーバとWebsocketコネクションを持ち、大量のメッセージを処理する必要があるケースが多い

RDBの趣味開発に一緒に取り組んでいただける方に求む要件等

(https://mobile.twitter.com/ryo_grid/status/1461110789570396162
に貼るために書いた記事です)

140文字だと納まらないので、ブログエントリの形で記述させてもらいます。
お願いする立場にもかかわらず条件をつけてしまい、厚かましいかとも思いますがご理解をお願いいたします。

少し遠回りになりますが、まず、やろうとしていることについて改めて説明させて頂きます。

開発の目的

  • 一文で言えば、「リレーショナルデータベース(RDB)を作ってみたいので作る!」
    • RDBを実際に実装してみることで、理解を深めたいという思惑もあり

開発用のGitHubリポジトリ


実装で目指すレベル

  • いわゆる車輪の再発明というやつで、特別に新規性のあるものを作ろうという話ではありません
    • あまり凝ったものを作るつもりはひとまずありません。教科書的なオーソドックスな実装で良いと考えています
    • ただし、一般的にRDBの構成要素として挙げられるところは一通り実装したいと思っています
      • 下図のCluster Communication、Remote Execution以外のところ


f:id:kanbayashi:20211120202139j:plain:w200

一緒に進めてくれる方を求めている理由

  • 一人でやるより何人かで取り組んだ方がモチベーションをキープできそうだから
    • 従って、求む方に関して開発にどれだけ貢献できるか、といったところは重要視していません
  • 時間がとれなければ活動が出来なくても仕方ないですし、飽きたので抜けるといった判断も尊重します
    • 極論、私がどこまで継続できるか、コミットできるか、も分かりません
  • 開発は専用のSlackを用意して、そこでコミュニケートしながら進められればと考えています

あなたに求む要件(条件)

  • 私と直接の面識があるか、ネット上でいくらかやりとりをしたことがあること
    • コミュニケーションをとりながら進めていくことを考えた場合に、ほとんど存じ上げない方の場合、うまくやっていけない懸念が生じるため
  • 職業プログラマであるか、それに近い程度のプライベートでのソフトウェア開発経験があること
    • RDBについては、使ったことがある、SQL書いてクエリ投げたことがある、という程度の知識があれば十分です
  • 「自分もRDBを書いてみたい!」というパッションを持っていること
  • ペースはさておき、一緒に学んでいきたいという姿勢であること

上記をご確認の上、一緒にやってもいいという方がいらっしゃれば、TwitterのDMにてご連絡をいただければと思います!
要件のところについて判断の上(あくまで私の主観での判断になることはご理解ください)、返信させていだきます!

よろしくお願いします!

リポジトリ名の変更もイカしたネーミングの提案があれば検討します

RESTで叩けるChordな分散KVS(Rust製)が動くところまで出来た!

RESTで叩けるChordな分散KVS(Rust製)が動くところまで来た!
長い道のりだった(感涙

  • 一応ノードダウンにも耐えるように作ってある
    • まだ、改善は必要
  • データはレプリケーションしているので、ある程度はノードがダウンしてもロストしない
  • データの整合性がどの程度守れるかはランニングテストでもして確認してみる予定
    • 整合性が極力守られるような考慮は、まだあまり実装できていないが・・・
    • ノードダウンやノード参加も適宜行いつつやってみる

github.com

Rustなので? WindowsMacLinuxとプラットフォームは選ばないし、ブラウザから叩けるようにもしてあります。
興味があれば Let's build and run !