Pythonで有効期限(TTL)付きキャッシュを実装する (1)
概要
今回は、 Python で有効期限(Time-To-Live, TTL)を持つキャッシュ機構を実装する方法を紹介します。
このキャッシュ機構は、以下のような用途で役に立ちます。
- インターネットに接続してデータを取得する関数であるが、そのデータが一定期間変化しない場合。
(天気予報のデータなど) - 頻繁に実行する関数であるが、リアルタイムのデータを必要としない場合。
関数にキャッシュ機能を付ける
まず初めに、Python の関数にキャッシュ機能を付ける方法を紹介します。
これは、functools
に含まれる lru_cache
デコレータを使用して実現できます(公式ドキュメント)。
cache
デコレータが lru_cache(maxsize=None)
と同じ動作をします。-
フィボナッチ数を求める関数は以下のように実装できますが、このままではとても遅いです。
def fibonacci(n): """n番目のフィボナッチ数を出力する.""" if n == 0: return 0 elif n == 1: return 1 else: return fibonacci(n - 2) + fibonacci(n - 1)
-
例
%time fibonacci(40) # CPU times: user 22.1 s, sys: 0 ns, total: 22.1 s # Wall time: 22.1 s # 102334155
-
-
lru_cache
デコレータを用いて関数の出力をキャッシュすることで、計算が高速になります。
(このことを「メモ化する」とも呼ばれます。)from functools import lru_cache @lru_cache(maxsize=None) def fibonacci(n): """n番目のフィボナッチ数を出力する.""" if n == 0: return 0 elif n == 1: return 1 else: return fibonacci(n - 2) + fibonacci(n - 1)
-
例
%time fibonacci(40) # CPU times: user 52 µs, sys: 1 µs, total: 53 µs # Wall time: 58.9 µs # 102334155
-
有効期限(TTL)付きキャッシュを実現する
それでは、有効期限付きのキャッシュを実現するにはどうすればよいでしょうか?
それは、指定した間隔で返り値が変化する関数とダミー引数の組み合わせで実現できます。
以下で具体例を用いて説明します。
(このアイディアは stackoverflowの回答 によるものです)
-
以下のような指定されたURLのコンテンツを取得する関数があるとします。
@lru_cache(maxsize=None) def get_content(url): """URLのコンテンツを取得する.""" ...
-
この関数に新たなダミー引数
dummy
を追加します。@lru_cache(maxsize=None) def get_content(url, dummy): """URLのコンテンツを取得する.""" ...
-
このダミー引数に、指定した間隔で値が変化する関数
get_ttl_hash
の返り値を入力します。hash = get_ttl_hash() get_content(url, dummy=hash)
-
こうすることで、一定期間中は関数の引数が同じ値になるのでキャッシュが使用され、
期限を過ぎると引数の値が変わるためキャッシュが使用されず、
再度関数の返り値が計算されるようになります。
指定した間隔で返り値が変化する関数は次のように実装できます。
import datetime
def get_ttl_hash(seconds=3600):
"""TTLキャッシュ用のハッシュ値を計算する。
Args:
seconds (int, optional): 有効期限(秒). Defaults to 3600.
Returns:
int: ハッシュ値
"""
utime = datetime.datetime.now().timestamp()
return round(utime / (seconds + 1))
まとめ
これでTTLキャッシュ機能を持つ関数を作ることができました。
ただ、毎回ダミー引数を用意してそこに get_ttl_hash
を代入するのは面倒なので、
次回は自動でそれを実現してくれるデコレータを実装します。