[Developer] #567 filter recent SQL cursors in Java

This commit is contained in:
devmrko
2026-07-01 15:06:41 +09:00
parent 1c66b973a7
commit d301e4dad1
4 changed files with 71 additions and 31 deletions

View File

@@ -45,6 +45,7 @@ public class OrdsProbeService {
private static final TypeReference<Map<String, Object>> MAP_TYPE = new TypeReference<>() {
};
private static final Logger log = LoggerFactory.getLogger(OrdsProbeService.class);
private static final int EXECUTION_EVIDENCE_LOOKBACK_MINUTES = 15;
private record ExecutionEvidenceLookup(
SqlExecutionEvidence evidence,
@@ -52,6 +53,18 @@ public class OrdsProbeService {
) {
}
private record RecentSqlCursor(
String sqlId,
int childNumber,
String sqlFulltext,
String lastActiveAt,
long executions,
long rowsProcessed,
long elapsedMillis,
long bufferGets
) {
}
private final BearerTokenService tokenService;
private final ProtectedObjectService protectedObjectService;
private final AuditService auditService;
@@ -260,8 +273,8 @@ public class OrdsProbeService {
+ "V$SQL과 DBMS_XPLAN은 일반 데이터 접근과 별도인 SYS 진단 권한입니다.");
}
return result.withExecutionEvidence(null,
"최근 2분 내 이 보호 대상의 실행 cursor를 찾지 못했습니다. "
+ "해당 ORDS 요청이 DB까지 실행됐는지 확인하세요.");
"최근 15분의 최신 실행 cursor 10건에서 이 보호 대상을 찾지 못했습니다. "
+ "ORDS 응답 지연 또는 shared pool 교체로 증적이 남지 않았을 수 있습니다.");
}
String message = evidence.hasPredicatePlan() ? null
: "SQL_ID는 찾았지만 DBMS_XPLAN Predicate Information을 읽지 못했습니다. "
@@ -299,9 +312,7 @@ public class OrdsProbeService {
JdbcTemplate evidenceJdbcTemplate,
ProtectedObject object
) {
String marker = "%FROM%" + object.owner().toUpperCase(Locale.ROOT)
+ "." + object.objectName().toUpperCase(Locale.ROOT) + "%";
return evidenceJdbcTemplate.query("""
List<RecentSqlCursor> candidates = evidenceJdbcTemplate.query("""
SELECT sql_id,
child_number,
sql_fulltext,
@@ -320,31 +331,49 @@ public class OrdsProbeService {
elapsed_time,
buffer_gets
FROM v$sql
WHERE UPPER(sql_text) LIKE ?
AND last_active_time >= SYSTIMESTAMP - INTERVAL '2' MINUTE
WHERE last_active_time >= SYSTIMESTAMP - NUMTODSINTERVAL(?, 'MINUTE')
ORDER BY last_active_time DESC
)
WHERE ROWNUM = 1
""", resultSet -> {
if (!resultSet.next()) {
return null;
}
String sqlId = resultSet.getString("sql_id");
int childNumber = resultSet.getInt("child_number");
String predicatePlan = findPredicatePlan(evidenceJdbcTemplate, sqlId, childNumber);
return new SqlExecutionEvidence(
sqlId,
childNumber,
resultSet.getString("sql_fulltext"),
resultSet.getTimestamp("last_active_time") == null
? null : resultSet.getTimestamp("last_active_time").toLocalDateTime().toString(),
resultSet.getLong("executions"),
resultSet.getLong("rows_processed"),
Math.round(resultSet.getLong("elapsed_time") / 1000.0d),
resultSet.getLong("buffer_gets"),
predicatePlan
);
}, marker);
WHERE ROWNUM <= 10
""", (resultSet, rowNum) -> new RecentSqlCursor(
resultSet.getString("sql_id"),
resultSet.getInt("child_number"),
resultSet.getString("sql_fulltext"),
resultSet.getTimestamp("last_active_time") == null
? null : resultSet.getTimestamp("last_active_time").toLocalDateTime().toString(),
resultSet.getLong("executions"),
resultSet.getLong("rows_processed"),
Math.round(resultSet.getLong("elapsed_time") / 1000.0d),
resultSet.getLong("buffer_gets")
), EXECUTION_EVIDENCE_LOOKBACK_MINUTES);
return candidates.stream()
.filter(candidate -> referencesProtectedObject(candidate.sqlFulltext(), object))
.findFirst()
.map(candidate -> new SqlExecutionEvidence(
candidate.sqlId(),
candidate.childNumber(),
candidate.sqlFulltext(),
candidate.lastActiveAt(),
candidate.executions(),
candidate.rowsProcessed(),
candidate.elapsedMillis(),
candidate.bufferGets(),
findPredicatePlan(evidenceJdbcTemplate, candidate.sqlId(), candidate.childNumber())
))
.orElse(null);
}
static boolean referencesProtectedObject(String sqlFulltext, ProtectedObject object) {
if (sqlFulltext == null || object == null) {
return false;
}
String normalizedSql = sqlFulltext.toUpperCase(Locale.ROOT)
.replace("\"", "")
.replaceAll("\\s+", "");
String objectName = (object.owner() + "." + object.objectName())
.toUpperCase(Locale.ROOT);
return normalizedSql.contains(objectName);
}
private String findPredicatePlan(

View File

@@ -112,7 +112,7 @@
<div class="section-heading compact-heading">
<div>
<h3>DB 실행 증적</h3>
<p class="section-subtitle">최근 2분 내 같은 보호 대상에 실행된 ORDS SQL을 SQL_ID로 확인한 결과입니다.</p>
<p class="section-subtitle">최근 15분의 최신 DB cursor 10건에서 같은 보호 대상을 찾아 SQL_ID로 확인한 결과입니다.</p>
</div>
<span class="badge text-bg-success" th:text="${'SQL_ID ' + result.executionEvidence().sqlId()}">SQL_ID</span>
</div>