perf(parse): #326 parseJson truncated-array O(N²) → 단일 패스
OciGenAiService.parseJson: - 잘린 배열 복구 시 각 idx에서 end를 1씩 늘려 readValue 시도하던 O(N²) 로직 제거 - findObjectEnd: brace depth counter (문자열/escape 처리) 단일 패스 O(N) - 8192 token 응답 처리 시간 수백 ms → 10ms 이하 예상 - 매 try마다 Jackson 예외 객체/스택트레이스 생성하던 부담 제거 설계서: docs/design/326-parsejson-optimization/README.md (Approved) Refs: #326
This commit is contained in:
@@ -150,26 +150,25 @@ public class OciGenAiService {
|
||||
return mapper.readValue(raw, Object.class);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
// Try to recover truncated array
|
||||
// #326 — Recover truncated array. Brace depth counter로 단일 패스 O(N).
|
||||
// 이전: 각 idx에서 end를 1씩 늘려가며 매번 readValue → O(N²) + 예외 스택트레이스 양산.
|
||||
if (raw.trim().startsWith("[")) {
|
||||
List<Object> items = new ArrayList<>();
|
||||
int idx = raw.indexOf('[') + 1;
|
||||
while (idx < raw.length()) {
|
||||
while (idx < raw.length() && " \t\n\r,".indexOf(raw.charAt(idx)) >= 0) idx++;
|
||||
if (idx >= raw.length() || raw.charAt(idx) == ']') break;
|
||||
if (raw.charAt(idx) != '{') break; // 객체 시작이 아니면 복구 중단
|
||||
|
||||
// Try to parse next object
|
||||
boolean found = false;
|
||||
for (int end = idx + 1; end <= raw.length(); end++) {
|
||||
try {
|
||||
Object obj = mapper.readValue(raw.substring(idx, end), Object.class);
|
||||
items.add(obj);
|
||||
idx = end;
|
||||
found = true;
|
||||
break;
|
||||
} catch (Exception ignored2) {}
|
||||
int end = findObjectEnd(raw, idx);
|
||||
if (end < 0) break; // 잘린 객체 — 거기서 멈춤
|
||||
try {
|
||||
Object obj = mapper.readValue(raw.substring(idx, end + 1), Object.class);
|
||||
items.add(obj);
|
||||
} catch (Exception ignored2) {
|
||||
break; // 불가해 객체 — 멈춤
|
||||
}
|
||||
if (!found) break;
|
||||
idx = end + 1;
|
||||
}
|
||||
if (!items.isEmpty()) {
|
||||
log.info("Recovered {} items from truncated JSON", items.size());
|
||||
@@ -179,4 +178,27 @@ public class OciGenAiService {
|
||||
|
||||
throw new RuntimeException("JSON parse failed: " + raw.substring(0, Math.min(80, raw.length())));
|
||||
}
|
||||
|
||||
/**
|
||||
* #326 — JSON 객체 시작 위치(`{`)에서 매칭되는 닫는 `}` 인덱스를 반환.
|
||||
* 문자열 안의 `{` `}`와 escape는 무시. 매칭 못 찾으면 -1.
|
||||
*/
|
||||
private static int findObjectEnd(String raw, int start) {
|
||||
int depth = 0;
|
||||
boolean inString = false;
|
||||
boolean escaped = false;
|
||||
for (int i = start; i < raw.length(); i++) {
|
||||
char c = raw.charAt(i);
|
||||
if (escaped) { escaped = false; continue; }
|
||||
if (c == '\\') { escaped = true; continue; }
|
||||
if (c == '"') { inString = !inString; continue; }
|
||||
if (inString) continue;
|
||||
if (c == '{') depth++;
|
||||
else if (c == '}') {
|
||||
depth--;
|
||||
if (depth == 0) return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user