db = \Config\Database::connect(); $logModel = model(NaverWorkerLogModel::class); $naverService = new \App\Services\NaverService(); // 서비스 생성 $redis = new \Redis(); try { // 두 가지 환경 변수 형식 지원 (REDIS_HOST 또는 redis.default.host) $this->redisHost = getenv('REDIS_HOST') ?: (getenv('redis.default.host') ?: '127.0.0.1'); $this->redisPort = getenv('REDIS_PORT') ?: (getenv('redis.default.port') ?: 6379); $this->redisDatabase = getenv('REDIS_DATABASE') ?: (getenv('redis.default.database') ?: 9); $redis->connect($this->redisHost, (int)$this->redisPort); $redis->select((int)$this->redisDatabase); CLI::write(CLI::color('🟢 Naver Worker running... (Redis: ' . $this->redisHost . ':' . $this->redisPort . ' DB:' . $this->redisDatabase . ')', 'green')); } catch (\Exception $e) { CLI::error("Redis 연결 불가: " . $e->getMessage()); return; } while (true) { // Redis brPop with retry logic $maxRetries = 3; $retryCount = 0; $result = null; while ($retryCount < $maxRetries) { try { $result = $redis->brPop(['naver:raw_queue'], 30); break; // 성공하면 루프 탈출 } catch (\RedisException $e) { $retryCount++; CLI::write(CLI::color("⚠️ Redis error (attempt {$retryCount}/{$maxRetries}): " . $e->getMessage(), 'yellow')); if ($retryCount >= $maxRetries) { CLI::error("❌ Redis reconnection failed after {$maxRetries} attempts"); break; } // Redis 재연결 시도 try { CLI::write(CLI::color('🔄 Reconnecting to Redis...', 'yellow')); $redis->close(); $redis->connect($this->redisHost, (int)$this->redisPort); $redis->select((int)$this->redisDatabase); CLI::write(CLI::color('✅ Redis reconnected', 'green')); } catch (\Exception $reconnectError) { CLI::error("Redis reconnection error: " . $reconnectError->getMessage()); sleep(2); // 재연결 실패 시 잠시 대기 } } } if (!$result) { // 데이터가 없어서 타임아웃 난 경우. // 굳이 sleep 안 해도 바로 다음 brPop이 다시 30초 대기를 시작함. continue; } if ($result) { $rawData = $result[1]; // [1] 꺼내자마자 DB에 원문 저장 (2차 임시 저장) - 실패 시 재시도 try { $logId = $logModel->insert([ 'raw_payload' => $rawData, 'status' => 'INIT' ]); } catch (\CodeIgniter\Database\Exceptions\DatabaseException $e) { // MySQL gone away 에러 시 재연결 후 재시도 if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) { CLI::write(CLI::color('⚠️ MySQL gone away, reconnecting...', 'yellow')); $this->db->close(); $this->db = \Config\Database::connect(); $logModel = model(NaverWorkerLogModel::class); // 재시도 $logId = $logModel->insert([ 'raw_payload' => $rawData, 'status' => 'INIT' ]); } else { throw $e; // 다른 에러면 그대로 throw } } try { $responseJson = json_decode($result[1], true); $payload = $responseJson['request_data'] ?? []; if (empty($payload)) { throw new \Exception("빈 페이로드 데이터"); } // 서비스의 함수 하나로 모든 처리 완료 $insertId = $naverService->processArticle($payload); // [3] 성공 시 로그 업데이트 (재연결 처리 포함) $this->safeUpdateLog($logModel, $logId, [ 'atcl_no' => $payload['articleNumber'] ?? null, 'status' => 'SUCCESS', 'target_db_id' => $insertId ]); CLI::write("✅ Success! DB ID: $insertId", 'cyan'); } catch (\Exception $e) { CLI::error("❌ Task Failed: " . $e->getMessage()); // 실패 로그는 여기서 남김 // 1. DB 상태를 FAIL로 업데이트 (필수) (재연결 처리 포함) $this->safeUpdateLog($logModel, $logId, [ 'status' => 'FAIL', 'error_msg' => $e->getMessage() ]); // 2. Redis 실패 큐에 백업 (선택 - 나중에 모아서 다시 던질 때 편함) $redis->lPush('naver:failed_queue', $rawData); helper('log'); write_custom_log("FAILED_DATA | Error: " . $e->getMessage(), 'ERROR', 'failed'); // 루프 과부하 방지 (연속 에러 시) sleep(1); } } } } /** * MySQL gone away 에러 발생 시 재연결 후 재시도하는 안전한 update */ protected function safeUpdateLog($logModel, $logId, $data) { try { return $logModel->update($logId, $data); } catch (\CodeIgniter\Database\Exceptions\DatabaseException $e) { if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) { CLI::write(CLI::color('⚠️ MySQL gone away on update, reconnecting...', 'yellow')); $this->db->close(); $this->db = \Config\Database::connect(); $logModel = model(\App\Models\Entities\NaverWorkerLogModel::class); // 재시도 return $logModel->update($logId, $data); } else { throw $e; } } } }