プロダクション

BEAR.Sunday既定のprod束縛に対して、アプリケーションがそれぞれのディプロイ環境に応じたモジュールをカスタマイズして束縛を行います。

既定のProdModule

既定のprod束縛では以下のインターフェイスの束縛がされています。

  • エラーページ生成ファクトリー
  • PSRロガーインターフェイス
  • ローカルキャッシュ
  • 分散キャッシュ

詳細はBEAR.PackageのProdModule.php参照。

アプリケーションのProdModule

既定のProdModuleに対してアプリケーションのProdModulesrc/Module/ProdModule.phpに設置してカスタマイズします。特にエラーページと分散キャッシュは重要です。

<?php
namespace MyVendor\Todo\Module;

use BEAR\Package\Context\ProdModule as PackageProdModule;
use BEAR\QueryRepository\CacheVersionModule;
use BEAR\Resource\Module\OptionsMethodModule;
use BEAR\Package\AbstractAppModule;

class ProdModule extends AbstractModule
{
    /**
     * {@inheritdoc}
     */
    protected function configure()
    {
        $this->install(new PackageProdModule);       // デフォルトのprod設定
        $this->override(new OptionsMethodModule);    // OPTIONSメソッドをプロダクションでも有効に
        $this->install(new CacheVersionModule('1')); // リソースキャッシュのバージョン指定
        
        // 独自のエラーページ
        $this->bind(ErrorPageFactoryInterface::class)->to(MyErrorPageFactory::class);
    }
}

キャッシュ

キャッシュはローカルキャッシュと、複数のWebサーバー間でシェアをする分散キャッシュの2種類があります。どちらのキャッシュもデフォルトはPhpFileCacheです。

ローカルキャッシュ

ローカルキャッシュはdeploy後に変更のないアノテーション等のキャッシュ例に使われ、分散キャッシュはリソース状態の保存に使われます。

分散キャッシュ

2つ以上のWebサーバーでサービスを行うためには分散キャッシュの構成が必要です。代表的なmemcachedRedisのキャッシュエンジンのそれぞれのモジュールが用意されています。

Memcached

<?php
namespace BEAR\HelloWorld\Module;

use BEAR\QueryRepository\StorageMemcachedModule;
use BEAR\Resource\Module\ProdLoggerModule;
use BEAR\Package\Context\ProdModule as PackageProdModule;
use BEAR\Package\AbstractAppModule;
use Ray\Di\Scope;

class ProdModule extends AbstractModule
{
    protected function configure()
    {
        // memcache
        // {host}:{port}:{weight},...
        $memcachedServers = 'mem1.domain.com:11211:33,mem2.domain.com:11211:67';
        $this->install(new StorageMemcachedModule(memcachedServers));
        
        // Prodロガーのインストール
        $this->install(new ProdLoggerModule);
        
        // デフォルトのProdModuleのインストール
        $this->install(new PackageProdModule);
    }
}

Redis

// redis
$redisServer = 'localhost:6379'; // {host}:{port}
$this->install(new StorageRedisModule($redisServer));

リソースの状態保存は単にTTLによる時間更新のキャッシュとの他に、TTL時間では消えない永続的なストレージとして(CQRS)の運用も可能です。その場合にはRedisで永続処理を行うか、Cassandraなどの他KVSのストレージアダプターを独自で用意する必要があります。

キャッシュ時間の指定

デフォルトのTTLを変更する場合StorageExpiryModuleをインストールします。

// Cache time
$short = 60;
$medium = 3600;
$long = 24 * 3600;
$this->install(new StorageExpiryModule($short, $medium, $long));

キャッシュバージョンの指定

リソースのスキーマが変わり、互換性が失われる時にはキャッシュバージョンを変更します。特にTTL時間で消えないCQRS運用の場合に重要です。

$this->install(new CacheVersionModule($cacheVersion));

ディプロイの度にリソースキャッシュを破棄するためには$cacheVersionに時刻や乱数の値を割り当てると変更が不要で便利です。

ログ

ProdLoggerModuleはプロダクション用のリソース実行ログモジュールです。インストールするとGET以外のリクエストをPsr\Log\LoggerInterfaceにバインドされているロガーでログします。

特定のリソースや特定の状態でログしたい場合は、カスタムのログをBEAR\Resource\LoggerInterfaceにバインドします。

use BEAR\Resource\LoggerInterface;
use Ray\Di\AbstractModule;

final class MyProdLoggerModule extends AbstractModule
{
    protected function configure(): void
    {
        $this->bind(LoggerInterface::class)->to(MyProdLogger::class);
    }
}

LoggerInterface__invokeメソッドでリソースのURIとリソース状態がResourceObjectオブジェクトとして渡されるのでその内容で必要な部分をログします。作成には既存の実装 ProdLoggerを参考にしてください。

デプロイ

⚠️ 上書き更新を避ける

サーバーにディプロイする場合

  • 駆動中のプロジェクトフォルダをrsyncなどで上書きするのはキャッシュやオンデマンドで生成されるファイルの不一致や、高負荷のサイトではキャパシティを超えるリスクがあります。安全のために別のディレクトリでセットアップを行い、そのセットアップが成功すれば切り替えるようにします。
  • DeployerBEAR.Sundayレシピを利用することができます。

クラウドにディプロイする時には

  • コンパイルが成功すると0、依存関係の問題を見つけるとコンパイラはexitコード1を出力します。それを利用してCIにコンパイルを組み込むことを推奨します。

コンパイル

推奨セットアップを行う際にvendor/bin/bear.compileスクリプトを使ってプロジェクトをウォームアップすることができます。コンパイルスクリプトはDI/AOP用の動的に作成されるファイルやアノテーションなどの静的なキャッシュファイルを全て事前に作成し、最適化されたautoload.phpファイルとpreload.phpを出力します。

  • コンパイルをすれば全てのクラスでインジェクションを行うのでランタイムでDIのエラーが出る可能性が極めて低くなります。
  • .envには含まれた内容はPHPファイルに取り込まれるのでコンパイル後に.envを消去可能です。コンテントネゴシエーションを行う場合など(例:api-app, html-app)1つのアプリケーションで複数コンテキストのコンパイルを行うときにはファイルの退避が必要です。
mv autoload.php api.autoload.php

composer.jsonを編集してcomposer compileの内容を変更します。

autoload.php

{project_path}/autoload.phpに最適化されたautoload.phpファイルが出力されます。composer dumpa-autoload --optimizeで出力されるvendor/autoload.phpよりずっと高速です。

注意:preload.phpを利用する場合、ほとんどの利用クラスが読み込まれた状態で起動するのでコンパイルされたautoload.phpは不要です。composerが生成するvendor/autload.phpをご利用ください。

preload.php

{project_path}/preload.phpに最適化されたpreload.phpファイルが出力されます。preloadを有効にするためにはphp.iniでopcache.preloadopcache.preload_userを指定する必要があります。

PHP 7.4でサポートされた機能ですが、7.4初期のバージョンでは不安定です。7.4.4以上の最新版を使いましょう。

例)

opcache.preload=/path/to/project/preload.php
opcache.preload_user=www-data

Note: パフォーマンスベンチマークはbenchmarkを参考にしてください。

.compile.php

実環境ではないと生成ができないクラス(例えば認証が成功しないとインジェクトが完了しないResourceObject)がある場合には、コンパイル時にのみ読み込まれるダミークラス読み込みをルートの.compile.phpに記述することによってコンパイルをすることができます。

.compile.php

<?php
require __DIR__ . '/tests/Null/AuthProvider.php'; // 常に生成可能なNullオブジェクト
$_SERVER[__REQUIRED_KEY__] = 'fake';

module.dot

コンパイルをすると”dotファイル”が出力されるのでgraphvizで画像ファイルに変換するか、GraphvizOnlineを利用すればオブジェクトグラフを表示することができます。スケルトンのオブジェクトグラフもご覧ください。

dot -T svg module.dot > module.svg

ブートストラップのパフォーマンスチューニング

immutable_cacheは、不変の値を共有メモリにキャッシュするためのPECLパッケージです。APCuをベースにしていますが、PHPのオブジェクトや配列などの不変の値を共有メモリに保存するため、APCuよりも高速です。また、APCuでもimmutable_cacheでも、PECLのIgbinaryをインストールすることでメモリ使用量が減り、さらなる高速化が期待できます。

現在、専用のキャッシュアダプターなどは用意されていません。ImmutableBootstrapを参考に、専用のBootstrapを作成し呼び出してください。初期化コストを最小限に抑え、最大のパフォーマンスを得ることができます。

php.ini

// エクステンション
extension="apcu.so"
extension="immutable_cache.so"
extension="igbinary.so"

// シリアライザーの指定
apc.serializer=igbinary
immutable_cache.serializer=igbinary