18
8

More than 5 years have passed since last update.

[AWS SDK for PHP] S3へ一括でコマンドを実行したい場合はCommandPoolを使えという話

Last updated at Posted at 2018-11-17

開発しているサービスで、添付画像付きの投稿処理がやたら遅く、Travis CIで実行しているE2Eテストが必ずタイムアウトするという問題が発生しました。

原因としてはS3に対して1ファイルずつputObjectコマンドを順次行ってアップロードしていたことです。

※記事に記載しているコードはあくまでどのように改善したかを理解してもらう為の簡単なサンプルです。本来はtry-catchやその他の処理が必要になりますので悪しからず。

■修正前コードサンプル

foreach ($uploadFiles as $uploadFile) {
    // 1ファイルずつs3->putObjectでS3にアップロード
    $response = $this->s3Instance->putObject([
        'Bucket' => ****,
        'Key' => $uploadFile['key'],
        'Body' => $uploadFile['body'],
        'ContentType' => ****,
//        ....
   ]);
}

お分かりだと思いますが、もし10ファイルをアップロードするとしたら10回分putObjectの処理時間がかかってしまいます汗

もしオリジナルの画像ファイルとは別にサイズごとの画像もS3に保管していたとしたら、さらに遅くなります。(あ、それうちのサービスだった・・)

なのでこういった場合は、複数のAWSコマンドを同時実行できるCommandPoolクラスを使いましょう。

■修正後コードサンプル

$commands = [];
foreach ($uploadFiles as $uploadFile) {
    // putObject用のコマンドを生成して配列に格納
    $commands[] = $this->s3Instance->getCommand('PutObject', [
        'Bucket' => ***,
        'Key' => $uploadFile['key'],
        'Body' => $uploadFile['body'],
        'ContentType' => ***,
//        ....
    ]);
}

// 同時コマンド実行!
CommandPool::batch($this->s3Instance, $commands);

まずputObjectコマンドをループの中では実行せず、getCommandでputObject用のコマンドを生成して配列に格納します。
あとはCommandPool::batchメソッドにs3インスタンスと生成したコマンド群を渡してあげれば簡単にコマンドを同時実行出来ます。

batchの中身を見ると、やってるのは単純にPromise処理です。

    public static function batch(
        AwsClientInterface $client,
        $commands,
        array $config = []
    ) {
        $results = [];
        self::cmpCallback($config, 'fulfilled', $results);
        self::cmpCallback($config, 'rejected', $results);

        return (new self($client, $commands, $config))
            ->promise()
            ->then(static function () use (&$results) {
                ksort($results);
                return $results;
            })
            ->wait();
    }

もちろんS3からファイルを取得する時なども同様に使えます。

$commands = [];
foreach ($keys as $key) {
    // 取得なのでGetObjectになる
    $commands[] = $this->s3Instance->getCommand('GetObject', [
        'Bucket'      => ****,
        'Key'         => $key,
    ]);
}
// S3からファイルをまとめて取得だ!
$responses = CommandPool::batch($this->s3Instance, $commands);

$fileContents = [];
// あとは順次各ファイルの中身を取り出すだけ
foreach ($responses as $response) {
    $data = $response['Body'];
    $fileContents[] = $data->getContents();
}
return $fileContents;

結果、S3オブジェクトの取得・アップロードをCommandPoolクラスを活用することによって処理時間が5倍以上高速になりました。
利用は非常にシンプルで使わない手はないと思いますので、ぜひ使ってみてください。

18
8
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
18
8