diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java
index 32a2fa5..99aa979 100644
--- a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java
+++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusRepository.java
@@ -83,11 +83,13 @@ public interface ApiStatusRepository extends BaseRepository {
);
/**
- * 판정 구간 내 오류율 0% 초과 API 목록. 개발 모드(-Deai.systemmode=D) 진단 로그 전용.
+ * 판정 구간에 트래픽이 있는 API 의 오류율·지연 판정값. 개발 모드(-Deai.systemmode=D) 진단 로그 전용.
*
* {@code findApiStatusEvents} 는 임계값을 넘고 상태 전이까지 일어난 API 만 돌려주므로
- * "오류는 나는데 왜 장애로 안 잡히나" 를 볼 수 없다. 이 쿼리는 임계값·현재 상태와 무관하게
- * 집계된 오류율을 그대로 보여준다.
+ * "오류는 나는데 왜 장애로 안 잡히나", "느린데 왜 지연이 아닌가" 를 볼 수 없다. 이 쿼리는
+ * 임계값·현재 상태와 무관하게 집계값을 그대로 보여준다.
+ *
+ * 오류가 0건이어도 지연 판정값은 확인해야 하므로 오류 발생 여부로 거르지 않는다.
*
* 컬럼 alias 는 반드시 따옴표로 감싼 camelCase 로 둔다. 인터페이스 프로젝션은 tuple
* alias 를 getter 프로퍼티명으로 찾는데, 따옴표가 없으면 Oracle 이 alias 를 대문자로 올려
@@ -95,15 +97,22 @@ public interface ApiStatusRepository extends BaseRepository {
* (단일 단어는 대소문자만 달라 우연히 매칭된다).
*/
@Query(nativeQuery = true, value =
- " SELECT S.API_NAME AS \"eaisvcname\""
+ " SELECT N.API_NAME AS \"eaisvcname\""
+ " , NVL(B.STATUS_CODE,'N') AS \"statusCode\""
- + " , S.TOTAL AS \"total\""
- + " , S.SUCCESS AS \"success\""
- + " , ROUND((S.TOTAL - S.SUCCESS) * 100 / S.TOTAL, 2) AS \"errorRate\""
- + " , ROUND(S.RESP_SUM / S.TOTAL, 2) AS \"avgRespTime\""
+ + " , NVL(S.TOTAL,0) AS \"total\""
+ + " , NVL(S.SUCCESS,0) AS \"success\""
+ + " , CASE WHEN NVL(S.TOTAL,0) > 0 THEN ROUND((S.TOTAL - S.SUCCESS) * 100 / S.TOTAL, 2) END AS \"errorRate\""
+ + " , CASE WHEN NVL(S.TOTAL,0) > 0 THEN ROUND(S.RESP_SUM / S.TOTAL, 2) END AS \"avgRespTime\""
+ " , NVL(D.TOTAL,0) AS \"delayTotal\""
+ " , CASE WHEN NVL(D.TOTAL,0) > 0 THEN ROUND(D.RESP_SUM / D.TOTAL, 2) END AS \"delayAvgRespTime\""
- + " FROM (SELECT API_NAME"
+ // 두 창 중 넓은 쪽으로 대상 API 를 먼저 뽑는다. 오류 창만 기준으로 잡으면 오류 창에 거래가
+ // 없을 때 지연 판정값까지 통째로 사라진다 (창 길이가 서로 다르다)
+ + " FROM (SELECT DISTINCT API_NAME"
+ + " FROM API_STATS_MINUTE"
+ + " WHERE TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
+ + " BETWEEN TO_CHAR(SYSDATE - NUMTODSINTERVAL(GREATEST(:errorRangeMinute, :delayRangeMinute),'MINUTE'),'YYYYMMDDHH24MI')"
+ + " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI')) N"
+ + " LEFT OUTER JOIN (SELECT API_NAME"
+ " , NVL(SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT),0) AS TOTAL"
+ " , NVL(SUM(SUCCESS_CNT),0) AS SUCCESS"
+ " , NVL(SUM((SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT) * AVG_RESP_TIME),0) AS RESP_SUM"
@@ -111,7 +120,7 @@ public interface ApiStatusRepository extends BaseRepository {
+ " WHERE TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
+ " BETWEEN TO_CHAR(SYSDATE - NUMTODSINTERVAL(:errorRangeMinute,'MINUTE'),'YYYYMMDDHH24MI')"
+ " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI')"
- + " GROUP BY API_NAME) S"
+ + " GROUP BY API_NAME) S ON N.API_NAME = S.API_NAME"
// 지연은 판정 구간이 따로다(delayRangeMinute). 오류 구간 평균으로 비교하면 실제 판정과 어긋난다
+ " LEFT OUTER JOIN (SELECT API_NAME"
+ " , NVL(SUM(SUCCESS_CNT + TIMEOUT_CNT + SYSTEM_ERR_CNT + BIZ_ERR_CNT),0) AS TOTAL"
@@ -120,11 +129,11 @@ public interface ApiStatusRepository extends BaseRepository {
+ " WHERE TO_CHAR(STAT_TIME,'YYYYMMDDHH24MI')"
+ " BETWEEN TO_CHAR(SYSDATE - NUMTODSINTERVAL(:delayRangeMinute,'MINUTE'),'YYYYMMDDHH24MI')"
+ " AND TO_CHAR(SYSDATE,'YYYYMMDDHH24MI')"
- + " GROUP BY API_NAME) D ON S.API_NAME = D.API_NAME"
- + " LEFT OUTER JOIN API_STATUS B ON S.API_NAME = B.EAISVCNAME"
- + " WHERE S.TOTAL > 0"
- + " AND S.TOTAL - S.SUCCESS > 0"
- + " ORDER BY (S.TOTAL - S.SUCCESS) / S.TOTAL DESC, S.API_NAME")
+ + " GROUP BY API_NAME) D ON N.API_NAME = D.API_NAME"
+ + " LEFT OUTER JOIN API_STATUS B ON N.API_NAME = B.EAISVCNAME"
+ // 두 창 중 어느 쪽이든 거래가 있으면 찍는다 (오류 0건, 오류 창 무거래 모두 포함)
+ + " WHERE NVL(S.TOTAL,0) > 0 OR NVL(D.TOTAL,0) > 0"
+ + " ORDER BY CASE WHEN NVL(S.TOTAL,0) > 0 THEN (S.TOTAL - S.SUCCESS) / S.TOTAL ELSE -1 END DESC, N.API_NAME")
List findApiErrorRates(
@Param("errorRangeMinute") int errorRangeMinute,
@Param("delayRangeMinute") int delayRangeMinute
diff --git a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java
index 3587df3..c6de265 100644
--- a/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java
+++ b/src/main/java/com/eactive/eai/rms/ext/djb/apistatus/ApiStatusService.java
@@ -99,11 +99,14 @@ public class ApiStatusService {
/**
- * 오류율 0% 초과 API 목록을 debug 로그로 남긴다. 개발 모드(-Deai.systemmode=D) 에서만 동작한다.
+ * 판정 구간에 거래가 있는 API 의 오류율·지연 판정값을 debug 로그로 남긴다.
+ * 개발 모드(-Deai.systemmode=D) 에서만 동작한다.
*
- * 상태 전이 조건(정상/지연 → 장애)을 못 넘긴 API 는 {@code findApiStatusEvents} 결과에
- * 나오지 않아 "오류는 나는데 장애로 안 잡힌다" 를 로그로 확인할 수 없다. 임계값·현재 상태와
- * 무관하게 집계된 오류율을 그대로 찍어 판정 결과와 대조할 수 있게 한다.
+ * 상태 전이 조건을 못 넘긴 API 는 {@code findApiStatusEvents} 결과에 나오지 않아
+ * "오류는 나는데 장애로 안 잡힌다", "느린데 지연이 아니다" 를 로그로 확인할 수 없다.
+ * 임계값·현재 상태와 무관하게 집계값을 그대로 찍어 판정 결과와 대조할 수 있게 한다.
+ *
+ * 오류 0건이어도 지연 판정값은 봐야 하므로 거래만 있으면 무조건 찍는다.
*
* 진단 목적이므로 조회 실패가 상태 판정 자체를 막아서는 안 된다 - 예외는 삼킨다.
*/
@@ -115,12 +118,13 @@ public class ApiStatusService {
try {
List rows = apiStatusRepository.findApiErrorRates(errorRangeMinute, delayRangeMinute);
if (rows.isEmpty()) {
- log.debug("오류율 탐지: 최근 {}분간 오류 발생 API 없음", errorRangeMinute);
+ log.debug("상태 판정 진단: 최근 {}분(오류)/{}분(지연) 거래 발생 API 없음",
+ errorRangeMinute, delayRangeMinute);
return;
}
StringBuilder sb = new StringBuilder();
- sb.append("오류율 탐지: 장애 임계 ").append(errorRate).append("% 초과(최근 ").append(errorRangeMinute)
+ sb.append("상태 판정 진단: 장애 임계 ").append(errorRate).append("% 초과(최근 ").append(errorRangeMinute)
.append("분), 지연 임계 ").append(delayAvgRespTime).append("ms 초과(최근 ").append(delayRangeMinute)
.append("분), 대상 ").append(rows.size()).append("건");
@@ -139,7 +143,7 @@ public class ApiStatusService {
log.debug(sb.toString());
} catch (Exception e) {
- log.debug("오류율 탐지 로그 생성 실패", e);
+ log.debug("상태 판정 진단 로그 생성 실패", e);
}
}