Created
February 14, 2025 12:28
-
-
Save ridwanpr/feecf946b2461e8b35cd87f5d01a6ee6 to your computer and use it in GitHub Desktop.
Laravel code example to handle s3 bucket rotation based on storage usage.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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