データ・キャッシュ

データ・キャッシュは PHP の変数をキャッシュに格納し、あとでキャッシュからそれらを読み込みます。 これは、クエリ・キャッシュページ・キャッシュ など、 より高度なキャッシュ機能の基礎でもあります。

以下のコードが、データ・キャッシュの典型的な利用パターンです。ここで、$cacheキャッシュ・コンポーネント を指しています。

// キャッシュから $data を取得しようと試みる
$data = $cache->get($key);

if ($data === false) {
    // キャッシュの中に $data が見つからない場合は一から作る
    $data = $this->calculateSomething();

    // $data をキャッシュに格納して、次回はそれを取得できるようにする
    $cache->set($key, $data);
}

// この時点で $data は利用可能になっている

バージョン 2.0.11 以降は、キャッシュ・コンポーネント が提供する getOrSet() メソッドを使って、 データを取得、計算、保存するためのコードを単純化することが出来ます。 次に示すコードは、上述の例と全く同じことをするものです。

$data = $cache->getOrSet($key, function () {
    return $this->calculateSomething();
});

キャッシュが $key と関連づけられたデータを保持している場合は、キャッシュされている値が返されます。 そうでない場合は、渡された無名関数が実行されて値が計算され、その値がキャッシュされるとともに返されます。

無名関数が外部のスコープの何らかのデータを必要とする場合は、それを use 文を使って渡すことが出来ます。 例えば、

$user_id = 42;
$data = $cache->getOrSet($key, function () use ($user_id) {
    return $this->calculateSomething($user_id);
});

Note: getOrSet() メソッドは、有効期限と依存もサポートしています。 詳しくは キャッシュの有効期限キャッシュの依存 を参照してください。

キャッシュ・コンポーネント

データ・キャッシュはメモリ、ファイル、データベースなどさまざまなキャッシュ・ストレージを表す、 いわゆる キャッシュ・コンポーネント に依存しています。

キャッシュ・コンポーネントは通常グローバルに設定しアクセスできるように アプリケーション・コンポーネント として登録されます。 以下のコードは、二台のキャッシュ・サーバを用いる Memcached を使うように cache アプリケーション・コンポーネントを構成する方法を示すものです。

'components' => [
    'cache' => [
        'class' => 'yii\caching\MemCache',
        'servers' => [
            [
                'host' => 'server1',
                'port' => 11211,
                'weight' => 100,
            ],
            [
                'host' => 'server2',
                'port' => 11211,
                'weight' => 50,
            ],
        ],
    ],
],

こうすると、上記のキャッシュ・コンポーネントに Yii::$app->cache という式でアクセスできるようになります。

すべてのキャッシュ・コンポーネントは同じ API をサポートしているので、アプリケーションの構成情報で設定しなおせば、 キャッシュを使っているコードに変更を加えることなく、異なるキャッシュ・コンポーネントに入れ替えることができます。 例えば上記の構成を APC キャッシュ を使うように変更する場合は以下のようにします:

'components' => [
    'cache' => [
        'class' => 'yii\caching\ApcCache',
    ],
],

Tip: キャッシュ・コンポーネントは複数登録することができます。cache という名前のコンポーネントが、 キャッシュに依存する多数のクラスによってデフォルトで使用されます (例えば yii\web\UrlManager など) 。

サポートされているキャッシュ・ストレージ

Yii はさまざまなキャッシュ・ストレージをサポートしています。以下がその概要です:

  • yii\caching\ApcCache: PHP の APC 拡張モジュールを使用します。 集中型の重厚なアプリケーションのキャッシュを扱うときには最速の一つとして考えることができます (例えば、サーバが一台で、専用のロード・バランサを持っていない、などの場合)。
  • このキャッシュを使用するには yii\caching\DbCache::$cacheTable で指定したテーブルを作成する必要があります。

  • ArrayCache のパフォーマンスを高めるために、yii\caching\ArrayCache::$serializerfalse に設定して、 保存するデータのシリアライズを無効にすることが出来ます。 .

  • このコンポーネントの目的は、キャッシュの可用性をチェックする必要があるコードを簡略化することです。 たとえば、開発中やサーバに実際のキャッシュ・サポートがない場合でも、 このキャッシュを使用するようにキャッシュ・コンポーネントを構成することができます。 そして、実際のキャッシュ・サポートが有効になったときに、対応するキャッシュ・コンポーネントに切替えて使用します。 どちらの場合も、Yii::$app->cachenull かも知れないと心配せずに、 データを取得するために同じコード Yii::$app->cache->get($key) を使用できます。

  • これはページ・コンテントなど大きなかたまりのデータに特に適しています。

  • yii\caching\MemCache: PHP の MemcacheMemcached 拡張モジュールを使用します。 分散型のアプリケーションでキャッシュを扱うときには最速の一つとして考えることができます (例えば、複数台のサーバで、ロード・バランサがある、などの場合) 。
  • yii\redis\Cache: Redis の key-value ストアに基づいてキャッシュ・コンポーネントを実装しています。 (Redis の バージョン 2.6.12 以降が必要とされます) 。
  • yii\caching\WinCache: PHP の WinCache エクステンションを使用します。 (参照リンク)
  • yii\caching\XCache (非推奨): PHP の XCache 拡張モジュールを使用します。
  • yii\caching\ZendDataCache (非推奨): キャッシュ・メディアとして Zend Data Cache を使用します。

Tip: 同じアプリケーション内で異なるキャッシュを使用することもできます。 一般的なやり方として、小さくとも常に使用されるデータ (例えば、統計データ) を格納する場合はメモリ・ベースのキャッシュ・ストレージを使用し、 大きくて使用頻度の低いデータ (例えば、ページ・コンテント) を格納する場合はファイル・ベース、またはデータベースのキャッシュ・ストレージを使用します 。

キャッシュ API

すべてのキャッシュ・コンポーネントが同じ基底クラス yii\caching\Cache を持っているので、以下の API をサポートしています。

  • データが見つからないか、もしくは有効期限が切れたり無効になったりしている場合は false を返します。

  • 取得できなかった場合は、渡されたコールバック関数を実行し、関数の返り値をそのキーでキャッシュに保存し、そしてその値を返します。

  • 各データはキーによって識別されます。もしキャッシュ内にキーがすでに存在する場合はスキップされます。

Note: boolean 型の false を直接にキャッシュしてはいけません。 なぜなら、get() メソッドは、データがキャッシュ内に見つからないことを示すために戻り値として false を使用しているからです。 代りに、配列内に false を置いてキャッシュすることによって、この問題を回避して下さい。

キャッシュされたデータを取得する際に発生するオーバーヘッドを減らすために、MemCache, APC などのいくつかのキャッシュ・ストレージは、 バッチ・モードで複数のキャッシュされた値を取得することをサポートしています。 multiGet()multiAdd() などの API はこの機能を十分に引き出すために提供されています。 基礎となるキャッシュ・ストレージがこの機能をサポートしていない場合には、シミュレートされます。

yii\caching\CacheArrayAccess インタフェイスを継承しているので、キャッシュ・コンポーネントは配列のように扱うことができます。 以下はいくつかの例です:

$cache['var1'] = $value1;  // $cache->set('var1', $value1); と同等
$value2 = $cache['var2'];  // $value2 = $cache->get('var2'); と同等

キャッシュのキー

キャッシュに格納される各データは、一意のキーによって識別されます。 キャッシュ内にデータを格納するときはキーを指定する必要があります。 また、あとでキャッシュからデータを取得するときは、それに対応するキーを提供しなければなりません。

キャッシュのキーとしては、文字列または任意の値を使用することができます。 キーが文字列でない場合は、自動的に文字列にシリアライズされます。

キャッシュのキーを定義する一般的なやり方として、全ての決定要素を配列の形で含めるという方方があります。 例えば yii\db\Schema はデータベース・テーブルのスキーマ情報を以下のキーを使用してキャッシュしています。

[
    __CLASS__,              // スキーマ・クラス名
    $this->db->dsn,         // データベース接続のデータ・ソース名
    $this->db->username,    // データベース接続のログイン・ユーザ
    $name,                  // テーブル名
];

見ての通り、キーは一意にデータベースのテーブルを指定するために必要なすべての情報を含んでいます。

Note: multiSet() または multiAdd() によってキャッシュに保存される値が持つことが出来るのは、 文字列または整数のキーだけです。それらより複雑なキーを設定する必要がある場合は、 set() または add() によって、値を個別に保存してください。

同じキャッシュ・ストレージが異なるアプリケーションによって使用されているときは、 キャッシュのキーの競合を避けるために、各アプリケーションではユニークなキーの接頭辞を指定する必要があります。 これは yii\caching\Cache::$keyPrefix プロパティを設定することで出来ます。例えば、アプリケーション構成情報で以下のように書くことができます:

'components' => [
    'cache' => [
        'class' => 'yii\caching\ApcCache',
        'keyPrefix' => 'myapp',       // ユニークなキャッシュのキーの接頭辞
    ],
],

相互運用性を確保するために、英数字のみを使用する必要があります。

キャッシュの有効期限

キャッシュに格納されたデータは、何らかのキャッシュ・ポリシー (例えば、キャッシュ・スペースがいっぱいになったときは最も古いデータが削除される、など) の執行によって除去されない限り、永遠に残り続けます。 この動作を変えるために set() を呼んでデータ・アイテムを保存するときに、有効期限パラメータを指定することができます。 このパラメータは、データ・アイテムが何秒間有効なものとしてキャッシュ内に残ることが出来るかを示します。 get() でデータ・アイテムを取得する際に有効期限が切れていた場合は、 キャッシュ内にデータが見つからなかったことを示す false が返されます。例えば、

// 最大で 45 秒間キャッシュ内にデータを保持する
$cache->set($key, $data, 45);

sleep(50);

$data = $cache->get($key);
if ($data === false) {
    // $data は有効期限が切れているか、またはキャッシュ内に見つからない
}

バージョン 2.0.11 以降は、デフォルトの無限の有効期限に替えて特定の有効期限を指定したい場合には、 キャッシュ・コンポーネントの構成で defaultDuration の値を指定することが出来ます。 これによって、特定の duration パラメータを毎回 set() に渡さなくてもよくなります。

キャッシュの依存

有効期限の設定に加えて、キャッシュされたデータは、いわゆる キャッシュの依存 (キャッシュが依存している事物) の変化によって無効にすることもできます。 例えば yii\caching\FileDependency は、キャッシュがファイルの更新時刻に依存していることを表しています。 この依存が変化したときは、対応するファイルが更新されたことを意味します。 その結果、キャッシュ内で見つかった古いファイルのコンテントは、無効とされるべきであり get()false を返さなければなりません。

キャッシュの依存は yii\caching\Dependency の子孫クラスのオブジェクトとして表現されます。 set() でキャッシュにデータ・アイテムを格納する際に、 関連するキャッシュの依存のオブジェクトを一緒に渡すことができます。例えば、

// example.txt ファイルの更新日時への依存を作成します。
$dependency = new \yii\caching\FileDependency(['fileName' => 'example.txt']);

// データは 30 秒で期限切れになります。
// さらに、example.txt が変更された場合、有効期限内でも無効になります。
$cache->set($key, $data, 30, $dependency);

// キャッシュはデータの有効期限が切れているかをチェックします。
// 同時に、関連付けられた依存が変更されているかもチェックします。
// これらの条件のいずれかが満たされている場合は false を返します。
$data = $cache->get($key);

以下は利用可能なキャッシュの依存の概要です:

Note: 依存を有するキャッシュについて exists() メソッドを使用することは避けてください。 このメソッドは、キャッシュされたデータに関連づけられた依存がある場合でも、依存が変化したかどうかをチェックしません。 つまり、exists()true を返しているのに、 get()false を返すという場合があり得ます。

クエリ・キャッシュ

クエリ・キャッシュは、データ・キャッシュ上に構築された特別なキャッシュ機能で、 データベースのクエリ結果をキャッシュするために提供されています。

クエリ・キャッシュは データベース接続 と有効な cache アプリケーション・コンポーネント を必要とします。 $dbyii\db\Connection のインスタンスと仮定した場合、クエリ・キャッシュの基本的な使い方は以下のようになります:

$result = $db->cache(function ($db) {

    // クエリ・キャッシュが有効で、かつクエリ結果がキャッシュ内にある場合、
    // SQL クエリ結果がキャッシュから提供されます
    return $db->createCommand('SELECT * FROM customer WHERE id=1')->queryOne();

});

クエリ・キャッシュは DAO だけではなく アクティブ・レコード でも使用することができます。

$result = Customer::getDb()->cache(function ($db) {
    return Customer::find()->where(['id' => 1])->one();
});

Info: いくつかの DBMS (例えば MySQL) もデータベース・サーバ・サイドのクエリ・キャッシュをサポートしています。 どちらのクエリ・キャッシュ・メカニズムを選んでも構いません。 前述した Yii のクエリ・キャッシュにはキャッシュの依存を柔軟に指定できるという利点があり、潜在的にはより効率的です。

2.0.14 以降は、下記のショートカットを使用することが出来ます。

(new Query())->cache(7200)->all();
// および
User::find()->cache(7200)->all();

構成

クエリ・キャッシュには yii\db\Connection を通して設定可能な三つのグローバルなオプションがあります:

  • enableQueryCache: クエリ・キャッシュを可能にするかどうか。デフォルトは true です。 実効的にクエリ・キャッシュをオンにするには queryCache によって指定される有効なキャッシュを持っている必要があることに注意してください。
  • 持続できる秒数を表します。 クエリ・キャッシュを永遠にキャッシュに残したい場合は 0 を指定することができます。 このプロパティは yii\db\Connection::cache() が持続時間を指定せず呼び出されたときに使用されるデフォルト値です。

  • queryCache: これはキャッシュ・アプリケーション・コンポーネントの ID を表します。 デフォルトは 'cache' です。有効なキャッシュ・コンポーネントが存在する場合にのみ、クエリ・キャッシュが使用可能になります。

使い方

クエリ・キャッシュを使用する必要がある複数の SQL クエリを持っている場合は yii\db\Connection::cache() を使用することができます。使い方は以下のとおりです。

$duration = 60;     // クエリ結果を 60 秒間 キャッシュ
$dependency = ...;  // 依存のオプション

$result = $db->cache(function ($db) {

    // ... ここで SQL クエリを実行します ...

    return $result;

}, $duration, $dependency);

無名関数内の任意の SQL クエリは、指定した依存とともに指定された期間キャッシュされます。 もしキャッシュ内に有効なクエリ結果が見つかった場合は、クエリはスキップされ、代りに結果がキャッシュから提供されます。 $duration の指定がない場合 queryCacheDuration で指定されている値が代りに使用されます。

場合によっては cache() 内でいくつかの特定のクエリに対してクエリ・キャッシュを無効にしたいことが有るでしょう。 そのときは yii\db\Connection::noCache() を使用します。

$result = $db->cache(function ($db) {

    // クエリ・キャッシュを使用する SQL クエリ

    $db->noCache(function ($db) {

        // クエリ・キャッシュを使用しない SQL クエリ

    });

    // ...

    return $result;
});

単一のクエリのためだけにクエリ・キャッシュを使用したい場合は、コマンドを構築するときに yii\db\Command::cache() を呼び出すことができます。例えば、

// クエリ・キャッシュを使い、期間を 60 秒にセットする
$customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->cache(60)->queryOne();

また、一つのコマンドに対してクエリ・キャッシュを無効にするために yii\db\Command::noCache() を使用することもできます。例えば、

$result = $db->cache(function ($db) {

    // クエリ・キャッシュを使用する SQL クエリ

    // このコマンドにはクエリ・キャッシュを使用しない
    $customer = $db->createCommand('SELECT * FROM customer WHERE id=1')->noCache()->queryOne();

    // ...

    return $result;
});

制約

リソース・ハンドラを返すようなクエリにはクエリ・キャッシュは働きません。 例えば、いくつかの DBMS において BLOB 型のカラムを用いる場合、 クエリ結果はカラム・データに対するリソース・ハンドラを返します。

いくつかのキャッシュ・ストレージはサイズに制約があります。 例えば Memcache では、各エントリのサイズは 1MB が上限値です。 そのためクエリ結果のサイズがこの制約を越える場合、キャッシュは失敗します。

キャッシュのフラッシュ

保存されている全てのキャッシュ・データを無効化する必要がある場合は、yii\caching\Cache::flush() を呼ぶことが出来ます。

コンソールから yii cache/flush を呼ぶことによっても、キャッシュをフラッシュすることが出来ます。

  • yii cache: アプリケーションで利用可能なキャッシュのリストを表示します。
  • yii cache/flush cache1 cache2: キャッシュ・コンポーネント cache1cache2 をフラッシュします (複数のコンポーネント名をスペースで区切って渡すことが出来ます)
  • yii cache/flush-all: アプリケーションの全てのキャッシュ・コンポーネントをフラッシュします。
  • yii cache/flush-schema db: 指定された DB 接続に対する DB スキーマ・キャッシュをクリアします。

Info: デフォルトでは、コンソール・アプリケーションは独立した構成情報ファイルを使用します。 正しい結果を得るためには、ウェブとコンソールのアプリケーション構成で同じキャッシュ・コンポーネントを使用していることを確認してください。