見出し画像

REALITYのコメントシステムを Goで書き直してみた話 REALITY Advent Calendar #2

こんにちは、REALITY Advent Calendar 2021 2日目担当サーバエンジニアのゆーしです。開発合宿は1歳の愛娘と離れるのがつらいので、リモートでの参加となりました。

今回の合宿ではREALITYのコメントシステムをGoで書き直すことに挑戦しました。

REALITYのコメントシステムについて

現在のREALITYのコメントシステムは、node.js +  express + Redis Pub/Subを用いたWebSocketサーバとなっています。コメント以外にも、REALITYではWebSocketを使った仕組みが数多くあり、それらは全て同様の構成になっています。詳しくは以下の記事などをみてもらえればと!

一方で、REALITYのAPIサーバはGoで実装されているので、この機会にAPIサーバと同じアーキテクチャでコメントシステムを実現できないか試してみることにしました。

ということで置き換えていってみよー

REALITYのAPIサーバでは、Webアプリケーション開発のサポートとしてGorilla web toolkitを採用しています。そのため、WebSocketサーバを作る際もWebSocketライブラリとしてその中のGorilla WebSocketを利用し、RedisライブラリについてもAPIサーバと同様にgo-redisを利用していこうと思います。

移植していて思うことは、node.jsはイベント駆動モデルのため、

const wss = new WebSocketServer({
 httpServer: server,
 autoAcceptConnections: false,
})
wss.on('request', function connection(request: WebSocketRequest) {
  const ws = request.accept()
  ws.on('error', function listener(err) {...})
  ws.on('message', function incoming(input) {...})
  ws.on('close', function listener(code, reason) {...})
}

といったように、イベントハンドラを書けば接続のライフサイクルはさほど意識せずにかけていました。しかしながら、Gorilla WebSocketのサンプルを参考にすると以下のように、

func (App) request(w http.ResponseWriter, r *http.Request) {
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		return
	}
    ws := NewWebSocket(conn)

    go ws.read()
    go ws.write()
}

func (ws *WebSocket) read() {
	defer func() {
		ws.Conn.Close()
	}()
	ws.Conn.SetReadLimit(MaxMessageSize)
	ws.Conn.SetReadDeadline(time.Now().Add(Timeout))
	ws.Conn.SetPongHandler(func(string) error {
        ws.Conn.SetReadDeadline(time.Now().Add(Timeout))
        return nil
    })
	for {
		_, message, err := c.conn.ReadMessage()
		if err != nil {
			break
		}
        // メッセージ処理
	}
}
func (ws *WebSocket) write() {
    ticker := time.NewTicker(Timeout)
	defer func() {
		ticker.Stop()
		ws.Conn.Close()
	}()
	for {
		select {
		case message, ok := <-c.send:
			ws.Conn.SetWriteDeadline(time.Now().Add(Timeout))
			if !ok {
				ws.Conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
            // メッセージ書き込み処理
            
		case <-ticker.C:
			ws.Conn.SetWriteDeadline(time.Now().Add(Timeout))
			if err := ws.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
				return
			}
		}
	}
}

Goルーチンでソケットの状況を監視するような作りとなっており、実装をしくじるとリークや無限ループが生まれそうで気を使いそうでした。

とはいえ、合宿の時間は限られているので細かい構成の検討は抜きにして、とりあえず実装していきます。

最終的に

この通り、無事新しいシステムに繋げてコメントを送ることができました!

画像1

REALITYのコメントシステムはコマンド機能で翻訳やおみくじができたり、その他にも様々なメッセージを表示する機能が実装されていますが、今回の合宿ではそういったものを全て移植することは叶わず、入室コメントの表示と通常のコメント送信を行えるようにするのみとなっています。

今後もう少し手を加えて、パフォーマンスの検証もやってみて、現在のものより良くなりそうであれば、刷新も考えていこうかなとも思っています。

REALITYではこういった取り組みで作ってみたものでも、使えるものはどんどんサービスに取り入れています。もしかすると明日以降の合宿の成果発表の中には、実際にサービスに導入されるものも出てくるかもしれませんね。

明日のアドベントカレンダーは!

3日目は、合宿中にREALITYエンジニアチームの中での常識人枠に認定されたsochiaiさんによる「REALITYにフリーマーケットを作った話」です。お楽しみに!