mirror of
https://github.com/archtechx/tenancy.git
synced 2026-05-06 22:24:04 +00:00
Enhance tenant migration command to support parallel execution
- Added a `--parallel` option to the Migrate command for concurrent tenant migrations. - Introduced a `parallel-batch-size` option to control the number of concurrent migrations. - Implemented a new method `runParallel` to handle parallel migrations. - Updated the `handle` method to initiate parallel migrations when the option is set. - Added tests to verify the functionality of the parallel migration option.
This commit is contained in:
parent
ab64f4599d
commit
3498a3f9c2
3 changed files with 219 additions and 10 deletions
|
|
@ -10,12 +10,18 @@ use Illuminate\Database\Migrations\Migrator;
|
||||||
use Stancl\Tenancy\Concerns\DealsWithMigrations;
|
use Stancl\Tenancy\Concerns\DealsWithMigrations;
|
||||||
use Stancl\Tenancy\Concerns\ExtendsLaravelCommand;
|
use Stancl\Tenancy\Concerns\ExtendsLaravelCommand;
|
||||||
use Stancl\Tenancy\Concerns\HasATenantsOption;
|
use Stancl\Tenancy\Concerns\HasATenantsOption;
|
||||||
|
use Stancl\Tenancy\Concerns\ParallelTenantMigrator;
|
||||||
|
use Stancl\Tenancy\Contracts\Tenant;
|
||||||
use Stancl\Tenancy\Events\DatabaseMigrated;
|
use Stancl\Tenancy\Events\DatabaseMigrated;
|
||||||
use Stancl\Tenancy\Events\MigratingDatabase;
|
use Stancl\Tenancy\Events\MigratingDatabase;
|
||||||
|
use Symfony\Component\Console\Input\InputOption;
|
||||||
|
|
||||||
class Migrate extends MigrateCommand
|
class Migrate extends MigrateCommand
|
||||||
{
|
{
|
||||||
use HasATenantsOption, DealsWithMigrations, ExtendsLaravelCommand;
|
use HasATenantsOption {
|
||||||
|
getOptions as private tenantsCommandOptions;
|
||||||
|
}
|
||||||
|
use DealsWithMigrations, ExtendsLaravelCommand, ParallelTenantMigrator;
|
||||||
|
|
||||||
protected $description = 'Run migrations for tenant(s)';
|
protected $description = 'Run migrations for tenant(s)';
|
||||||
|
|
||||||
|
|
@ -31,12 +37,20 @@ class Migrate extends MigrateCommand
|
||||||
$this->specifyParameters();
|
$this->specifyParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
protected function getOptions()
|
||||||
* Execute the console command.
|
{
|
||||||
*
|
return array_merge([
|
||||||
* @return mixed
|
[
|
||||||
*/
|
'parallel-batch-size',
|
||||||
public function handle()
|
null,
|
||||||
|
InputOption::VALUE_OPTIONAL,
|
||||||
|
'Maximum concurrent tenant migrations per batch when using --parallel',
|
||||||
|
'10',
|
||||||
|
],
|
||||||
|
], $this->tenantsCommandOptions());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): int
|
||||||
{
|
{
|
||||||
foreach (config('tenancy.migration_parameters') as $parameter => $value) {
|
foreach (config('tenancy.migration_parameters') as $parameter => $value) {
|
||||||
if (! $this->input->hasParameterOption($parameter)) {
|
if (! $this->input->hasParameterOption($parameter)) {
|
||||||
|
|
@ -45,15 +59,73 @@ class Migrate extends MigrateCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->confirmToProceed()) {
|
if (! $this->confirmToProceed()) {
|
||||||
return;
|
return static::FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
tenancy()->runForMultiple($this->option('tenants'), function ($tenant) {
|
if ($this->option('parallel')) {
|
||||||
|
return $this->runParallel();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->runMigrationForTenants($this->option('tenants'));
|
||||||
|
|
||||||
|
return static::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function runParallel(): int
|
||||||
|
{
|
||||||
|
if (! class_exists(\Illuminate\Support\Facades\Concurrency::class)) {
|
||||||
|
$this->error('Parallel tenant migrations require Laravel 11 or newer (Concurrency facade).');
|
||||||
|
|
||||||
|
return static::FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$keys = $this->tenantKeys();
|
||||||
|
if ($keys === []) {
|
||||||
|
$this->info('No tenants to migrate.');
|
||||||
|
|
||||||
|
return static::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->info('Running migrations in parallel');
|
||||||
|
|
||||||
|
$this->runParallelTenantBatches(
|
||||||
|
$keys,
|
||||||
|
max(1, (int) $this->option('parallel-batch-size')),
|
||||||
|
function (int $batchIndex, int $batchTotal, array $batch): void {
|
||||||
|
$n = count($batch);
|
||||||
|
$this->line(sprintf(
|
||||||
|
'Parallel batch %d/%d (%d tenant%s)',
|
||||||
|
$batchIndex + 1,
|
||||||
|
$batchTotal,
|
||||||
|
$n,
|
||||||
|
$n === 1 ? '' : 's'
|
||||||
|
));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return static::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string|int>
|
||||||
|
*/
|
||||||
|
private function tenantKeys(): array
|
||||||
|
{
|
||||||
|
$keys = [];
|
||||||
|
foreach ($this->getTenants() as $tenant) {
|
||||||
|
$keys[] = $tenant instanceof Tenant ? $tenant->getTenantKey() : $tenant;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function runMigrationForTenants(?array $tenants = []): void
|
||||||
|
{
|
||||||
|
tenancy()->runForMultiple($tenants, function ($tenant) {
|
||||||
$this->line("Tenant: {$tenant->getTenantKey()}");
|
$this->line("Tenant: {$tenant->getTenantKey()}");
|
||||||
|
|
||||||
event(new MigratingDatabase($tenant));
|
event(new MigratingDatabase($tenant));
|
||||||
|
|
||||||
// Migrate
|
|
||||||
parent::handle();
|
parent::handle();
|
||||||
|
|
||||||
event(new DatabaseMigrated($tenant));
|
event(new DatabaseMigrated($tenant));
|
||||||
|
|
|
||||||
108
src/Concerns/ParallelTenantMigrator.php
Normal file
108
src/Concerns/ParallelTenantMigrator.php
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Stancl\Tenancy\Concerns;
|
||||||
|
|
||||||
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Concurrency;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parallel `tenants:migrate` via {@see Concurrency} and option forwarding for nested Artisan calls.
|
||||||
|
*/
|
||||||
|
trait ParallelTenantMigrator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Options for nested `tenants:migrate` (parallel workers). Omits `--parallel`.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
protected function optionsForNestedMigrateCall(): array
|
||||||
|
{
|
||||||
|
$options = [];
|
||||||
|
|
||||||
|
if ($database = $this->option('database')) {
|
||||||
|
$options['--database'] = $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('force')) {
|
||||||
|
$options['--force'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($paths = $this->option('path')) {
|
||||||
|
$options['--path'] = $paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('realpath')) {
|
||||||
|
$options['--realpath'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($schemaPath = $this->option('schema-path')) {
|
||||||
|
$options['--schema-path'] = $schemaPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('pretend')) {
|
||||||
|
$options['--pretend'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('seed')) {
|
||||||
|
$options['--seed'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($seeder = $this->option('seeder')) {
|
||||||
|
$options['--seeder'] = $seeder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('step')) {
|
||||||
|
$options['--step'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('graceful')) {
|
||||||
|
$options['--graceful'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->option('no-interaction')) {
|
||||||
|
$options['--no-interaction'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string|int> $keys
|
||||||
|
* @param callable(int $batchIndex, int $batchTotal, list<string|int> $batchKeys): void $beforeBatch
|
||||||
|
*/
|
||||||
|
protected function runParallelTenantBatches(array $keys, int $batchSize, callable $beforeBatch): void
|
||||||
|
{
|
||||||
|
$forward = $this->optionsForNestedMigrateCall();
|
||||||
|
$batches = array_chunk($keys, max(1, $batchSize));
|
||||||
|
$total = count($batches);
|
||||||
|
|
||||||
|
foreach ($batches as $i => $batch) {
|
||||||
|
$beforeBatch($i, $total, $batch);
|
||||||
|
|
||||||
|
$tasks = [];
|
||||||
|
foreach ($batch as $key) {
|
||||||
|
$tasks[] = static fn () => self::migrateOneTenantViaArtisan($key, $forward);
|
||||||
|
}
|
||||||
|
|
||||||
|
Concurrency::run($tasks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $forwardOptions
|
||||||
|
*/
|
||||||
|
private static function migrateOneTenantViaArtisan(string|int $key, array $forwardOptions): void
|
||||||
|
{
|
||||||
|
$code = Artisan::call('tenants:migrate', array_merge($forwardOptions, [
|
||||||
|
'--tenants' => [$key],
|
||||||
|
'--force' => true,
|
||||||
|
]));
|
||||||
|
|
||||||
|
if ($code !== 0) {
|
||||||
|
throw new RuntimeException("Tenant migration failed for [{$key}] with exit code {$code}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||||
namespace Stancl\Tenancy\Tests;
|
namespace Stancl\Tenancy\Tests;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Artisan;
|
use Illuminate\Support\Facades\Artisan;
|
||||||
|
use Illuminate\Support\Facades\Concurrency;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Event;
|
use Illuminate\Support\Facades\Event;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
@ -92,6 +93,34 @@ class CommandsTest extends TestCase
|
||||||
$this->assertTrue(Schema::hasTable('users'));
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test]
|
||||||
|
public function migrate_command_works_with_parallel_option()
|
||||||
|
{
|
||||||
|
if (! class_exists(Concurrency::class)) {
|
||||||
|
$this->markTestSkipped('Parallel tenant migrations require the Concurrency facade (Laravel 11+).');
|
||||||
|
}
|
||||||
|
|
||||||
|
config(['concurrency.default' => 'sync']);
|
||||||
|
|
||||||
|
$tenant1 = Tenant::create();
|
||||||
|
$tenant2 = Tenant::create();
|
||||||
|
|
||||||
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
|
|
||||||
|
Artisan::call('tenants:migrate', [
|
||||||
|
'--parallel' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertFalse(Schema::hasTable('users'));
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant1);
|
||||||
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
|
tenancy()->end();
|
||||||
|
|
||||||
|
tenancy()->initialize($tenant2);
|
||||||
|
$this->assertTrue(Schema::hasTable('users'));
|
||||||
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
public function rollback_command_works()
|
public function rollback_command_works()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue