記事の閲覧数を安直なカウンターで増やす実装をやめるべき理由と代替策
目次
はじめに
ウェブシステムを構築する際、記事ごとの閲覧数を表示する機能を実装することがあります。 また、この閲覧数をもとにして「人気の記事ランキング」などのコンテンツが必要になることがあります。 これらの閲覧数を管理する方法として、記事テーブルに「閲覧数」カラムを設け、記事が閲覧されるたびにその値を1増やす実装が考えられます。
しかし、現代のウェブシステムではこの方法を採用すると、さまざまな問題が発生する可能性があります。本記事では、その問題点を明らかにするとともに、より適切な閲覧数の管理方法について解説します。
問題点
閲覧数をデータベースで管理する際の根本的な問題は、不特定多数のユーザーによる並行アクセスが発生する中で、同じカラムを適切に更新・管理しなければならないことです。単純な実装では、この問題を解決するためにトランザクションを利用することになります。しかし、トランザクションを用いたカラムの更新には以下のような問題が生じます。
- トランザクションを発行して1つのカラムを更新する場合、更新中のトランザクションが完了するまで他の処理は待機状態となります。そのため、処理時間が長くなり、システム全体のパフォーマンスが低下します。
- 1アクセスごとにデータベースへの書き込みが発生するため、負荷が大きくなります。その結果、負荷に耐えるためにより高性能なデータベースを用意する必要が出てくる可能性があります。
- 閲覧数が増えるほどデータベースへの書き込み回数も増加し、スケーラビリティの観点から問題が発生することがあります。
さらに、閲覧数を基にランキングを生成する場合、ウェブサーバーのキャッシュやCDNを導入するとアクセスが直接ウェブシステムに到達しないため、正確な閲覧数を取得できなくなる可能性があります。
これらの問題を考慮すると、小規模なシステムではリアルタイムに単一カラムへトランザクションを用いて書き込む方法でも大きな問題にならないことがありますが、システムの規模が拡大すると適切な方法とは言えなくなります。
問題の解決策
この問題を解決する方法はいくつかありますが、その前に重要なのは「どの程度正確な閲覧数を知る必要があるのか」という要件を明確にすることです。多くの場合、閲覧数の反映に多少の遅延があっても問題になりません。 例えば、ランキングの出力に利用するのであれば、ランキング計算時に正確な数値が得られれば十分です。
そのため、以下のような実装方針の中から、要件に見合う方法を採用することで、この問題を解決できます。
バッチ処理による集計
この方法ではすべてのアクセスをリアルタイムにカウントせずに、まずは記録のみを行います。その後、一定間隔でバッチ処理を実行して一定期間のアクセス数を集計して記事の閲覧数を更新します。
アクセスの記録先としては、以下のような選択肢があります。
- RDB / Redis / NoSQL に専用のアクセステーブルを用意し、各アクセスを記録する
- Web サーバーのアクセスログ(Apache/Nginx のログファイル)を活用し、ツールで集計する
この方式の特徴は、集計の頻度を調整することで負荷をコントロールできる点 にあります。頻度を減らせばデータベースの負荷を軽減でき、頻度を増やせばよりリアルタイムに近い更新が可能です。
一方で、バッチ処理を用いるため、リアルタイム性には欠ける というデメリットがあります。特に更新頻度を低く設定した場合、閲覧数が反映されるまでに時間がかかることが多くなります。
なお、CDNやウェブサーバーのキャッシュが有効な場合、アクセスログに記録されないケースがある ため注意が必要です。そのような場合は、キャッシュを無効化した専用のAPIを用意し、記事閲覧時にJavaScriptでAPIを呼び出してシステム側にアクセスを記録する方法が考えられます。
メッセージキューを用いたバックグラウンドジョブによる遅延更新
この方法では、記事の閲覧時に直接閲覧数を更新せず、閲覧数を更新するジョブをメッセージキューに送信します。バックグラウンドジョブはキューに蓄積されたメッセージを処理し、適切なタイミングで閲覧数を更新します。
この方式の特徴は、記事の閲覧時にトランザクションを発生させずに処理を完結できるため、並列処理のレスポンスが向上する点にあります。また、閲覧数を更新するワーカーの数を制限すればデータベースへの更新トランザクションの最大数を制御でき、負荷を想定の範囲内に抑えることが可能です。
一方で、アクセス数が増加するとキューのメッセージ数が増えて閲覧数の反映が遅れる可能性があるという欠点があります。また、メッセージキューを導入していないシステムではキュー管理の運用負担が増える というデメリットもあります。しかし、システム内に適切なバックグラウンド処理基盤が存在する場合は、比較的シンプルな実装で導入できるため、有効な手法となります。
こちらの方法を採用する場合でも、 CDNやウェブサーバーのキャッシュを利用する場合はJavaScript経由でAPIを呼び出す方法が考えられます。ただしAPIを通じて直接データベースを更新すると、トランザクションの待ち時間が長くなることで正しく閲覧数が記録されない可能性があります。API化を行った場合でもメッセージキューを活用してアクセス処理を早期に完了させつつ、閲覧数の計算を適切に処理するリソースを確保することが重要 です。
Google Analytics などのアクセスログ分析ツールを利用する
閲覧数の管理を独自に開発せず、Google Analytics などの外部ツールを活用する方法 です。適切な設定を行えば、記事ごとのアクセス数の確認やランキングの作成が可能になり、API経由で視聴数を取得することもできます。また、これらのツールにはスパム対策やリロード対策が組み込まれているため、不正なカウントを除外した正確なデータを取得しやすい というメリットがあります。
この方法の最大の利点は、ウェブサイト運営の多くのユースケースを、システム開発を一切せずに満たせる点 にあります。ただし、ツールの使い方を習得する必要があるほか、以下のような制約もあります。
- データの反映に時間がかかる(リアルタイム更新には対応していないことが多い)
- カスタマイズ性が低い(ツールが提供していない独自の数値を取得できない)
- APIの利用制限(リクエスト回数の制限がある)に対応した利用方法での実装が必要
そのため、要件がこちらで解決できる場合は、この手法を採用するとよいですが、細かい要件がある場合には別の手法と組み合わせる必要があるでしょう。
まとめ
記事の閲覧数をリアルタイムにカウントする実装は一見シンプルに思えますが、データベースの負荷増大やトランザクションの競合、キャッシュの影響 など、さまざまな課題を引き起こします。そのため、要件に応じた適切な手法を選択することが重要です。
本記事では、閲覧数を効率的に管理するための3つの手法 を紹介しました。選択すべき方法は、リアルタイム性の必要性、システムの負荷、運用コスト などによって異なります。
- リアルタイム性を重視するなら → メッセージキュー方式やRedisなどのインメモリデータストアを併用
- 負荷を抑えつつ正確なカウントを取得したいなら → バッチ処理
- シンプルな運用を求めるなら → Google Analytics などの外部ツール
重要なのは、何も考えずにトランザクションを用いた方法を採用するのではなく、システムの特性に応じた最適な手法を選択すること です。要件をしっかりと見極め、適切な閲覧数管理の実装を行いましょう。