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); } }