diff --git a/src/main/java/com/interplug/qcast/batch/JobLauncherController.java b/src/main/java/com/interplug/qcast/batch/JobLauncherController.java index 8250c362..9cd27498 100644 --- a/src/main/java/com/interplug/qcast/batch/JobLauncherController.java +++ b/src/main/java/com/interplug/qcast/batch/JobLauncherController.java @@ -110,8 +110,19 @@ public class JobLauncherController { // return "Job " + jobName + " started"; return resultMap; + } catch (JobExecutionAlreadyRunningException e) { + log.warn("Job {} 이 다른 인스턴스에서 이미 실행 중입니다.", jobName); + resultMap.put("code", "ALREADY_RUNNING"); + resultMap.put("message", "Job " + jobName + " is already running on another instance."); + return resultMap; } catch (Exception e) { logExceptionDetails("Manual launch failed", jobName, e); + if (isDeadlockException(e)) { + logDeadlockDetails(jobName, e); + resultMap.put("code", "DEADLOCK"); + resultMap.put("message", "Deadlock detected while executing job " + jobName + ". Please retry."); + return resultMap; + } resultMap.put("code", "FAILED"); resultMap.put("message", e.getMessage()); return resultMap; @@ -291,54 +302,29 @@ public class JobLauncherController { // // return jobName+ " executed successfully"; - // 재시도 로직 추가 - int maxRetries = 3; - int retryCount = 0; - long initialDelay = 60000; // 1분 + JobParameters jobParameters = new JobParametersBuilder() + .addString("jobName", jobName) + .addDate("time", new Date()) + .toJobParameters(); - while (retryCount < maxRetries) { - JobParameters jobParameters = null; - try { - jobParameters = new JobParametersBuilder() - .addString("jobName", jobName) - .addDate("time", new Date()) - .addLong("run.id", System.currentTimeMillis()) - .toJobParameters(); - - log.info("Job {} 실행 시도 (재시도 {}/{})", jobName, retryCount + 1, maxRetries); - JobExecution jobExecution = jobLauncher.run(job, jobParameters); - log.info("Job {} started with execution id {}", jobName, jobExecution.getId()); - - log.info("Job {} 완료 상태: {}", jobName, jobExecution.getExitStatus().getExitCode()); - return jobName + " executed successfully"; - - } catch (Exception e) { - retryCount++; - logExceptionDetails("Scheduled launch failed", jobName, e); - - // 데드락 오류인지 확인 - boolean isDeadlock = isDeadlockException(e); - - if (isDeadlock && retryCount < maxRetries) { - long delay = initialDelay * retryCount; // 1분, 2분, 3분 증가 - log.warn("Deadlock detected for job {}. retry {}/{} in {}ms. params={}", - jobName, retryCount, maxRetries, delay, jobParameters); - - try { - Thread.sleep(delay); - } catch (InterruptedException ie) { - Thread.currentThread().interrupt(); - log.error("Job {} 재시도 중 인터럽트 발생", jobName, ie); - return "Job " + jobName + " interrupted during retry"; - } - } else { - log.error("Job {} 실행 실패 (재시도 {}/{})", jobName, retryCount, maxRetries, e); - return "Error executing job " + jobName + ": " + e.getMessage(); - } + try { + log.info("Job {} 실행 시작", jobName); + JobExecution jobExecution = jobLauncher.run(job, jobParameters); + log.info("Job {} completed with execution id {} and status {}", + jobName, jobExecution.getId(), jobExecution.getExitStatus().getExitCode()); + return jobName + " executed successfully"; + } catch (JobExecutionAlreadyRunningException e) { + log.warn("Job {} 이 다른 인스턴스에서 이미 실행 중입니다. 스킵합니다.", jobName); + return "Job " + jobName + " is already running on another instance. Skipped."; + } catch (Exception e) { + logExceptionDetails("Scheduled launch failed", jobName, e); + if (isDeadlockException(e)) { + logDeadlockDetails(jobName, e); + return "Deadlock detected while executing job " + jobName + ". Manual retry required."; } + log.error("Job {} 실행 실패", jobName, e); + return "Error executing job " + jobName + ": " + e.getMessage(); } - - return "Job " + jobName + " failed after " + maxRetries + " retries"; } /** @@ -402,6 +388,38 @@ public class JobLauncherController { } } + private void logDeadlockDetails(String jobName, Exception e) { + log.error("===== [DEADLOCK] Job: {} 데드락 상세 정보 =====", jobName); + + Throwable current = e; + int depth = 0; + while (current != null && depth < 10) { + log.error("[DEADLOCK] Exception chain [{}]: {} - {}", + depth, current.getClass().getName(), current.getMessage()); + + if (current instanceof SQLException) { + SQLException sqlEx = (SQLException) current; + log.error("[DEADLOCK] SQLState: {}, ErrorCode: {}, Message: {}", + sqlEx.getSQLState(), sqlEx.getErrorCode(), sqlEx.getMessage()); + + // SQLException 체인의 next exception도 확인 + SQLException nextEx = sqlEx.getNextException(); + int sqlDepth = 0; + while (nextEx != null && sqlDepth < 10) { + log.error("[DEADLOCK] SQL NextException [{}]: SQLState={}, ErrorCode={}, Message={}", + sqlDepth, nextEx.getSQLState(), nextEx.getErrorCode(), nextEx.getMessage()); + nextEx = nextEx.getNextException(); + sqlDepth++; + } + } + current = current.getCause(); + depth++; + } + + log.error("[DEADLOCK] Full stack trace:", e); + log.error("===== [DEADLOCK] Job: {} 데드락 상세 정보 끝 =====", jobName); + } + private boolean isDeadlockException(Throwable throwable) { Throwable root = throwable; while (root.getCause() != null) { @@ -409,10 +427,16 @@ public class JobLauncherController { } if (root instanceof SQLException) { SQLException sqlEx = (SQLException) root; - return "40001".equals(sqlEx.getSQLState()) || sqlEx.getErrorCode() == 1205; + if ("40001".equals(sqlEx.getSQLState()) || sqlEx.getErrorCode() == 1205) { + return true; + } } String message = root.getMessage(); - return message != null && message.toLowerCase().contains("deadlock"); + if (message == null) return false; + String lowerMessage = message.toLowerCase(); + return lowerMessage.contains("deadlock") + || message.contains("デッドロック") + || message.contains("교착 상태"); } private String buildSqlExceptionChain(SQLException sqlEx) { diff --git a/src/main/resources/template/excel/excel_download_quotation_detail_template.xlsx b/src/main/resources/template/excel/excel_download_quotation_detail_template.xlsx index 5602c4d3..66f32a69 100644 Binary files a/src/main/resources/template/excel/excel_download_quotation_detail_template.xlsx and b/src/main/resources/template/excel/excel_download_quotation_detail_template.xlsx differ diff --git a/src/main/resources/template/excel/excel_download_simulation_detail_template.xlsx b/src/main/resources/template/excel/excel_download_simulation_detail_template.xlsx index b15b0cf3..31064125 100644 Binary files a/src/main/resources/template/excel/excel_download_simulation_detail_template.xlsx and b/src/main/resources/template/excel/excel_download_simulation_detail_template.xlsx differ