From 91d981325374ba70017572d2d47a1c70afff2f38 Mon Sep 17 00:00:00 2001 From: joungmin Date: Mon, 15 Jun 2026 15:39:50 +0900 Subject: [PATCH] =?UTF-8?q?docs(design):=20#331=20VectorService=20batchUpd?= =?UTF-8?q?ate=20=EC=84=A4=EA=B3=84=EC=84=9C=20(Architect)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jdbc.batchUpdate(SqlParameterSource[]) 단일 호출 + IdGenerator.newId() 공통화. 테스트는 본 범위 밖 (#343 후속 테스트 인프라). 설계서: docs/design/331-vector-batch-insert/README.md (Approved) Refs: #331 (Architect) --- docs/design/331-vector-batch-insert/README.md | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 docs/design/331-vector-batch-insert/README.md diff --git a/docs/design/331-vector-batch-insert/README.md b/docs/design/331-vector-batch-insert/README.md new file mode 100644 index 0000000..973dc45 --- /dev/null +++ b/docs/design/331-vector-batch-insert/README.md @@ -0,0 +1,81 @@ +# 설계서: VectorService batch insert + IdGenerator 공통화 (#331) + +> **상태**: Approved +> **작성**: [AI] Architect · **최종수정**: 2026-06-15 +> **추적성** — Redmine: #331 · 부모: #293 (검색/벡터 Reviewer 후속, 09-Done) +> · 구현 파일: `backend-java/src/main/java/com/tasteby/service/VectorService.java` +> · 테스트: 본 이슈 범위 밖 (단위 테스트 인프라 도입은 #343 후속 묶음에 해당) + +## 1. 목적 (Why) + +`VectorService.saveRestaurantVectors`가 chunk N개를 N번의 단건 `jdbc.update`로 처리한다. 현재 `buildChunks`가 1개 청크만 반환해 N=1이지만, 향후 chunk 분할 도입 시 N+1 INSERT 비효율. 또한 UUID 생성 코드가 인라인 변환(`UUID.randomUUID().toString().replace("-", "").substring(0, 32).toUpperCase()`)으로 다른 곳의 `IdGenerator.newId()`와 중복. + +## 2. 범위 + +- **포함** + - `jdbc.batchUpdate(sql, SqlParameterSource[])`로 단일 호출 전환. + - UUID 생성을 `IdGenerator.newId()` 공통 유틸로 교체. +- **제외** + - 단위/통합 테스트 도입 (테스트 인프라 미도입 — 별도 후속 #343 묶음). + - `buildChunks`의 chunk 분할 로직 자체 변경 (현재 단일 청크 정책 유지). + - `restaurant_vectors` 스키마 변경. + +## 3. 인수조건 + +- [ ] `saveRestaurantVectors`가 한 번의 `jdbc.batchUpdate` 호출로 N개 청크 삽입. +- [ ] UUID 인라인 변환 제거 → `IdGenerator.newId()` 호출. +- [ ] 회귀 없음 — 신규 식당 등록 시 `restaurant_vectors`에 정상 row 추가. +- [ ] N=0 가드(`chunks.isEmpty()`)는 유지. + +## 4. 컨텍스트 & 제약 + +- Spring `NamedParameterJdbcTemplate.batchUpdate(String, SqlParameterSource[])` 사용. +- Oracle VECTOR 타입 파라미터는 `float[]`로 그대로 바인딩 가능 (`MapSqlParameterSource.addValue`). +- 한 batch 안 `int[]` 반환 → batch 결과 카운트는 사용하지 않음(throw if 어쩌고 미적용). +- `IdGenerator.newId()` 시그니처: `public static String newId()` → 32-char uppercase hex (현재 인라인과 동일). + +## 5. 아키텍처 개요 + +``` +saveRestaurantVectors(restaurantId, chunks) + ├ if chunks.isEmpty() → return + ├ embeddings = genAi.embedTexts(chunks) + ├ params[] = build N개 MapSqlParameterSource + │ .addValue("id", IdGenerator.newId()) + │ .addValue("rid", restaurantId) + │ .addValue("chunk", chunks.get(i)) + │ .addValue("emb", float[] embeddings[i]) + └ jdbc.batchUpdate(sql, params) +``` + +## 6. 함수 명세 + +| 함수 | 책임 | 비고 | +|------|------|------| +| `VectorService.saveRestaurantVectors(id, chunks)` (수정) | batchUpdate 1회 | IdGenerator 사용 | + +## 7. 흐름 + +1. embed 호출 (기존). +2. `SqlParameterSource[]` 생성. +3. `jdbc.batchUpdate(sql, params)` 단일 호출. + +## 8. 엣지케이스 + +- **chunks 빈 배열**: 조기 return (기존 유지). +- **embed 결과와 chunks 크기 불일치**: 현재 OCI GenAI는 입력 N → 출력 N 보장. 안전 가드 추가는 본 범위 밖 (필요 시 후속). + +## 9. 테스트 (수동만) + +- dev에서 신규 식당 등록(데몬 또는 수동 trigger) → `SELECT count(*) FROM restaurant_vectors WHERE restaurant_id = '...'` 정상 row 확인. + +## 10. 리스크 & 대안 + +- **선택**: `NamedParameterJdbcTemplate.batchUpdate`. 단일 트랜잭션 + 단일 round-trip. +- **대안 A**: `JdbcTemplate.batchUpdate(BatchPreparedStatementSetter)` — 더 저수준이지만 named param 손실. +- **대안 B**: MERGE로 upsert — 동일 restaurant_id 재처리 시 중복 제거 가능. 다만 본 이슈 범위 밖. + +## 11. 미해결 질문 + +- chunk 분할 정책(현재 1개 단일 청크) — 후속 (검색 정확도 vs 토큰 비용 트레이드오프 결정). +- batchUpdate 결과 row 수 검증 — 운영 모니터링 도구 도입 후 결정.