こんにちは。私たちは 06/27, 28 の2日間で開催された春ハッカソンに参加し、QOSMOS というWebアプリケーションを開発しました!
このブログでは、QOSMOSの紹介と使用した技術について書いていこうと思います。
補足:春ハッカソンにて技術賞を獲得出来ました。投票してくれた皆さんありがとうございます。
※ QOSMOS には traP の現役部員のみがログイン・アクセスすることができます。外部公開はされていません。
QOSMOS とは?
一言で言うと、traP のオンライン上でのやり取りやその盛り上がりを星空に見立てて可視化する Web アプリケーションです。
traP では内製の連絡ツール兼交流ツールである "traQ" が使われています。traP の部員同士は traQ を用いて開発を進めたり、イベント参加者を募ったり、雑談したりしています。今回の春ハッカソンの告知や開発のための準備も traQ 上で行われました。

さて、traQ が本格的に使用されるようになってから10年弱という長い期間が経過しており、チャンネル数は非常に膨大なものになっています。また、traQ では階層型のチャンネル作成をサポートしており、あるチャンネルに紐づけて関連した子チャンネルが大量に作成されています。その結果、traQ の全体像は精力的に活動を行っている部員でも把握しきれていないという現状があります。
この複雑な構造を有している traQ を何とか可視化できないだろうか?そして、せっかくならば美しく可視化して、今どのチャンネルが盛り上がっているかまで把握できないだろうか?というモチベーションからハッカソンで QOSMOS を開発することが決定しました。
QOSMOS は大まかに以下の機能で成り立っています:
- traQの階層型チャンネルの構造を可視化
- ユーザーのチャンネル移動やメッセージ投稿の可視化
- 頻繁にメッセージ投稿や閲覧が行われているチャンネルの強調
3つ目については、直近の閲覧者数やメッセージ投稿数をもとに各チャンネルごとに盛り上がり度のスコアを算出し、相対的に最も盛り上がっているチャンネルを決定して表示します。
QOSMOS の機能紹介
QOSMOS にログインすると、描画構築後に次のような画面になります。

- チャンネルツリー

星を模した光り輝く球を用いてtraQの階層型チャンネルを可視化しています。
画面中心にある白い星は実際には存在しないチャンネルですが、これを仮想的な root チャンネルとしています。その周りを第1階層にあたる9つのチャンネルが囲み、9つのチャンネルそれぞれから更に第2階層のチャンネルへ、そこから第3階層へ、......といったようにどんどん外側に伸びていきます。
球をクリックすると、その球に対応するチャンネルの詳細情報を閲覧することができます。また、そのチャンネルの子チャンネルが存在する場合は、子チャンネルに対応する救が展開されて表示されます。

2. 波紋・光の線
traQ上で誰かがチャンネルにメッセージを投稿すると、そのチャンネルとその親チャンネルに波紋が現れます。

ユーザーが見ているチャンネルを移動すると、前にいたチャンネルから今いるチャンネルへ光の線が飛びます。

3. 描画モード切替
画面に表示するチャンネル情報について ALL, NORMAL, ACTIVE の3種類のモードが存在し、自由に変更することができます。
- ALL
全てのチャンネルを強制的に表示します。非常に綺麗。 - NORMAL
3階層までのチャンネルすべてと一定以上盛り上がっているチャンネルを表示します。 - ACTIVE
一定以上盛り上がっているチャンネルのみを表示します。
技術的な話
フロントエンド
描画は Three.js 経由で WebGL を呼び出しています。(当初は WebGPU を使ってみようという話になっていたのですが、Three.js のサポートが部分的であったので WebGL に落ち着きました。)
フロントで一番負荷のかかる処理は 3D のチャンネルツリーの描画.....と思いきや、実は各チャンネルの座標の計算です。描画モードを「ALL」にするとわかるのですが、よい PC でもそれなりに時間がかかります。この計算には d3-force-3d というライブラリを用いているのですが、計算を端折りながらいい感じの見た目にするためにはかなり事細かにパラメータ等を調整する必要があり、最後の最後までかなり苦戦していました。(特に「ALL」モードは半ばあきらめていたのですが、26B の ktppp くんが 2 目の午後に素晴らしい実装を投げてくれました。ありがとう!)
また、NORMAL モードでは、クリックした頂点から子チャンネルに続く辺が伸びますが、愚直に展開すると周囲の頂点に干渉するため、頂点座標の再計算を行う必要があります。現在の配置情報を活用し、その差分を計算することで、滑らかな子チャンネルの展開を実現しています。
キーボードでの操作性や演出のタイミング等にもかなり注力しているので、良かったら深夜の作業のお供として遊んでいただければ幸いです。
バックエンド
traP内でバックエンド開発に広く使われている Go を用いて開発しました。
リアルタイムの情報配信にはSSEを用い、traQ との通信には OAuth2, REST API, WebSocket を使用しています。
QOSMOSのバックエンドに求められる機能は主に以下の4つです。
- traQ のチャンネル情報の取得
- traQ の WebSocket から投稿や閲覧状態の変化を受信
- 各チャンネルの盛り上がりスコアを管理
- フロントエンドへ SSE で情報配信
これらを分割して issue として設定し、それを解消していく形で開発を進めました。
開発に当たって意識した点は traQ のチャンネル数です。先述した通り traQ には約10年分の歴史があり、約 7000 個のチャンネルが存在します。毎回の通信で全てのチャンネルの情報を送受信しているとクライアントや traQ のサーバーへの負荷が大きくなる他、通信量を過大になってしまうと考えられたため、いくつかの工夫を行いました。
まず、定期的な同期時に盛り上がり度のスコアに大きな変動があったチャンネルや長い間同期されていないチャンネルを優先的に選択し、一度の同期では最大100件のチャンネル情報のみを送信するようにしました。これは、先述の基準に用いて各チャンネルの重みを計算し、その重み (を正規化した値) に従う確率分布から 100 回のサンプリングを行うことによって実現しています。各チャンネルの Viewers の polling 等についても、同様の確率分布から一定数を抽出するという手法をとっています。(CPU のタスクスケジューリングに着想を得ました。)
また、新しいクライアントが接続した際、最初に全チャンネル構造を送信する必要がありますが、同時に多くの人が接続した場合に初期データ送信がメモリを圧迫する恐れがあります。そこで、初期データの同時送信数にも制限をかけたうえでキューイングを行い、一度に負荷が集中しないようになっています。
全体として、リアルタイム性とサーバー負荷の軽減を両立するための情報の取捨選択に多くのリソースが割かれていました。この点については準備期間や開発期間中に活発に議論が行われ、結果としてアプリケーションを発表した際に traQ や QOSMOS が落ちるような事故もなく終われたので良かったかなと思います。
(嘘です。Polling のテストをしようとしてリミッターをかけ忘れた結果一度 traQ が落ちかけました。ごめんなさい。)
一方で、今回は約 1 日という短い開発期間に対して作りたいものの規模が比較的大きかったので、開発経験者を中心に AI エージェントを多用しました。その結果、コードがかなり散らかった状態になってしまった面があるかなと思います。Web アプリケーション開発に AI を一切使わないというのは今では考えられないのですが、ハッカソンとはいえど、教育的な観点でみればもう少し保守性を意識できるとよかったかな、なんて思ったりもします。
メンバーからひと言
uni_kakurenbo
少しだけ、ハッカソンと LLM 等の生成 AI によるコーディングエージェント (以下「AI」)について、個人的に考えたことを述べたいなと思います。(このブログはチームメンバーが分担して書いているので、本文での記述と差異があるかもしれませんが、ここに述べるのは私個人の考えです。)
皆さんもご存知の通り、今回の春ハッカソンではおそらく教育的な観点から自主的に AI の使用を控えていた班も多くあったそうです。私としても、曲がりなりにも新入生を持つ班のリーダーやはりこういった AI とどう向き合うか、という点についてはやはりあれこれと考えたわけですが、その結果、我々の班では AI も積極的に活用して作りたいものを完成させよう、という方向に舵を切ることになりました。
私は、春ハッカソンが存在する意義は、一言で表現するならば「新入生の子らの、今後のtraP での柔軟な活動のためのウォーミングアップとして、2 日間での集中的な開発に挑戦してもらう」ことにあるのではないかと解釈しています。そして、そのウォーミングアップには、その分野の開発に必要な基礎の習得だったり、チーム開発の流れの理解だったり、というのが挙げられると思うのですが、私は、「動機付け」という要素が非常に重要だと考えています。より平易に表現するならば、新入生のみんなにとにかく (チーム) 開発を楽しんで、今後のモチベーションを高めてほしい、ということです。
ここで、これは Web 分野に限った話ではないと思いますが、「コーディング」とは、いわゆる「開発」における唯一本質的な部分ではないはずです。そして、その「コーディング」を練習するのは、必ずしもこの春ハッカソンである必要はないと思うのです。新入生の二人が、ともにこの分野における開発の完全な初心者ではなくある程度の基礎知識があったことも加味すると、なおのこと、開発におけるコードを書く部分は AI に投げて、よい上位のタスクに専念するというのは、彼らにとっての経験としても、決して悪いものではないはずだと考えました。
とはいえ、やはり AI を多用したことに対する批判的な意見も散見されますし、自粛することが意味のないことだとは思いません。批判があること自体は理解できますが、ただ、単に「すごいものを完成させたいから」という動機だけがあったわけではないということはご理解いただきたいです。
ただ、一つだけ後悔があるとすれば、新入生のみんなを意識するあまり、同期で、Web 開発が初心者の Ida-ji へのサポートが十分にできなかった点でしょうか。当時は春ハッカソンは新入生のためのものだから、と割り切ろうとしてしまっていましたが、振り返ってみると、Ida-ji は我々の班で唯一の Web 初心者だったわけです。もう少し、手厚く補助をしてあげるべきだったかもしれません。それにもかかわらず、彼はこの作品に素晴らしい音楽といのちを与えてくれましたし、リーダーであった私以上に班の取りまとめ等に貢献してくれました。この場を借りて再度、謝罪と最大限の感謝を伝えたいです。
w4ma
review を中心にバックエンドを担当しました。
B1,B2 の後輩たちがチームの大多数を占める中、私は M1 という圧倒的年長者だったのでサポートに徹したりメンターっぽいことをやったりしようと最初から思っていました。が、ふたを開けてみると想像以上にメンバーが頼もしく、自分がサポートするまでもなくどんどん開発が進んでいくのが印象的でした。
また、今回は限られた開発期間の中でそこそこ規模の大きなものを作ろうとしていたのもあってCodexを使い倒して開発を進めたのですが、AIの進化に驚くとともに、今後どのように技術スキルを伸ばしていくのが良いか考えさせられる2日間になりました…。
研究などでなかなか時間が取れずメンバーには結構迷惑をかけてしまったのですが形になって良かったです、ありがとうございました。
ちなみに QOSMOS という名前は私が発案したのですが、ウケが良くてうれしかったです。
Eraxyso
バックエンドの実装やレビューを中心に担当しました。短い開発期間の中でも、AIをうまく活用することで実装や修正を素早く進められ、ハッカソンにおける開発効率を大きく高められると実感しました。
Ida-ji
BGM とフロントエンドとバックエンドを担当しました!
Web に関しては私は traP の講習会を走っただけの初心者だったのですが、新入生含め他の人がかなり Web 開発の経験があるらしくてびっくりしました。実装や技術設計などは他の人に任せて、準備期間ではアイデア出しだったりメンバーへの連絡を重点的にしました。本番期間では、初めに BGM を作り、その後に@uni_kakurenbo から軽い issue を貰ってはそれを消化するみたいな感じで開発に携わりました。とても学ぶことが多くて、モチベがめっちゃ上がりました。
特に @uni_kakurenbo がコードのバグなどを素早く発見してくれたりして本当に助かりました。ありがとう。
Web、頑張りたいネ…
aorinngo
初めての春ハッカソン楽しかった~!
初めてのハッカソン、初めてのチーム開発ということで、先輩方からいろいろなことを学ぶことができる経験になりました。
このアプリの原案は僕と @uni_kakurenbo が話しながら考えたのですが、思ってたよりも大規模なプロジェクトになってしまってびっくりしています。
自分はまだWeb開発にもチーム開発にも慣れていない部分が多いですが、今回のハッカソンを通して、アイデアが少しずつ画面上の形になっていく楽しさを強く感じれたなと思います。
ktppp
初のハッカソン、とても楽しかったです!
チーム開発をするのが初めてで、特にgitについて分からないことが多かったのですが、先輩方がたくさん教えてくれたおかげで微力ながら開発に参加することができました。
発表会の直前に全チャンネルを表示するモードを追加しようとしていたところ、なかなかうまくいかずかなりの時間(と codex のクレジット)を消費してしまったのですが、ギリギリで実装しきることができたのがとても嬉しかったです。
制作物の規模がかなり大きく、概要を聞いたときには正直少し不安がありました。しかし、それでも結果として上手くいったという成功体験ができたのがとてもよかったと思います。