先日、S3互換のMinIOの代替えとしてGarageの記事を書きましたが、やっていなかったパフォーマンスの計測をしてみました。
MinIOとGarageをそれぞれDockerコンテナとして起動した状態で、LaravelのArtisanコマンドからS3ドライバのleague/flysystem-aws-s3-v3 経由でファイルのアップロード、ダウンロード、ファイル削除について時間計測していきます。
テストケース
GarageとMinIOをそれぞれDockerコンテナとして起動します。
Laravel12 + league/flysystem-aws-s3-v3 を使って、
次の8つのケースで時間計測をします。
公平を期す為に、Laravelの .env を、Garage用とMinIO用を用意し、
.env を交換することで接続先を切り替えることにします。
つまり、同じソースコードを使ってGarageとMinIOのテストを実行します。
▼1:ファイルアップロード(数での勝負:API経由)
数十バイト程度の小容量のファイルを1000個、連続で順次アップロードします。
▼2:ファイルアップロード(容量での勝負:API経由)
百メガバイトの大容量のファイルを10個、連続で順次アップロードします。
▼3:ファイルダウンロード(数での勝負:API経由)
数十バイト程度の小容量のファイルを1000個、API経由で順次連続でダウンロードします。
▼4:ファイルダウンロード(容量での勝負:API経由)
百メガバイトの大容量のファイルを10個、API経由で連続で順次ダウンロードします。
▼5:ファイルダウンロード(数での勝負:HTTP経由)
数十バイト程度の小容量のファイルを1000個、HTTP経由で順次ダウンロードします。
▼6:ファイルダウンロード(容量での勝負:HTTP経由)
百メガバイトの大容量のファイルを10個、HTTP経由で順次ダウンロードします。
▼7:ファイル削除(数での勝負:API経由)
数十バイト程度の小容量のファイルを1000個、順次連続で削除します。
▼8:ファイル削除(容量での勝負:API経由)
百メガバイトの大容量のファイルを10個、連続で順次削除します。
実行環境
- Ubuntu24.04.3 LTS (WSL2 on Windows11)
- Docker Desktop v4.51.0
- PHP 8.4.14
- Laravel Framework 12.39.0
- OS: Ubuntu24.04.3 LTS on WSL2 (Windows11 OSビルド:26200.7171)
- Machine: AMD Ryzen 7 7730U(2.00GHz) / RAM 16GB / 64bit
フォルダ構成
[プロジェクトトップ]
├─ docker/ ・・・Docker用
│ ├─ garage/ ・・・Garage用
│ │ ├─ data/ ・・・オブジェクト保存用
│ │ ├─ meta/ ・・・メタデータ保存用
│ │ └─ garage.toml
│ └─ minio/ ・・・MinIO用
│ ├─ certs/ ・・・CA用
│ └─ data/ ・・・オブジェクト保存用
├─ laravel/ ・・・Laravel用
│ ├─ .env.garage
│ └─ .env.minio
└─ docker-compose.yml
コンテナ構築:GarageとMinIO
Garage、MinIO それぞれ執筆時点での最新のDcokerイメージで構築します。
▼「docker-compose.yml」
services:
garaged:
image: dxflrs/garage:v2.1.0
container_name: garaged
volumes:
- ./docker/garage/garage.toml:/etc/garage.toml
- ./docker/garage/meta:/var/lib/garage/meta
- ./docker/garage/data:/var/lib/garage/data
restart: unless-stopped
ports:
- 3900:3900
- 3901:3901
- 3902:3902
- 3903:3903
minio:
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
ports:
- '9000:9000'
- '9001:9001'
environment:
- MINIO_ROOT_USER=minio
- MINIO_ROOT_PASSWORD=minio123
volumes:
- ./docker/minio/data:/data
- ./docker/minio/certs:/root/.minio/certs
command: server /data --console-address ":9001"
コンテナ構築:
docker compose up -d
コンテナ構築後にGarageの初期設定をします。
- クラスターレイアウト作成:2GBくらい
- APIキー作成:コピーして保存
- バケット作成:laravel
- バケットへのAPIキー割り当て
- バケットのWebアクセスの許可
▼初期設定は次の記事を参照
MinIOは動作確認も含めて http://localhost:9001/ から minio / minio123 でログインして、バケット「laravel」を作成しておきます。
いつの間にかMinIOのWeb UIからバケットの公開設定が出来なくなっていたので😡💢、mcコマンド直叩きでバケットを公開に設定。
▼「docker-compose.yml」のあるフォルダで実行
docker compose exec minio /bin/mc alias set myminio http://localhost:9000 minio minio123
docker compose exec minio /bin/mc anonymous set public myminio/laravel

テストコードについて
公平を期す為に、テストコードは一か所に保存し、
.envファイルをGarage用とMinIO用とを用意して、
.envファイルを切り替えて接続先を変更することにします。
LaravelはComposerでインストールします。(インストーラー使うまでもないので)
composer create-project laravel/laravel:^12 laravel
S3ドライバーをインストールしておきます。
composer require league/flysystem-aws-s3-v3 "^3.0" --with-all-dependencies
▼.env.garage:AWS_*** の箇所のみ掲載、Keyは各自の環境に合わせて修正
AWS_ACCESS_KEY_ID=GKf8a28b48acae1b70abb15f8d
AWS_SECRET_ACCESS_KEY=eb59ab4e5ca8e1f4d15885d121e93867e8d033c544bcbc8e82dc37d4d8aca578
AWS_DEFAULT_REGION=garage
AWS_BUCKET=laravel
AWS_ENDPOINT=http://localhost:3900
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_URL=http://laravel.web.garage.localhost:3902
▼.env.minio:AWS_*** の箇所のみ掲載
AWS_ACCESS_KEY_ID=minio
AWS_SECRET_ACCESS_KEY=minio123
AWS_DEFAULT_REGION=ap-northeast-1
AWS_BUCKET=laravel
AWS_USE_PATH_STYLE_ENDPOINT=true
AWS_ENDPOINT=http://minio:9000
AWS_URL=http://minio:9000/laravel
▼Garage用の「.env」に切り替え:laravelフォルダ内で
cp -f .env.garage .env
▼MinIO用の「.env」に切り替え:laravelフォルダ内で
cp -f .env.minio .env
100MBのファイルは次のコードで「100mb.txt」として作成し、
Laravelプロジェクトの「storage/100mb.txt」としてブッ込んでおきます。
テストコードからは「storage_path(‘100mb.txt’)」として参照します。
▼create100mb.php
<?php
ini_set('memory_limit', -1);
$content = "";
for ($i = 0; $i < 100 * 1024 * 1024; $i++) {
$content .= "A";
}
file_put_contents(__DIR__ . '/100mb.txt', $content);
テストコードはArtisanコマンドとして作成することにします。
cd laravel
php artisan make:command BenchmarkS3UploadSmall
php artisan make:command BenchmarkS3UploadBig
php artisan make:command BenchmarkS3DownloadSmallViaApi
php artisan make:command BenchmarkS3DownloadBigViaApi
php artisan make:command BenchmarkS3DownloadSmallViaHttp
php artisan make:command BenchmarkS3DownloadBigViaHttp
php artisan make:command BenchmarkS3DeleteSmall
php artisan make:command BenchmarkS3DeleteBig
テストケース1:ファイルアップロード(数での勝負)
▼テストコード:app/Console/Commands/BenchmarkS3UploadSmall.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\Storage;
class BenchmarkS3UploadSmall extends Command
{
protected $signature = 'benchmark:s3:upload:small';
protected $description = 'ファイルアップロード(数で勝負)';
public function handle()
{
$iterations = 1000;
$time = Benchmark::measure(function () use ($iterations) {
$content = "This is a sample file content for S3 upload benchmarking.";
$basename = "small";
$extension = ".txt";
$nl = strlen((string) $iterations);
for ($i = 1; $i <= $iterations; $i++) {
$filename = sprintf("%s%0{$nl}d%s", $basename, $i, $extension);
Storage::disk('s3')->put($filename, $content);
}
});
$this->info("Time for S3 upload over {$iterations} iterations: {$time} ms");
}
}
▼Garageでの実行結果

▼MinIOのでの実行結果

テストケース2:ファイルアップロード(容量での勝負)
▼テストコード:app/Console/Commands/BenchmarkS3UploadBig.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Http\File;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\Storage;
class BenchmarkS3UploadBig extends Command
{
protected $signature = 'benchmark:s3:upload:big';
protected $description = 'ファイルアップロード(容量で勝負)';
public function handle()
{
ini_set('memory_limit', -1);
$iterations = 10;
$time = Benchmark::measure(function () use ($iterations) {
$basename = "big";
$extension = ".txt";
$nl = strlen((string) $iterations);
for ($i = 1; $i <= $iterations; $i++) {
$filename = sprintf("%s%0{$nl}d%s", $basename, $i, $extension);
Storage::disk('s3')->putFileAs('/', new File(storage_path('100mb.txt')), $filename);
}
});
$this->info("Time for S3 upload over {$iterations} iterations: {$time} ms");
}
}
▼Garageでの実行結果

▼MinIOでの実行結果

テストケース3:ファイルダウンロード(数での勝負:API経由)
▼テストコード:app/Console/Commands/BenchmarkS3DownloadSmallViaApi.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\Storage;
class BenchmarkS3DownloadSmallViaApi extends Command
{
protected $signature = 'benchmark:s3:download:small';
protected $description = 'ファイルダウンロード(数で勝負)';
public function handle()
{
$iterations = 1000;
$time = Benchmark::measure(function () use ($iterations) {
$basename = "small";
$extension = ".txt";
$nl = strlen((string) $iterations);
for ($i = 1; $i <= $iterations; $i++) {
$filename = sprintf("%s%0{$nl}d%s", $basename, $i, $extension);
Storage::disk('s3')->get($filename);
}
});
$this->info("Time for S3 download over {$iterations} iterations: {$time} ms");
}
}
▼Garageでの実行結果

▼MinIOでの実行結果

テストケース4:ファイルダウンロード(容量での勝負:API経由)
▼テストコード:app/Console/Commands/BenchmarkS3DownloadBigViaApi.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\Storage;
class BenchmarkS3DownloadBigViaApi extends Command
{
protected $signature = 'benchmark:s3:download:big:api';
protected $description = 'ファイルダウンロード(容量で勝負:API経由)';
public function handle()
{
ini_set('memory_limit', -1);
$iterations = 10;
$time = Benchmark::measure(function () use ($iterations) {
$basename = "big";
$extension = ".txt";
$nl = strlen((string) $iterations);
for ($i = 1; $i <= $iterations; $i++) {
$filename = sprintf("%s%0{$nl}d%s", $basename, $i, $extension);
Storage::disk('s3')->get($filename);
}
});
$this->info("Time for S3 download over {$iterations} iterations: {$time} ms");
}
}
▼Garageでの実行結果

▼MinIOでの実行結果

テストケース5:ファイルダウンロード(数での勝負:HTTP経由)
▼テストコード:app/Console/Commands/BenchmarkS3DownloadSmallViaHttp.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class BenchmarkS3DownloadSmallViaHttp extends Command
{
protected $signature = 'benchmark:s3:download:small:http';
protected $description = 'ファイルダウンロード(数で勝負:HTTP経由)';
public function handle()
{
$iterations = 1000;
$time = Benchmark::measure(function () use ($iterations) {
$basename = "small";
$extension = ".txt";
$nl = strlen((string) $iterations);
for ($i = 1; $i <= $iterations; $i++) {
$filename = sprintf("%s%0{$nl}d%s", $basename, $i, $extension);
$url = Storage::disk('s3')->url($filename);
Http::get($url);
}
});
$this->info("Time for S3 download over {$iterations} iterations: {$time} ms");
}
}
▼Garageでの実行結果

▼MinIOでの実行結果

テストケース6:ファイルダウンロード(容量での勝負:HTTP経由)
▼テストコード:app/Console/Commands/BenchmarkS3DownloadBigViaHttp.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class BenchmarkS3DownloadBigViaHttp extends Command
{
protected $signature = 'benchmark:s3:download:big:http';
protected $description = 'ファイルダウンロード(容量で勝負:HTTP経由)';
public function handle()
{
$iterations = 10;
$time = Benchmark::measure(function () use ($iterations) {
$basename = "big";
$extension = ".txt";
$nl = strlen((string) $iterations);
for ($i = 1; $i <= $iterations; $i++) {
$filename = sprintf("%s%0{$nl}d%s", $basename, $i, $extension);
$url = Storage::disk('s3')->url($filename);
Http::get($url);
}
});
$this->info("Time for S3 download over {$iterations} iterations: {$time} ms");
}
}
▼Garageでの実行結果

▼MinIOでの実行結果

テストケース7:ファイル削除(数での勝負)
▼テストコード:app/Console/Commands/BencharkS3DeleteSmall.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\Storage;
class BenchmarkS3DeleteSmall extends Command
{
protected $signature = 'benchmark:s3:delete:small';
protected $description = 'ファイル削除(数で勝負)';
public function handle()
{
$iterations = 1000;
$time = Benchmark::measure(function () use ($iterations) {
$basename = "small";
$extension = ".txt";
$nl = strlen((string) $iterations);
for ($i = 1; $i <= $iterations; $i++) {
$filename = sprintf("%s%0{$nl}d%s", $basename, $i, $extension);
Storage::disk('s3')->delete($filename);
}
});
$this->info("Time for S3 delete over {$iterations} iterations: {$time} ms");
}
}
▼Garageでの実行結果

▼MinIOでの実行結果

テストケース8:ファイル削除(容量での勝負)
▼テストコード:app/Console/Commands/BenchmarkS3DeleteBig.php
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Benchmark;
use Illuminate\Support\Facades\Storage;
class BenchmarkS3DeleteBig extends Command
{
protected $signature = 'benchmark:s3:delete:big';
protected $description = 'ファイル削除(容量で勝負)';
public function handle()
{
$iterations = 10;
$time = Benchmark::measure(function () use ($iterations) {
$basename = "big";
$extension = ".txt";
$nl = strlen((string) $iterations);
for ($i = 1; $i <= $iterations; $i++) {
$filename = sprintf("%s%0{$nl}d%s", $basename, $i, $extension);
Storage::disk('s3')->delete($filename);
}
});
$this->info("Time for S3 delete over {$iterations} iterations: {$time} ms");
}
}
▼Garageでの実行結果

▼MinIOでの実行結果

評価
▼計測時間を表にまとめました。左側がミリ秒、右側が速い方に対する割合です。
| テストケース | Garage | MinIO | Garage | MinIO |
|---|---|---|---|---|
| 1:UL(数) | 3427.424908 | 7318.668905 | 1.00 | 2.14 |
| 2:UL(容) | 4629.371765 | 6721.416733 | 1.00 | 1.45 |
| 3:DL(数:API) | 2267.454906 | 2744.063043 | 1.00 | 1.21 |
| 4:DL(容:API) | 2712.938350 | 6364.651428 | 1.00 | 2.35 |
| 5:DL(数:HTTP) | 1089.610721 | 2197.579758 | 1.00 | 2.02 |
| 6:DL(容:HTTP) | 3418.729089 | 5288.171182 | 1.00 | 1.55 |
| 7:DEL(数) | 2532.800805 | 2784.917459 | 1.00 | 1.10 |
| 8:DEL(容) | 93.745823 | 86.441237 | 1.08 | 1.00 |
顕著な差が出ましたね。
削除のテストケース7,8以外はGarageの方が圧倒的に速いです。
容量勝負の削除は10ファイル分の削除フラグを立てているだけなので速いのでしょう。
筆者はストレージには詳しくないので、保存形式がどうとか、スループットがどうとか、その辺はよくわかりません。
詳しい人、解説してください。
とりあえず、パフォーマンス勝負ではGarageの圧勝という結果になりました。
MinIOよ今までありがとう、そしてさようなら。
- 2
- 1
- 0
- 0



コメント