mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-04 10:42:23 +00:00
fix(cleanup): stop loading deployment logs and unused backup executions
The stuck resources cleanup selected every column of every deployment, including logs, and ran out of memory during app:init. Select only the id and application_id. Also drop the unused executions property on the backup pages and an unused execution loader. Closes #12435 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
bb6dfd147e
commit
9c6138fe18
9 files changed
+50
-20
No files matched your search
@@ -19,6 +19,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
|
||||
### Fixed
|
||||
|
||||
- Finish the startup and the nightly cleanup of stuck resources on instances with many large deployment logs. Before, the cleanup loaded the logs of all deployments and stopped with "Allowed memory size exhausted", so stuck deployments, tasks, and backups were not marked as failed and the proxy configuration was not set up ([#12435](https://github.com/coollabsio/coolify/issues/12435))
|
||||
- Open the database backup pages and **Settings > Backup** faster when a backup has many executions. Before, these pages loaded all executions of the backup on each request, but they did not show them
|
||||
- Queue a deployment from the MCP `deploy` tool and the MCP `control` start action while a deployment of the same application is in progress, as the API and the **Deploy** button do. Before, both started at once, so two rolling updates could remove the new container of each other and stop the application. A deployment of the same commit is now skipped, and the response gives the UUID of the deployment in progress. A rolling update now also removes only the containers that ran before it started ([#12442](https://github.com/coollabsio/coolify/issues/12442))
|
||||
- Do not copy the preview deployments of an application when you clone it. A clone of an application with a fixed preview URL template (for example `demo-mr-{{pr_id}}.example.com`) failed with a duplicate preview domain error and left a stopped, partial clone. The clone gets its own previews from new pull requests
|
||||
- Handle an existing Cloudflare DNS record with a different IP when Coolify adds the DNS record of a new domain automatically. Coolify now replaces the record when it created the record and no other resource uses it. Otherwise it opens the **Configure DNS** dialog, where you can replace the record, and the domain shows the conflict instead of "Not checked"
|
||||
|
||||
@@ -70,7 +70,9 @@ class CleanupStuckedResources extends Command
|
||||
echo "Error in cleaning stuck servers: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$applicationsDeploymentQueue = ApplicationDeploymentQueue::query()->lazyById();
|
||||
$applicationsDeploymentQueue = ApplicationDeploymentQueue::query()
|
||||
->select(['id', 'application_id'])
|
||||
->lazyById();
|
||||
foreach ($applicationsDeploymentQueue as $applicationDeploymentQueue) {
|
||||
if (is_null($applicationDeploymentQueue->application)) {
|
||||
echo "Deleting stuck application deployment queue: {$applicationDeploymentQueue->id}\n";
|
||||
|
||||
@@ -120,8 +120,7 @@ class Emails extends Command
|
||||
case 'application-deployment-success-daily':
|
||||
$applications = Application::all();
|
||||
foreach ($applications as $application) {
|
||||
$deployments = $application->get_last_days_deployments();
|
||||
if ($deployments->isEmpty()) {
|
||||
if (! $application->hasDeploymentsInLastDays()) {
|
||||
continue;
|
||||
}
|
||||
$this->mail = (new DeploymentSuccess($application, 'test'))->toMail();
|
||||
|
||||
@@ -23,8 +23,6 @@ class Execution extends Component
|
||||
|
||||
public ?ScheduledDatabaseBackup $backup;
|
||||
|
||||
public $executions;
|
||||
|
||||
public $s3s;
|
||||
|
||||
public array $parameters = [];
|
||||
@@ -73,10 +71,8 @@ class Execution extends Component
|
||||
if (! $backup) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$executions = collect($backup->executions)->sortByDesc('created_at');
|
||||
$this->database = $database;
|
||||
$this->backup = $backup;
|
||||
$this->executions = $executions;
|
||||
$this->s3s = S3Storage::query()
|
||||
->where('team_id', $database->team()->id)
|
||||
->where('is_usable', true)
|
||||
|
||||
@@ -28,9 +28,6 @@ class SettingsBackup extends Component
|
||||
#[Locked]
|
||||
public $s3s;
|
||||
|
||||
#[Locked]
|
||||
public $executions = [];
|
||||
|
||||
#[Validate(['nullable'])]
|
||||
public ?string $description = null;
|
||||
|
||||
@@ -58,7 +55,6 @@ class SettingsBackup extends Component
|
||||
$this->backup->enabled = false;
|
||||
$this->backup->save();
|
||||
}
|
||||
$this->executions = $this->backup?->executions ?? [];
|
||||
}
|
||||
$this->settings = $settings;
|
||||
$this->is_backup_before_update_enabled = $settings->is_backup_before_update_enabled;
|
||||
@@ -112,7 +108,6 @@ class SettingsBackup extends Component
|
||||
$this->s3s = S3Storage::whereTeamId(0)->get();
|
||||
|
||||
$this->description = $this->database->description;
|
||||
$this->executions = $this->backup->executions;
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
|
||||
@@ -1323,9 +1323,9 @@ class Application extends BaseModel
|
||||
return ApplicationDeploymentQueue::where('application_id', $this->id)->where('status', ApplicationDeploymentStatus::FINISHED->value)->where('pull_request_id', 0)->orderBy('created_at', 'desc')->first($columns);
|
||||
}
|
||||
|
||||
public function get_last_days_deployments()
|
||||
public function hasDeploymentsInLastDays(int $days = 7): bool
|
||||
{
|
||||
return ApplicationDeploymentQueue::where('application_id', $this->id)->where('created_at', '>=', now()->subDays(7))->orderBy('created_at', 'desc')->get();
|
||||
return ApplicationDeploymentQueue::where('application_id', $this->id)->where('created_at', '>=', now()->subDays($days))->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -87,11 +87,6 @@ class ScheduledDatabaseBackup extends BaseModel
|
||||
return $this->belongsToMany(S3Storage::class, 'scheduled_database_backup_s3_storage')->withTimestamps();
|
||||
}
|
||||
|
||||
public function get_last_days_backup_status($days = 7)
|
||||
{
|
||||
return $this->hasMany(ScheduledDatabaseBackupExecution::class)->where('created_at', '>=', now()->subDays($days))->get();
|
||||
}
|
||||
|
||||
public function executionsPaginated(int $skip = 0, int $take = 10)
|
||||
{
|
||||
$executions = $this->hasMany(ScheduledDatabaseBackupExecution::class)->with('s3Replicas.s3')->orderBy('created_at', 'desc');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\ApplicationDeploymentQueue;
|
||||
use App\Models\Environment;
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Project;
|
||||
@@ -9,6 +10,7 @@ use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\Team;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
@@ -42,3 +44,43 @@ it('deletes scheduled tasks whose configured owner no longer exists', function (
|
||||
expect($validTask->fresh())->not->toBeNull()
|
||||
->and($orphanedTask->fresh())->toBeNull();
|
||||
});
|
||||
|
||||
it('deletes orphaned deployments without loading their logs', function () {
|
||||
InstanceSettings::unguarded(fn () => InstanceSettings::firstOrCreate(['id' => 0]));
|
||||
Queue::fake();
|
||||
|
||||
$team = Team::factory()->create();
|
||||
$server = Server::factory()->create(['team_id' => $team->id]);
|
||||
$destination = StandaloneDocker::where('server_id', $server->id)->firstOrFail();
|
||||
$project = Project::factory()->create(['team_id' => $team->id]);
|
||||
$environment = $project->environments()->first()
|
||||
?? Environment::factory()->create(['project_id' => $project->id]);
|
||||
$application = Application::factory()->create([
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
$validDeployment = ApplicationDeploymentQueue::create([
|
||||
'application_id' => $application->id,
|
||||
'deployment_uuid' => 'valid-deployment',
|
||||
'server_id' => $server->id,
|
||||
'logs' => str_repeat('x', 1000),
|
||||
]);
|
||||
$orphanedDeployment = ApplicationDeploymentQueue::create([
|
||||
'application_id' => PHP_INT_MAX,
|
||||
'deployment_uuid' => 'orphaned-deployment',
|
||||
'server_id' => $server->id,
|
||||
'logs' => str_repeat('x', 1000),
|
||||
]);
|
||||
|
||||
DB::enableQueryLog();
|
||||
$this->artisan('cleanup:stucked-resources')->assertSuccessful();
|
||||
$deploymentSelects = collect(DB::getQueryLog())
|
||||
->pluck('query')
|
||||
->filter(fn (string $query) => str_starts_with($query, 'select') && str_contains($query, 'from "application_deployment_queues"'));
|
||||
|
||||
expect($validDeployment->fresh())->not->toBeNull()
|
||||
->and($orphanedDeployment->fresh())->toBeNull()
|
||||
->and($deploymentSelects)->not->toBeEmpty()
|
||||
->and($deploymentSelects->filter(fn (string $query) => str_contains($query, '*')))->toBeEmpty();
|
||||
});
|
||||
@@ -136,7 +136,6 @@ test('instance backup settings render when the instance database has no backup s
|
||||
|
||||
Livewire::test(SettingsBackup::class)
|
||||
->assertOk()
|
||||
->assertSet('executions', [])
|
||||
->assertSee('Backup is not configured')
|
||||
->assertSee('Configure backup');
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user