エキサイトの武藤です。
エキサイトホールディングス Advent Calendar 2022の14日目の記事です。
https://qiita.com/advent-calendar/2022/excite-hd
担当しているサービスのランキング機能のリビルドを行いました。 実現するにあたって、考慮したところを紹介します。
どのようなランキングか?
記事メディアを前提として、ランキングの仕様を説明します。 下記の仕様を想定します。
- 記事ごとのPVやいいね数を集計したランキング
- 日次更新
以前のランキング機能のシステム概要
以前のランキング機能のシステム概要について説明します。
まず、バッチが集計を行い、ランキングデータを生成します。 このとき、記事のタイトルや本文といった表示用のデータと合わせてランキングを生成します。
生成したランキングデータは、ファイルに書き出され、アプリケーションが実行しているサーバへ配布されます。 アプリケーションは、ファイルを読み込んで、ランキングを表示します。
バッチは1日1回実行します。翌日のランキング更新では、前日と同名のファイルに上書きされます。アプリケーションは、配布し直されたファイルを同名で参照します。
この仕様によって、以下のデメリットがありました。
- アプリケーションごとにファイルが配布されることによるデータの点在化
- ファイルはランキング生成時のみに更新されるため、コンテンツのデータが更新されたときに、追従できない (手動でバッチを実行する等の運用が発生する)
- ランキングのデータが蓄積されない
リビルドした仕様
上記のデメリット解消のため、システムフローの修正と新たにテーブル設計をしました。
システムフロー
特段、真新しいものではなく、シンプルな仕様を考えました。
DB管理にすることで、データが一元化されます。日付ごとに保存されるので、ランキングのデータが蓄積できます。 APIのみがDBにアクセスするようにし、データフローをシンプルにします。
ランキングの日付や順位は記事とは別のテーブルで管理します。必要に応じてコンテンツのテーブルをJOINして取得します。これによって、最新状態のコンテンツを取得できます。
テーブル設計
続いて、テーブルの設計です。 記事とランキングテーブルです。
DBはMySQLを前提とします。
create table `article` ( `article_id` varchar(255) not null, `title` varchar(11) not null, `body` text not null, `insert_date` datetime not null default current_timestamp, primary key (`article_id`) ) ENGINE=InnoDB default CHARSET=utf8mb4; create table `article_ranking_pv` ( `ranking_date` date not null, `article_id` varchar(255) not null, `pv` int(11) not null, `insert_date` datetime not null default current_timestamp, primary key (`ranking_date`, `article_id`), KEY `ranking_date` (`ranking_date`,`pv`) ) ENGINE=InnoDB default CHARSET=utf8mb4;
artilce_ranking_pv
では、ランキング日時と記事ID、集計時点のPVを持ちます。
次に、ランキングと記事を取得するSQLのEXPLAIN です。
explain select ranking_date, article_id, title, pv, ( select count(r2.pv) from article_ranking_pv as r2 where article_ranking_pv.pv < r2.pv and article_ranking_pv.ranking_date = r2.ranking_date ) + 1 as ranking_order from article_ranking_pv inner join article using (article_id) where ranking_date = '2022-12-08' order by pv desc;
サブクエリでスコア(pv)の比較を行い、ランク順位 (ranking_order) を算出します。 スコアの降順にすることでランク順で記事と順位を取得できます。
DBのミドルウェアによっては、RANK関数が実装されているものもあります。MySQLではRANK関数はありませんので上記のSQLとなりました。
終わりに
今回は前提とした仕様をもとにこちらの設計となりました。 ランキングの仕様によっては設計も変わると思います。
長年運用されているシステムですと、標準的とは言えないレガシーな仕組みのものがたまにあります。 今回はシンプルな方針でリビルドすることで対応しました。1つずつ技術負債を解消していければと思います。
こちらの記事が参考になれば幸いです。