ばとらの部屋
Maximum AtCoder Leaderboardを作りました
作成日 2024年10月30日 / 最終更新日 2025年1月29日
サイトは こちら から見ることができます。
※現在サイトに欠陥があったため非公開
ソースコードはGitHubリポジトリから見ることができます。
埼玉大学プログラミングサークル「Maximum」では、活動の1つとして競技プログラミングがあり、部内メンバーはAtCoderのコンテストに積極的に参加しています。
そこで、メンバーにもっと競争力を持ってもらうために、部内の月間ランキングなんてあったらいいな~と思い開発に取り掛かりました。
フロントエンド
バックエンド
デプロイ
実装方法は以下の図のようになります。どうやらAtCoderは、各ユーザーのリンクの末尾に/history/json
を追加することで、これまでに参加したコンテストの詳細やレートの変動などの情報がjson形式で受け取ることができるので、これをプロキシサーバーを介して(CORSの都合上)fetchをすることでAPIとして過去の成績を取得することができます。
フロントエンドの処理は省きます
適切なリクエストを送信するために、フロントから送られてきたユーザーのリストusers
に対してエラーハンドリングを行います。
if (!users) return c.text("Bad Request", 400);
if (!Array.isArray(users)) return c.text("Bad Request", 400);
if (!users.every((user) => typeof user === "string"))
return c.text("Bad Request", 400);
users
がundefinedの場合users
が配列ではない場合このような場合にはBad Request
を返却することにします。
各ユーザーに対する処理の全体は以下のようになります。
const userData = [];
for (const user of users) {
const cache = await c.env.USER_CACHE.get(user);
if (cache) {
const { data, timestamp } = JSON.parse(cache);
if (Date.now() - timestamp < 1000 * 60 * 60) {
userData.push(data);
continue;
}
}
const res = await fetch(`https://atcoder.jp/users/${user}/history/json`);
const data = await res.json();
userData.push(data);
await c.env.USER_CACHE.put(user, JSON.stringify({
data,
timestamp: Date.now(),
}));
await new Promise((resolve) => setTimeout(resolve, 1000));
}
各リクエストに対して1000msの遅延をしています。
Cloudflare Workersのキャッシュ先としてKVへの保存を選びました。詳しくは以下のサイトをご覧ください。
動的コンテツをエッジのKVにキャッシュする - ゆーすけべー日記
Web APIのパフォーマンス向上に「Dynamic Content Storing = DCS」という戦略を考えている。 Web APIに限らず、サーバーサイドで動的に作られるコンテンツ全てへ適応できるものである。 本番環境で運用したわけではない
https://yusukebe.com/posts/2022/dcs/
ユーザーに対してキャッシュが存在するかをKV USER_CACHE
を確認します。
const cache = await c.env.USER_CACHE.get(user);
キャッシュが存在するときは、有効期限を確認して期限内であればfetchをせずにキャッシュからデータを取ってきてそのままリストuserData
へ挿入します。
if (cache) {
const { data, timestamp } = JSON.parse(cache);
if (Date.now() - timestamp < 1000 * 60 * 60) {
userData.push(data);
continue;
}
}
キャッシュが存在しない或いは有効期限が切れているときは、KVに新たにキャッシュを保存します。
await c.env.USER_CACHE.put(user, JSON.stringify({
data,
timestamp: Date.now(),
}));
このようにユーザーに対してデータとキャッシュの有効期限を持たせます。