正直、精査していないので普通に穴があるかもしれない。
GitHub Repo
- コンセプト
- 利用者皆の貢献により構成されるシステム
- 課題感: 既存の分散SNS(Mastdon、Nostr、Bluesky、etc..)は言うて、サーバ運用者にかかる負荷が高く、その割に見返りもない
- 利用者皆の貢献により構成されるシステム
- その他のポイント
- gossipプロトコルによるブロードキャストを軸にしたシステム(メッセージングはweaveworks/mesh ライブラリが担う)
- パフォーマンスやデータの一貫性より実装の容易さとシンプルさに重点を置く
- 結局のところは省工数にしたいという理由に落ちるかもしれないが、この手のシステムで複雑な仕組みを入れると安定して動くようにするのが大変
- 上の理由からDHTなどの構造化の仕組みは(ひとまず)採用しない
- 全体的にファジーにやる(ex: イベントデータのロストも少量であれば許容する)
- 他の分散SNSと比べてピュアP2Pというまともなパフォーマンスで動かすのが難しいアーキであるが、ユーザ体験のレベルとのトレードオフでどうにかする
- ただし、どこなら落として良いかの見極めはする
- 他の分散SNSと比べてピュアP2Pというまともなパフォーマンスで動かすのが難しいアーキであるが、ユーザ体験のレベルとのトレードオフでどうにかする
- 各サーバはオーバレイNW上で動作させる(NATはグローバルIPを持つサーバによる中継で超える)
- Nostr のアイデアをベースとする
- 秘密鍵と公開鍵を認証基盤?として利用する、イベントデータのデータ構造、マイクロブログのアプリケーションを実現するための各種Kindの設計など
- ただし、アーキテクチャが異なることから、出来なくなること、逆に可能となることがあるのでそれに合わせてプロトコルの最適化を行う
- 従って、寄せられるところは寄せるものの、それが困難な点などでは互換性が失われた形での設計となる
- goでmeshを使う前提(meshと互換性のあるライブラリが他言語にもあれば良いが現状無い)
- meshでは64bitのIDを各ノードが持っている
- 各ノードはオーバレイNWに参加している全ノードのIDを知っている(最新化まで遅延はあるが、100ノード程度までであれば、2-3秒もあれば大丈夫・・・ではないかと思う)
- 各ユーザが自身のマシンにサーバを立てる。各ユーザが利用するクライアント(プレゼンテーション層を担う何か)はreadもwriteも自身のそれに対して行う(詳細後述)
各サーバがオーバレイNW上で64bit ID空間にマップされていることを活用し、DHTベースのKVSなどと同様の考え方で、データのレプリケーション他を行う(詳細後述)- Nostrプロトコルが可読性と汎用性に重きを置いた結果、マイクロブログシステムでの利用で通信量が比較的大きなものとなったことを踏まえ、データをバイナリフォーマットにシリアライズする、アプリケーション特化での最適化を行うといった方法で通信量を低く抑える
- pullよりもpush(詳細後述)
- 各自が自身のマシンにサーバを立てる。readもwriteもそれに対して行う
- 自宅マシン(つまりグローバルIPを持たないマシン)に立てたりした場合、tailscaleでVPN張ってスマホなんかからはアクセスする想定
- パブリックIPで運用するサーバもいないとオーバレイできる系が成立しない
- => パブリックIPで運用する場合の考慮としてクライアントからのwriteも署名で検証する設定を用意する
各サーバは自身がオフラインの間のイベントデータを復帰時に受け取れるよう2つの代理サーバを持つ代理サーバの決定ロジックは後述のレプリカ担当と同様
- pullよりもpush(を基本としてまずは考えてみる)
- ブロードキャストして、followしてる者(もしくは代理役を任されているサーバの依頼元?がfollowしている場合)にだけ受け取ってもらい、他のものには捨ててもらう
- 原則、ブロードキャスト時に受け手はオンラインである想定
が、サーバがオンラインになった際は未取得のデータを自身の代理サーバから受け取るfollowしていなくとも自身がレプリカ担当であれば、保持する。マスターがオフラインであった場合に問い合わせが来たら返すレプリカ担当は2ノードとし、マスターがオフラインの場合は、両方に問い合わせて結果をマージ1番目のレプリカ担当はChordと同様にID空間を円環として見た上でマスターのIDにID空間の最大値の三分の一を足し、それより大きく、かつ一番近いIDを持つノードとする2番目のレプリカ担当は三分の二を足して同様にする
- ブロードキャストして、followしてる者(もしくは代理役を任されているサーバの依頼元?がfollowしている場合)にだけ受け取ってもらい、他のものには捨ててもらう
- 秘密鍵
- クライアントだけが持っていれば良い
- サーバには公開鍵だけを設定しておく。その情報があれば、正しい秘密鍵を持っていないユーザからのwriteは弾けるし、他のサーバが一部のメッセージをユニキャストで飛ばしてくることもできる
- なお、秘密鍵・公開鍵はNostrのものがそのまま使えるようにする(HEX形式)
- 基本はオンメモリでデータを保持
- が、永続化も定期的にgobで書き出す程度はやる。ひとまず(毎回全ては無駄なので、時刻の範囲でまとめる?そのためには時刻範囲を絞れるデータ構造が必要か)
- メモリに載らない量になってきたら古いデータを捨てていく
- 組み込みDBを導入するのも悪くないが、検索が必要になった際の負荷を考えると、対象となるデータは少なく維持した方が良いのでは、という思いからまずは上ポチの仕様
- サーバ間通信はバイナリで、{kind、コンテント、タグ、公開鍵、署名、イベントID、タイムスタンプ}。バイナリに置き換えている点を除いてNostrと同一
だが、イベントIDは64bit(uint64)に変える- バイナリフォーマットにはひとまずMessagePackを使う。後でProtoBufに切り替えるかもしれない
- サーバを立てる時の面倒が増えるので、通信を over TLSで行うといったことはしない
- meshはアプリケーションで共通に設定したパスワードから共通鍵を生成し暗号化を行えるようだが、パフォーマンス低下の懸念やOSSとの相性の悪さから当該機能は利用しない
- もしやるにしても、E2EEを別途行うといったことになろうが、ブロードキャストと相性が悪いという課題がある
- フォロー
- NW上のノードID一覧(≒ ユーザID一覧)は分かるので、その情報からプロフィールを引いて、ユーザ覧を出すことは作り上は可能
- が、どちらかというと、他のルート(ex: SNS、他のマイクロブログ)で公開鍵を知ってfollowする、というフローの方がお遊びシステム的にはちょうどいい感じもする
- リプライ
- フォローしている者に対してしかできず、当事者間だけにしか見えない
- 対象の投稿のイベントIDは分かるので、それをコンテントに含めて投げる。うまいことネストはできるようにする
- ファボ
- post元のユーザのマスターサーバ
及び、レプリカ担当のサーバ個々にユニキャストする オンラインになった時はレプリカ担当からイベントデータを受け取る- ファボした者は対象の投稿にファボしたことのみ分かり総数は分からない。された者は総数が分かる
- ファボしていない者は投稿に関するファボについて何の情報も知り得ない
- post元のユーザのマスターサーバ
- プロフ
- 更新したらブロードキャスト
- 新規にfollowした場合はマスター
かレプリカから最新のものを取得 (レプリカの仕組みが実装されるまでの暫定的な仕様として、20%の確率でpostのタグにプロフの最終更新時刻を含め、それにより古いプロフを持っているフォロワーは更新の必要性を知ることができるようにする)
ノードの参加と離脱離脱に関してはgracefulに行われないことを前提とする参加時IDのレンジに応じてレプリカの担当が変わるので、データの委譲を良しなにやる
離脱時レプリカ担当が離脱してしまった時に、レプリカの数が2つである状態を維持しておく必要があるどうやって離脱を検出するか?マスターがレプリカ担当ノードにユニキャストで定期的にハートビートしておく・・か?離脱したノードが戻ってくる可能性も考えると、離脱している時間が一定時間を超えたら、レプリカを増やす、とかにしたいが大変そう
- クライアント
- 自分のサーバと通信
- バイナリでreq/respするRESTをひとまず想定
- (その前段で、サーバサイドレンダリングでクライアントを済ませたり、テキストベースのREST I/Fから作ったりと段階的に進めるとは思う)
- 前述の通りwriteには署名をつける
- readへのレスポンスはイベントデータの形式に沿ったものとしない。少なくとも署名はサーバで検証済みなので除く
ノード離脱が起きた場合、レプリカを増やすことがあるが、あるノードA(離脱したノードより大きいIDで一番近いIDを持っていたノード)にレプリカのデータを渡すにしても、今の設計だと、離脱したノードが複数のマスターのレプリカ担当な場合があるそれらのマスターないし、それらのレプリカ担当らのリストを作って、各ノードからレプリカデータを受け取るというのは結構しんどい気がする。そもそも、データをもらう先のノードリストが簡単に作れるだろうか?