186 lines
7.1 KiB
PHP
186 lines
7.1 KiB
PHP
<?php
|
|
|
|
use Illuminate\Foundation\Inspiring;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Schedule;
|
|
use Illuminate\Console\Scheduling\Event;
|
|
|
|
/**
|
|
* -----------------------------------------------------------------------------
|
|
* Gifticon Platform - Console Routes / Scheduler Registry
|
|
* -----------------------------------------------------------------------------
|
|
*
|
|
* [운영/개발 공통] 스케줄/워커/큐 동작 확인 치트시트
|
|
*
|
|
* ✅ 스케줄 등록 목록 확인
|
|
* docker exec -it gifticon-app php artisan schedule:list
|
|
*
|
|
* ✅ 스케줄러 컨테이너 프로세스 확인
|
|
* docker exec -it gifticon-scheduler ps aux
|
|
*
|
|
* ✅ 워커 컨테이너 프로세스 확인
|
|
* docker exec -it gifticon-worker ps aux
|
|
*
|
|
* ✅ 워커 하트비트 로그 확인 (워커가 큐를 소비하고 있음을 증명)
|
|
* docker exec -it gifticon-app sh -lc "tail -n 200 storage/logs/laravel.log | grep '\[worker-heartbeat\]' | tail -n 20"
|
|
*
|
|
* ✅ 큐 길이 확인 (default 큐)
|
|
* docker exec -it gifticon-redis redis-cli LLEN queues:default
|
|
*
|
|
* ✅ 실패 잡 확인
|
|
* docker exec -it gifticon-app php artisan queue:failed
|
|
*
|
|
* -----------------------------------------------------------------------------
|
|
* [장애 대응 가이드]
|
|
* -----------------------------------------------------------------------------
|
|
* 1) "메일은 되는데 워커가 이상" / "큐가 쌓이기만 함"
|
|
* - queue:failed 확인 → 실패가 쌓이면 코드/설정 오류 가능
|
|
* - LLEN queues:default 확인 → 계속 증가하면 워커가 소비 못함
|
|
* - 워커를 일시적으로 디버그 모드로 실행(컨테이너에서):
|
|
* docker exec -it gifticon-worker php artisan queue:work -vvv --tries=1 --timeout=90
|
|
*
|
|
* 2) "스케줄이 안 돈다"
|
|
* - schedule:list로 등록 여부 확인
|
|
* - gifticon-scheduler 컨테이너가 schedule:work로 떠 있는지 확인
|
|
* - 스케줄은 등록됐는데 실행 흔적이 없다면: 로그/권한/환경(APP_ENV) 분기 확인
|
|
*
|
|
* 3) "Redis 연결 이상"
|
|
* - 컨테이너 내부에서 Redis 연결 테스트:
|
|
* docker exec -it gifticon-worker php -r '$r=new Redis(); var_dump($r->connect("gifticon-redis", 6379, 1)); echo $r->ping().PHP_EOL;'
|
|
*
|
|
* -----------------------------------------------------------------------------
|
|
* 운영 안전장치(추천)
|
|
* -----------------------------------------------------------------------------
|
|
* - 테스트용 스케줄은 APP_ENV=local|development에서만 등록되도록 제한
|
|
* - 운영에서 불필요한 스케줄 로그가 쌓이는 사고 예방
|
|
*/
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// 공통: 스케줄 등록 헬퍼 (cron 문자열 기반)
|
|
// -----------------------------------------------------------------------------
|
|
function registerScheduleCron(string $name, string $cron, $job, array $opt = []): Event
|
|
{
|
|
$tz = $opt['timezone'] ?? config('app.timezone', 'Asia/Seoul');
|
|
$lock = $opt['without_overlapping'] ?? true;
|
|
|
|
$event = is_callable($job)
|
|
? Schedule::call(function () use ($name, $job) {
|
|
try {
|
|
Log::info("[schedule:$name] start");
|
|
$job();
|
|
Log::info("[schedule:$name] done");
|
|
} catch (\Throwable $e) {
|
|
Log::error("[schedule:$name] failed", [
|
|
'err' => $e->getMessage(),
|
|
]);
|
|
throw $e;
|
|
}
|
|
})
|
|
: Schedule::command((string) $job);
|
|
|
|
// 중요: withoutOverlapping 전에 name 필수
|
|
$event->name($name);
|
|
|
|
$event->cron($cron)->timezone($tz);
|
|
|
|
// 운영 옵션
|
|
if ($lock) $event->withoutOverlapping();
|
|
if (!empty($opt['on_one_server'])) $event->onOneServer();
|
|
if (!empty($opt['run_in_background'])) $event->runInBackground();
|
|
|
|
// 실패 시 로그 남기기 (Laravel 12에서도 지원)
|
|
// NOTE: 잡 실패를 즉시 알기 좋음
|
|
$event->onFailure(function (\Throwable $e) use ($name) {
|
|
Log::error("[schedule:$name] onFailure", [
|
|
'err' => $e->getMessage(),
|
|
]);
|
|
});
|
|
|
|
return $event;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// 운영용(Production 포함): 워커/스케줄러 생존 확인용 Heartbeat
|
|
// - 스케줄러가 매 분 실행
|
|
// - 큐로 잡을 던지고
|
|
// - 워커가 처리하면 로그가 찍힘
|
|
// => scheduler + redis + worker 전체 체인이 정상임을 증명
|
|
// -----------------------------------------------------------------------------
|
|
|
|
//Schedule::call(function () {
|
|
// dispatch(new \App\Jobs\WorkerHeartbeatJob());
|
|
//})
|
|
// ->everyMinute()
|
|
// ->name('worker_heartbeat_dispatch')
|
|
// ->withoutOverlapping();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Artisan 커맨드 (개발/운영 공통) - 단발성 테스트 도구 호출용
|
|
// docker exec -it gifticon-app php artisan inspire
|
|
// docker exec -it gifticon-app php artisan tick
|
|
// -----------------------------------------------------------------------------
|
|
Artisan::command('inspire', function () {
|
|
$this->comment(Inspiring::quote());
|
|
})->purpose('Display an inspiring quote');
|
|
|
|
Artisan::command('tick', function () {
|
|
Log::info('[tick] artisan command ok '.now());
|
|
$this->info('tick ok: '.now());
|
|
})->purpose('Quick artisan health check');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// 개발 전용 스케줄/테스트 스케줄 (운영에서 로그 쌓이는 사고 방지)
|
|
// -----------------------------------------------------------------------------
|
|
$env = (string) config('app.env', 'production');
|
|
$isDev = in_array($env, ['local', 'development', 'dev', 'testing'], true);
|
|
|
|
if ($isDev) {
|
|
// "스케줄러가 실제로 돌아가는지" 확인용 - 운영에서는 불필요
|
|
// Schedule::call(function () {
|
|
// Log::info('[schedule-dev] alive '.now());
|
|
// })
|
|
// ->everyMinute()
|
|
// ->name('dev_scheduler_alive')
|
|
// ->withoutOverlapping();
|
|
//
|
|
// // 예시: 5분마다 실행
|
|
// registerScheduleCron('every_5m_log', '*/5 * * * *', function () {
|
|
// Log::info('[job-dev] every 5 minutes '.now());
|
|
// });
|
|
//
|
|
// // 예시: 매일 새벽 2시 10분
|
|
// registerScheduleCron('every_day_0210_log', '10 2 * * *', function () {
|
|
// Log::info('[job-dev] daily 02:10 '.now());
|
|
// });
|
|
//
|
|
// // 예시: 매월 1일 03:30
|
|
// registerScheduleCron('monthly_1st_0330', '30 3 1 * *', function () {
|
|
// Log::info('[job-dev] monthly 1st 03:30 '.now());
|
|
// });
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// 운영용 스케줄은 여기 아래에 "명시적으로" 추가해라.
|
|
// - 개발 예시와 섞으면 운영에서 실수할 가능성이 커짐.
|
|
// - registerScheduleCron()으로 이름 통일해서 관리.
|
|
// -----------------------------------------------------------------------------
|
|
|
|
// 예시(운영): 정산 배치
|
|
// registerScheduleCron('settlement_daily', '0 4 * * *', 'app:settlement:daily', [
|
|
// 'without_overlapping' => true,
|
|
// 'on_one_server' => true,
|
|
// ]);
|
|
|