0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【リーダブルコード】初心者エンジニアが読み解く「分/時間カウンタ」を設計・実装する際のヒント - 第15章

Last updated at Posted at 2025-01-31

こんばんは。マル太です!

前回は、リーダブルコードの第14章「テストと読みやすさ」についてまとめました。

今回は、第15章「「分/時間カウンタ」を設計・実装する」について、まとめていきたいと思います。

どんな「カウンタ」を作るの?

この章では、過去1分間、1時間のWebサーバーへのリクエスト数をカウントする「分/時間カウンタ」を作成します。

「分/時間カウンタ」とは、以下のようなものです。

・直近1分間と1時間の転送バイト数を把握したい
・高パフォーマンスなアプリケーションにも耐えうる設計であること

問題を整理する

まず、「分/時間カウンタ」を作成するにあたって、どんな問題があるかを整理します。

1.パフォーマンス

高頻度でリクエスト数をカウントする必要があるため、処理速度が重要になります。

2.正確性

過去1分間、1時間のカウント数を正確に集計する必要があります。

3.保守性

コードが複雑になると、後々の修正や機能追加が難しくなります。

解決策を検討する

問題を整理した上で、解決策を検討していきます。

試案1:素朴な解決策

Python
class Counter:
    def __init__(self):
        self.counts = []

    def add(self, value):
        self.counts.append((time.time(), value))

    def get_count(self, seconds):
        now = time.time()
        total = 0
        for t, v in self.counts:
            if now - t <= seconds:
                total += v
        return total

このコードは、リクエストが来るたびにエントリを追加し、get_count()が呼ばれるたびに、エントリを先頭から走査してカウント数を計算します。

この方法だと、エントリ数が増えるほど計算量が増えてしまい、パフォーマンスが悪いです。

試案2:ベルトコンベヤー設計
試案1のパフォーマンス問題を解決するアイデアとして、「ベルトコンベヤー設計」があります。

ベルトコンベヤー設計では、リングバッファと呼ばれる特別なデータ構造を使用し、固定長の配列とインデックスにて、データを順番に格納していく仕組みです。

データが追加されるたびに、インデックスが更新され、古いデータが新しいデータで上書きされます。

Python
class Counter:
    def __init__(self, size):
        self.counts = [0] * size
        self.times = [0] * size
        self.size = size

    def add(self, value):
        index = int(time.time()) % self.size
        self.counts[index] = value
        self.times[index] = time.time()

    def get_count(self, seconds):
        now = time.time()
        total = 0
        for i in range(self.size):
            if now - self.times[i] <= seconds:
                total += self.counts[i]
        return total

この設計にすることで、get_count()の計算量を大幅に削減できます。

しかし、この設計にも問題点があります。

それは1時間よりも長い間隔の集計に対応できないことです。

試案3:時間バケツの設計
試案2での課題を解決するために、「時間バケツ設計」が紹介されています。

これは、固定長の時間間隔(バケツ)で集計を行う設計です。

この設計では、1分間、1時間といった集計間隔をあとから追加できます。

Python
class BucketCounter:
    def __init__(self, bucket_size):
        self.buckets = []
        self.bucket_size = bucket_size

    def add(self, value):
        now = time.time()
        self.buckets.append((now, value))
        self._remove_old_buckets(now)

    def get_count(self, seconds):
        now = time.time()
        total = 0
        for t, v in self.buckets:
            if now - t <= seconds:
                total += v
        return total

    def _remove_old_buckets(self, now):
        self.buckets = [(t, v) for t, v in self.buckets if now - t <= self.bucket_size]

※時間バケツ設計の解説
ここでは、バケツと呼ばれる固定長の時間間隔で区切られたデータ構造でカウントを保持します。
各バケツには、開始時刻とその時間帯のカウント数が格納されます。

上記時間バケツ設計の良い点

・柔軟性
時間バケツ設計では、集計間隔を後から自由に変更できます。
例えば、1分間、1時間だけでなく、30秒間や12時間といった間隔でも集計できます。

・パフォーマンス
試案1のように毎回全てのデータを走査するのではなく、バケツ単位で集計を行うため、データ量が増えてもパフォーマンス劣化が少ないです。

・保守性
バケツの管理や集計処理がクラスとしてまとまっているため、コードの見通しが良く、保守がしやすいです。



以上の点から、試案1から試案3までの内、最終的に
「時間バケツ設計」
が一番実装するのに相応しいという結論になりました。

まとめ

今回は、過去1分間、1時間のWebサーバーへのリクエスト数をカウントする「分/時間カウンタ」を作成しました。

本章では、最終的に「時間バケツ設計」を採用しましたが、
重要なことは、「問題を正しく認識し、様々な解決策を試行錯誤すること」です。

また、それぞれの解決策にはメリット・デメリットがあるため、「要件に合った解決策を選択すること」も重要です。

このプロセスを辿ることで、より良いコードを作成できることを学びました。

最後に

最後までお読みいただきありがとうございました。

「リーダブルコード」解説ブログは、今回で最終回となります。

このブログでのアウトプットを通して、「リーダブルコード」への理解を深めることができたかなと思います。

この経験を活かして、今後も機会があれば、様々な技術情報の発信をしていきたいと考えています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?