Compare commits

...

1 Commits

Author SHA1 Message Date
joungmin
40e448fe95 fix(search): WebSearchService HTTP timeout 추가 (connect 5s, request 15s)
- 특정 검색에서 무한 hang → backend virtual thread 점유로 후속 벌크 작업 중단
- Naver/DDG 둘 다 timeout 적용
- 타임아웃 시 HttpTimeoutException → 호출자(bulk)에서 notfound 안전 처리

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-15 21:52:31 +09:00
2 changed files with 10 additions and 0 deletions

View File

@@ -6,6 +6,11 @@
## 2026-06-15 ## 2026-06-15
### 🐛 WebSearchService HTTP timeout 추가 (v0.1.49)
- 벌크 백필 중 특정 검색에서 무한 hang → backend executor virtual thread 점유로 후속 작업 중단 (90건 처리 후 멈춤)
- connectTimeout=5s + request timeout=15s (Naver/DDG 둘 다)
- 해당 식당은 HttpTimeoutException → notfound로 안전 처리
### ⏱️ bulk-tabling/catchtable SSE timeout 10분 → 3시간 (v0.1.48) ### ⏱️ bulk-tabling/catchtable SSE timeout 10분 → 3시간 (v0.1.48)
- 대량 백필(724건 ≈ 100분) 시 10분 SSE timeout으로 중간 끊김 → 3시간으로 확장 - 대량 백필(724건 ≈ 100분) 시 10분 SSE timeout으로 중간 끊김 → 3시간으로 확장
- 백엔드 작업은 virtual thread로 별도 진행됐지만 emit() 예외로 마지막 cache.flush + complete 누락이슈 해소 - 백엔드 작업은 virtual thread로 별도 진행됐지만 emit() 예외로 마지막 cache.flush + complete 누락이슈 해소

View File

@@ -14,6 +14,7 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
import java.net.http.HttpResponse; import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.*; import java.util.*;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -30,8 +31,10 @@ public class WebSearchService {
private static final Logger log = LoggerFactory.getLogger(WebSearchService.class); private static final Logger log = LoggerFactory.getLogger(WebSearchService.class);
private static final int MAX_RESULTS = 5; private static final int MAX_RESULTS = 5;
private static final Duration REQ_TIMEOUT = Duration.ofSeconds(15);
private static final HttpClient HTTP = HttpClient.newBuilder() private static final HttpClient HTTP = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL) .followRedirects(HttpClient.Redirect.NORMAL)
.connectTimeout(Duration.ofSeconds(5))
.build(); .build();
private static final Pattern DDG_RESULT = Pattern.compile( private static final Pattern DDG_RESULT = Pattern.compile(
@@ -74,6 +77,7 @@ public class WebSearchService {
String url = "https://openapi.naver.com/v1/search/webkr.json?query=" + encoded + "&display=30"; String url = "https://openapi.naver.com/v1/search/webkr.json?query=" + encoded + "&display=30";
HttpRequest req = HttpRequest.newBuilder() HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(url)) .uri(URI.create(url))
.timeout(REQ_TIMEOUT)
.header("X-Naver-Client-Id", naverClientId) .header("X-Naver-Client-Id", naverClientId)
.header("X-Naver-Client-Secret", naverClientSecret) .header("X-Naver-Client-Secret", naverClientSecret)
.GET() .GET()
@@ -104,6 +108,7 @@ public class WebSearchService {
String url = "https://html.duckduckgo.com/html/?q=" + encoded; String url = "https://html.duckduckgo.com/html/?q=" + encoded;
HttpRequest req = HttpRequest.newBuilder() HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(url)) .uri(URI.create(url))
.timeout(REQ_TIMEOUT)
.header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36") .header("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36")
.header("Accept", "text/html,application/xhtml+xml") .header("Accept", "text/html,application/xhtml+xml")
.header("Accept-Language", "ko-KR,ko;q=0.9") .header("Accept-Language", "ko-KR,ko;q=0.9")