frappe-core-cache について
Frappe 開発者向けのキャッシュとロックのリファレンススキルです。Redis、リクエスト単位、ドキュメントのキャッシュ API と無効化方法をまとめています。
主な機能
- 01キー値とハッシュの操作
- 02TTL とジェネレーター読み込み
- 03関数とドキュメントのキャッシュ
- 04リクエスト単位のキャッシュパターン
- 05Redis 分散ロック
- 06キャッシュ無効化の指針
活用シーン
- 01関数の繰り返し実行結果のキャッシュ
- 02単一リクエスト内でのデータ再利用
- 03重要な処理の同時実行防止
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-core-cacheインストール後、Claude Code で/frappe-core-cacheを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/.gitkeepanti-patterns.mdapi-reference.mdexamples.md詳細
Frappe キャッシュとロック
クイックリファレンス
| アクション | メソッド | 備考 |
|---|---|---|
| 値を設定 | frappe.cache.set_value(key, val) | TTL を省略可能 |
| 値を取得 | frappe.cache.get_value(key) | 値がない場合は None を返します |
| 取得または生成 | frappe.cache.get_value(key, generator=fn) | キャッシュミス時に fn() を呼び出します |
| 値を削除 | frappe.cache.delete_value(key) | 単一のキーまたはキーのリスト |
| パターンで削除 | frappe.cache.delete_keys(pattern) | ワイルドカード * によるマッチング |
| ハッシュに設定 | frappe.cache.hset(name, key, val) | Redis のハッシュフィールド |
| ハッシュから取得 | frappe.cache.hget(name, key) | 単一のハッシュフィールド |
| ハッシュをすべて取得 | frappe.cache.hgetall(name) | ハッシュ全体を dict として返します |
| ハッシュから削除 | frappe.cache.hdel(name, key) | ハッシュフィールドを削除 |
| ハッシュの存在を確認 | frappe.cache.hexists(name, key) | bool を返します |
| キャッシュ済みドキュメント | frappe.get_cached_doc(dt, dn) | キャッシュからドキュメント全体を取得 |
| ドキュメントキャッシュをクリア | frappe.clear_document_cache(dt, dn) | キャッシュ済みドキュメントを無効化 |
| デコレーターキャッシュ | @redis_cache | 関数の戻り値を自動的にキャッシュ |
| リクエストキャッシュ | frappe.local.cache | リクエストごとの dict(Redis ではありません) |
判断ツリー
What caching pattern do you need?
│
├─ Cache a function result automatically?
│ ├─ Pure function (same args → same result) → @redis_cache
│ └─ Need custom key/TTL → manual get_value/set_value
│
├─ Cache a document?
│ ├─ Read-only access → frappe.get_cached_doc()
│ └─ Need to invalidate → frappe.clear_document_cache()
│
├─ Cache structured data (multiple fields)?
│ └─ Redis hash → hset/hget/hgetall
│
├─ Per-request cache (avoid repeated DB calls in one request)?
│ └─ frappe.local.cache dict
│
├─ Prevent concurrent execution?
│ └─ Distributed lock → frappe.lock("resource_name")
│
└─ Invalidate cache?
├─ Single key → delete_value(key)
├─ Pattern → delete_keys("prefix*")
└─ All site cache → frappe.clear_cache()
文字列操作
設定と取得
# Set a value (persists until evicted or deleted)
frappe.cache.set_value("exchange_rate_USD", 1.08)
# Set with TTL (expires after N seconds)
frappe.cache.set_value("exchange_rate_USD", 1.08, expires_in_sec=3600)
# Get value (returns None if missing)
rate = frappe.cache.get_value("exchange_rate_USD")
# Get with generator (calls function on cache miss, stores result)
rate = frappe.cache.get_value(
"exchange_rate_USD",
generator=lambda: fetch_exchange_rate("USD"),
)
ユーザー単位の値
# Store per-user preference
frappe.cache.set_value("dashboard_layout", "compact", user="[email protected]")
# Retrieve for specific user
layout = frappe.cache.get_value("dashboard_layout", user="[email protected]")
削除
# Single key
frappe.cache.delete_value("exchange_rate_USD")
# Multiple keys
frappe.cache.delete_value(["exchange_rate_USD", "exchange_rate_EUR"])
# Pattern-based deletion (wildcard)
frappe.cache.delete_keys("exchange_rate*")
ハッシュ操作
関連するフィールドを単一のキーの下にまとめるには、ハッシュを使用します。
# Set hash fields
frappe.cache.hset("config|notifications", "email_enabled", True)
frappe.cache.hset("config|notifications", "sms_enabled", False)
frappe.cache.hset("config|notifications", "max_retries", 3)
# Get single field
email_on = frappe.cache.hget("config|notifications", "email_enabled")
# Get all fields as dict
config = frappe.cache.hgetall("config|notifications")
# {"email_enabled": True, "sms_enabled": False, "max_retries": 3}
# Delete field
frappe.cache.hdel("config|notifications", "sms_enabled")
# Check existence
exists = frappe.cache.hexists("config|notifications", "email_enabled")
生成関数を使ったハッシュ
# hget with generator — calls function on miss
value = frappe.cache.hget(
"user|permissions",
"[email protected]",
generator=lambda: compute_permissions("[email protected]"),
)
@redis_cache デコレーター
引数に基づいて関数の戻り値を自動的にキャッシュします。
from frappe.utils.caching import redis_cache
@redis_cache
def get_item_price(item_code, price_list):
"""Expensive query — cached automatically."""
return frappe.db.get_value("Item Price",
{"item_code": item_code, "price_list": price_list},
"price_list_rate",
)
# First call — hits database, stores in Redis
price = get_item_price("ITEM-001", "Standard Selling")
# Second call — returns from cache
price = get_item_price("ITEM-001", "Standard Selling")
# Clear all cached results for this function
get_item_price.clear_cache()
TTL を指定する場合
@redis_cache(ttl=300) # expires after 5 minutes
def get_exchange_rate(from_currency, to_currency):
return fetch_rate_from_api(from_currency, to_currency)
@redis_cache のルール:
- 引数がハッシュ可能(文字列、数値、タプル)であることを必ず確認してください。引数に dict やリストを渡さないでください。
- 基になるデータが変更されたときは、必ず
.clear_cache()を呼び出してください。 - 副作用のある関数には使用しないでください。キャッシュヒット時は関数が実行されません。
frappe.local.cache:リクエスト単位のキャッシュ
frappe.local.cache は、単一の HTTP リクエストの間だけ存在する通常の Python dict です。Redis には保存されません。
def get_user_settings():
"""Avoid repeated DB calls within a single request."""
if "user_settings" not in frappe.local.cache:
frappe.local.cache["user_settings"] = frappe.get_doc(
"User Settings", frappe.session.user
)
return frappe.local.cache["user_settings"]
次の場合は frappe.local.cache を使用します。
- 同じデータが 1 回のリクエスト中に複数回必要になる
- リクエストをまたいでデータを保持する必要がない
- Redis のオーバーヘッドをゼロにしたい
ドキュメントのキャッシュ
# Get cached document (read-only, no permission check)
settings = frappe.get_cached_doc("System Settings")
item = frappe.get_cached_doc("Item", "ITEM-001")
# Invalidate when document changes
frappe.clear_document_cache("Item", "ITEM-001")
# Cached single value
val = frappe.db.get_value("Item", "ITEM-001", "item_name", cache=True)
frappe.get_cached_doc() が返すドキュメントを変更しないでください。共有参照が返されるため、変更すると後続のすべての読み取りでキャッシュが破損します。
分散ロック
Redis ベースのロックを使用して、重要なセクションが同時に実行されるのを防ぎます。
# Context manager (recommended)
with frappe.lock("process_payroll"):
# Only one worker executes this block at a time
process_all_salary_slips()
# Lock auto-released on exit
# Manual lock/unlock
frappe.lock("inventory_sync")
try:
sync_inventory()
finally:
frappe.unlock("inventory_sync") # ALWAYS unlock in finally
ルール:
- ロックが確実に解放されるよう、必ず
with frappe.lock()(コンテキストマネージャー)を使用してください。 - ロックを数秒以上保持しないでください。長時間のロックはワーカーの枯渇を引き起こします。
- 衝突を避けるため、必ず内容が分かるロック名を使用してください。
キャッシュ無効化のパターン
パターン 1:TTL ベース(有効期間)
frappe.cache.set_value("dashboard_stats", compute_stats(), expires_in_sec=300)
最適な用途:多少古くても問題のないデータ(為替レート、ダッシュボードの集計値)。
パターン 2:イベントベースの無効化
# In hooks.py
doc_events = {
"Item Price": {
"on_update": "my_app.cache.invalidate_price_cache",
"on_trash": "my_app.cache.invalidate_price_cache",
}
}
# In my_app/cache.py
def invalidate_price_cache(doc, method):
frappe.cache.delete_keys("item_price*")
# Or clear specific function cache:
# get_item_price.clear_cache()
最適な用途:変更直後に必ず最新である必要があるデータ。
パターン 3:ハイブリッド(TTL + イベント)
@redis_cache(ttl=600)
def get_pricing_rules():
return frappe.get_all("Pricing Rule", fields=["*"])
# Event hook clears cache immediately on change
def on_pricing_rule_update(doc, method):
get_pricing_rules.clear_cache()
最適な用途:頻繁に読み取られ、更新は時々しか発生しないデータ。
一般的なキャッシュキー(内部)
| キーパターン | 内容 |
|---|---|
doctype::meta::{dt} | DocType メタデータ |
user_permissions::{user} | ユーザー権限キャッシュ |
bootinfo::{user} | ユーザーの起動情報 |
notifications::{user} | 通知件数 |
document_cache::{dt}::{dn} | キャッシュ済みドキュメント |
内部キャッシュキーに直接書き込まないでください。必ず、ドキュメントに記載された API メソッド(get_cached_doc、clear_document_cache など)を使用してください。
パフォーマンスガイドライン
- キャッシュする値が外部データに由来する場合は、必ず TTL を設定してください。TTL がないと、手動で無効化するか Redis が追い出すまで、古いデータが残り続けます。
- 大きなオブジェクト(>1 MB)はキャッシュしないでください。Redis は pickle シリアライズを使用するため、大きな値はシリアライズのオーバーヘッドとメモリ使用量を増加させます。
- 1 回のリクエスト内で複数回必要となるデータには、必ず
frappe.local.cacheを使用してください。Redis への往復を完全に回避できます。 - 通常の無効化手段として
frappe.clear_cache()を使用しないでください。サイトのキャッシュキーをすべて消去するため、キャッシュが空の状態からのパフォーマンス低下が発生します。 - 独自のキャッシュキーには、衝突を避けるため、必ずアプリ名をプレフィックスとして付けてください(例:
myapp|exchange_rate)。
Redis の設定
デフォルト設定:{bench}/config/redis_cache.conf
| 設定 | デフォルト | 説明 |
|---|---|---|
| Port | 13000 | Redis キャッシュポート |
| Bind | 127.0.0.1 | リッスンアドレス |
| maxmemory-policy | allkeys-lru | 追い出しポリシー |
| maxmemory | 256mb | 最大メモリ(調整可能) |
キーの名前空間
すべてのキャッシュキーには、Frappe によってサイト名が自動的にプレフィックスとして付加されます。
# You write:
frappe.cache.set_value("my_key", "value")
# Redis stores:
# "mysite.localhost|my_key"
frappe.cache.make_key(key, user, shared) がプレフィックスを付加します。shared=True パラメーターを指定すると、サイト間で共有するキーのためにサイトのプレフィックスが削除されます(まれなユースケースです)。
バージョンごとの違い
| 機能 | v14 | v15 | v16 |
|---|---|---|---|
frappe.cache.set_value | 利用可能 | 利用可能 | 利用可能 |
@redis_cache | 利用不可 | 利用可能 | 利用可能 |
@redis_cache(ttl=) | 利用不可 | 利用可能 | 利用可能 |
frappe.lock コンテキストマネージャー | 利用可能 | 利用可能 | 利用可能 |
frappe.local.cache | 利用可能 | 利用可能 | 利用可能 |
ジェネレーターを使った hget | 利用可能 | 利用可能 | 利用可能 |
関連項目
- references/examples.md — キャッシュの実装パターン
- references/anti-patterns.md — よくあるキャッシュの誤り
- references/api-reference.md — API シグネチャの完全な一覧
frappe-core-database— キャッシュのメリットがあるデータベースクエリfrappe-core-permissions— ユーザー権限のキャッシュ