스테이징 서버가 죽지 않게: Laravel에서 작업 스케줄링 분리

발행: (2025년 11월 30일 오전 07:39 GMT+9)
4 min read
원문: Dev.to

Source: Dev.to

문제

실제 앱을 개발한다면 스테이징 환경이 필요합니다. 저는 Laravel 10 앱을 위해 Digital Ocean droplet(2 vCPU) 하나에 간단히 스테이징을 운영하고 있습니다. 어느 날 서버가 멈추었고, htop을 보면 두 CPU 코어가 100 %에 달했고 메모리는 거의 가득 찼습니다:

1[||||||||||||||||||100.0%]   
2[||||||||||||||||||100.0%]     
Mem[||||||||||||||| 3.2G/4.0G]

두 CPU가 모두 포화 상태였고 메모리는 4 GB 중 3.2 GB에 머물렀습니다.

왜 중요한가

ps aux와 로그를 빠르게 살펴보니 스케줄러가 원인이라는 것을 알 수 있었습니다. 프로덕션에서는 의미가 있는 무거운 스케줄 명령들—가격 내보내기, 수천 개 제품을 가진 공급자 동기화, 대규모 데이터셋 처리—이 작은 스테이징 서버를 마구 죽이고 있었습니다. 스테이징에서는 그런 작업을 자주, 혹은 전혀 실행할 필요가 없습니다.

해결책

환경에 따라 schedule() 메서드를 분리하세요. Laravel 10에서는 app/Console/Kernel.php를 편집하면 되고(Laravel 11+에서는 routes/console.php로 이동합니다).

final class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule): void
    {
        $this->scheduleCommon($schedule);

        if ($this->app->environment(AppEnvironmentEnum::PRODUCTION->value)) {
            $this->scheduleProduction($schedule);
        }

        if ($this->app->environment(AppEnvironmentEnum::STAGING->value)) {
            // Less frequent or reduced workload for staging
            $this->scheduleStaging($schedule);
        }
    }
}

구조화 방법

공통 스케줄

모든 환경에서 실행되어야 할 작업: 정리, 가지치기, 기본 유지보수.

private function scheduleCommon(Schedule $schedule): void
{
    // Keep DB lean everywhere
    $schedule->command(PruneCommand::class, ['--model' => [CartItem::class]])->hourly();
    $schedule->command(PruneCommand::class, ['--model' => [OrderItemNotification::class]])->daily();

    // Clear expired tokens
    $schedule->command(PruneExpired::class)->dailyAt('03:00');

    // Basic notifications
    $schedule->command(TelegramNotificationSendCommand::class)->hourly()->withoutOverlapping();
    $schedule->command(UserSendConfirmationNotificationCommand::class)->hourly();
}

프로덕션 스케줄

자주 실행되어야 하고 충분한 리소스를 가진 무거운 작업들.

private function scheduleProduction(Schedule $schedule): void
{
    // Search index sync
    $schedule->command(SearchIndexSyncDiff::class)->dailyAt('05:00');

    // Auto price exports - multiple times per day
    $schedule->command(PriceExportAutoDispatchCommand::class)
             ->cron('15 6,8,10,12,14,16,18 * * *');

    // Supplier sync - runs 7 times per day
    $schedule->command(TmSyncPriceExportProduct::class)
             ->cron('1 6,8,10,12,14,16,18 * * *');

    // Product availability updates
    $schedule->command(ProductAvailabilityUpdateCommand::class)
             ->hourlyAt(15)
             ->between('08:00', '19:00');
}

스테이징 스케줄

동일한 명령이지만 덜 자주 실행—테스트에 실제로 필요한 것만.

private function scheduleStaging(Schedule $schedule): void
{
    // Price exports - once per day is enough
    $schedule->command(PriceExportAutoDispatchCommand::class)
             ->cron('15 6 * * *');

    // Supplier sync - once per day
    $schedule->command(TmSyncPriceExportProduct::class)
             ->cron('1 6 * * *');

    // Test suspended orders more frequently for debugging
    $schedule->command(OrderProcessSuspendedCommand::class)
             ->everyMinute()
             ->between('08:00', '19:00')
             ->withoutOverlapping();
}

결과

워크로드를 분리한 뒤 스테이징 서버의 CPU 사용량은 15‑20 %로 떨어졌고 메모리 사용량은 약 1.5 GB 수준으로 안정화되었습니다.

환경CPU 사용량메모리 사용량
Staging (before)100 %3.2 GB / 4 GB
Staging (after)15‑20 %1.5 GB / 4 GB

프로덕션은 영향 없이 정상적으로 전력 가동을 유지했습니다.

결론

처음부터 환경별로 작업 스케줄을 분리하세요. 스테이징에서는 필수 유지보수 작업만 실행하고 무거운 작업은 제한하세요. 프로덕션 스케줄을 그대로 스테이징에 복사하면 금방 리소스가 고갈됩니다. 서버 자원에 맞게 빈도를 조정하면 인프라가 건강하게 유지됩니다.

Back to Blog

관련 글

더 보기 »