Skip to content

Instantly share code, notes, and snippets.

@ridwanpr
Created February 14, 2025 12:28
Show Gist options
  • Save ridwanpr/feecf946b2461e8b35cd87f5d01a6ee6 to your computer and use it in GitHub Desktop.
Save ridwanpr/feecf946b2461e8b35cd87f5d01a6ee6 to your computer and use it in GitHub Desktop.
Laravel code example to handle s3 bucket rotation based on storage usage.
// Automatically manage bucket rotation based on storage usage.
// Each bucket has a 25GB limit, so the **BUCKET_THRESHOLD_GB** is set to 24GB to trigger a switch to a new bucket.
<?php
namespace App\Services;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
class BucketManager
{
private const BUCKET_PREFIX = 's';
private const BUCKET_START = 3;
private const BUCKET_END = 7;
private const BUCKET_THRESHOLD_GB = 24;
private const CACHE_KEY = 'current_bucket';
private const CACHE_DURATION_MINUTES = 60;
public function getCurrentBucket(): string
{
$currentBucket = Cache::get(self::CACHE_KEY);
if (!$currentBucket) {
$currentBucket = $this->findFirstAvailableBucket();
Cache::put(self::CACHE_KEY, $currentBucket, now()->addMinutes(self::CACHE_DURATION_MINUTES));
}
if ($this->getBucketUsageGB($currentBucket) >= self::BUCKET_THRESHOLD_GB) {
$currentBucket = $this->findFirstAvailableBucket();
Cache::put(self::CACHE_KEY, $currentBucket, now()->addMinutes(self::CACHE_DURATION_MINUTES));
}
return $currentBucket;
}
public function storeFile(string $path, $contents, array $options = []): array
{
$bucket = $this->getCurrentBucket();
try {
Storage::disk($bucket)->put($path, $contents, $options);
return [
'bucket' => $bucket,
'url' => Storage::disk($bucket)->url($path)
];
} catch (\Exception $e) {
Log::error("Failed to store file in bucket {$bucket}: " . $e->getMessage());
throw $e;
}
}
private function findFirstAvailableBucket(): string
{
for ($i = self::BUCKET_START; $i <= self::BUCKET_END; $i++) {
$bucket = self::BUCKET_PREFIX . $i;
if ($this->getBucketUsageGB($bucket) < self::BUCKET_THRESHOLD_GB) {
return $bucket;
}
}
Log::error('All buckets are near capacity!');
throw new \RuntimeException('All storage buckets are near capacity');
}
private function getBucketUsageGB(string $bucket): float
{
try {
$files = Storage::disk($bucket)->allFiles();
$totalBytes = 0;
foreach ($files as $file) {
$totalBytes += Storage::disk($bucket)->size($file);
}
return $totalBytes / (1024 * 1024 * 1024);
} catch (\Exception $e) {
Log::error("Failed to get bucket usage for {$bucket}: " . $e->getMessage());
return 0;
}
}
public function deleteFile(string $bucket, string $path): bool
{
try {
return Storage::disk($bucket)->delete($path);
} catch (\Exception $e) {
Log::error("Failed to delete file from bucket {$bucket}: " . $e->getMessage());
return false;
}
}
}
// Make sure to make filesystem config for each bucket in filesystem.php.
// Use naming convention for driver: s3, s4, s5, and so on.. adjust credential accordingly for each bucket driver
's3' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
'throw' => false,
'report' => false,
],
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment