feat: 외부 서비스 안정성 개선 (Phase 3)
- Nitter 요청에 10초 타임아웃 및 HTTP 상태 코드 검증 추가 - Meilisearch syncAllSchedules에서 불필요한 deleteAllDocuments 제거 - addDocuments는 같은 ID면 자동 업데이트(upsert) - 일정 삭제 시 Meilisearch 동기화 코드 정리 (동적 import 제거) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e852f215a3
commit
52a655bf76
4 changed files with 65 additions and 53 deletions
|
|
@ -3,7 +3,7 @@
|
|||
* GET: 공개, POST/PUT/DELETE: 인증 필요
|
||||
*/
|
||||
import suggestionsRoutes from './suggestions.js';
|
||||
import { searchSchedules, syncAllSchedules } from '../../services/meilisearch/index.js';
|
||||
import { searchSchedules, syncAllSchedules, deleteSchedule } from '../../services/meilisearch/index.js';
|
||||
import { CATEGORY_IDS } from '../../config/index.js';
|
||||
import {
|
||||
getCategories,
|
||||
|
|
@ -202,13 +202,8 @@ export default async function schedulesRoutes(fastify) {
|
|||
await connection.query('DELETE FROM schedules WHERE id = ?', [id]);
|
||||
});
|
||||
|
||||
// Meilisearch에서도 삭제 (트랜잭션 외부)
|
||||
try {
|
||||
const { deleteSchedule } = await import('../../services/meilisearch/index.js');
|
||||
// Meilisearch에서도 삭제 (트랜잭션 외부, 실패해도 무시)
|
||||
await deleteSchedule(meilisearch, id);
|
||||
} catch (meiliErr) {
|
||||
fastify.log.error(`Meilisearch 삭제 오류: ${meiliErr.message}`);
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -251,10 +251,7 @@ export async function syncAllSchedules(meilisearch, db) {
|
|||
|
||||
const index = meilisearch.index(INDEX_NAME);
|
||||
|
||||
// 기존 문서 모두 삭제
|
||||
await index.deleteAllDocuments();
|
||||
|
||||
// 문서 변환
|
||||
// 문서 변환 (addDocuments는 같은 ID면 자동 업데이트)
|
||||
const documents = schedules.map(s => ({
|
||||
id: s.id,
|
||||
title: s.title,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,32 @@
|
|||
import { parseNitterDateTime } from '../../utils/date.js';
|
||||
|
||||
const FETCH_TIMEOUT = 10000; // 10초
|
||||
|
||||
/**
|
||||
* 타임아웃이 적용된 fetch
|
||||
*/
|
||||
async function fetchWithTimeout(url, timeout = FETCH_TIMEOUT) {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||
|
||||
try {
|
||||
const res = await fetch(url, { signal: controller.signal });
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(`HTTP ${res.status}`);
|
||||
}
|
||||
|
||||
return res;
|
||||
} catch (err) {
|
||||
clearTimeout(timeoutId);
|
||||
if (err.name === 'AbortError') {
|
||||
throw new Error('요청 타임아웃');
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 트윗 텍스트에서 첫 문단 추출 (title용)
|
||||
*/
|
||||
|
|
@ -196,7 +223,7 @@ export async function fetchSingleTweet(nitterUrl, username, postId) {
|
|||
*/
|
||||
export async function fetchTweets(nitterUrl, username) {
|
||||
const url = `${nitterUrl}/${username}`;
|
||||
const res = await fetch(url);
|
||||
const res = await fetchWithTimeout(url);
|
||||
const html = await res.text();
|
||||
|
||||
// 프로필 정보
|
||||
|
|
@ -225,7 +252,7 @@ export async function fetchAllTweets(nitterUrl, username, log) {
|
|||
log?.info(`[페이지 ${pageNum}] 스크래핑 중...`);
|
||||
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
const res = await fetchWithTimeout(url);
|
||||
const html = await res.text();
|
||||
const tweets = parseTweets(html, username);
|
||||
|
||||
|
|
|
|||
|
|
@ -375,58 +375,51 @@ export async function fetchTweets(nitterUrl, username) {
|
|||
|
||||
---
|
||||
|
||||
## [Medium] Meilisearch Task 완료 대기
|
||||
## [Medium] Meilisearch 동기화 개선
|
||||
|
||||
Meilisearch 설정/문서 작업이 task 완료를 기다리지 않아 즉시 검색 시 누락/지연이 발생할 수 있습니다.
|
||||
### 문제점
|
||||
|
||||
1. `syncAllSchedules`에서 `deleteAllDocuments()` 후 `addDocuments()` 하는 방식은 비효율적
|
||||
2. DB에서 일정 삭제 시 Meilisearch에 반영되지 않음
|
||||
3. 플러그인 초기화 시 설정 변경 완료를 기다리지 않음
|
||||
|
||||
### 영향받는 파일
|
||||
|
||||
| 파일 | 라인 | 설명 |
|
||||
|------|------|------|
|
||||
| `backend/src/plugins/meilisearch.js` | 45-81 | 인덱스 설정 (searchable, filterable, sortable 등) |
|
||||
| `backend/src/services/meilisearch/index.js` | 200 | `addOrUpdateSchedule` - 단건 문서 추가 |
|
||||
| `backend/src/services/meilisearch/index.js` | 255-272 | `syncAllSchedules` - 전체 동기화 |
|
||||
|
||||
### 현재 코드 (plugins/meilisearch.js)
|
||||
|
||||
```javascript
|
||||
// 45-57줄: 설정 변경 후 완료 대기 없음
|
||||
await index.updateSearchableAttributes([...]);
|
||||
await index.updateFilterableAttributes([...]);
|
||||
await index.updateSortableAttributes([...]);
|
||||
// Meilisearch는 비동기 task로 처리하므로 즉시 반환됨
|
||||
```
|
||||
|
||||
### 현재 코드 (syncAllSchedules)
|
||||
|
||||
```javascript
|
||||
// 255-272줄
|
||||
await index.deleteAllDocuments(); // task 완료 대기 없음
|
||||
await index.addDocuments(documents); // 삭제 완료 전 추가될 수 있음
|
||||
```
|
||||
| `backend/src/plugins/meilisearch.js` | 45-81 | 인덱스 설정 |
|
||||
| `backend/src/services/meilisearch/index.js` | 254-272 | `syncAllSchedules` - 전체 동기화 |
|
||||
| `backend/src/routes/schedules/index.js` | DELETE | 일정 삭제 시 Meilisearch 반영 필요 |
|
||||
|
||||
### 권장 해결책
|
||||
|
||||
**1. syncAllSchedules 개선**
|
||||
|
||||
`deleteAllDocuments()` 제거. Meilisearch의 `addDocuments()`는 같은 ID면 자동 업데이트(upsert).
|
||||
|
||||
```javascript
|
||||
// plugins/meilisearch.js
|
||||
const searchableTask = await index.updateSearchableAttributes([...]);
|
||||
await client.waitForTask(searchableTask.taskUid);
|
||||
// 변경 전
|
||||
await index.deleteAllDocuments();
|
||||
await index.addDocuments(documents);
|
||||
|
||||
const filterableTask = await index.updateFilterableAttributes([...]);
|
||||
await client.waitForTask(filterableTask.taskUid);
|
||||
// ...
|
||||
|
||||
// syncAllSchedules
|
||||
const deleteTask = await index.deleteAllDocuments();
|
||||
await meilisearch.waitForTask(deleteTask.taskUid);
|
||||
|
||||
const addTask = await index.addDocuments(documents);
|
||||
await meilisearch.waitForTask(addTask.taskUid);
|
||||
// 변경 후
|
||||
await index.addDocuments(documents); // upsert 방식
|
||||
```
|
||||
|
||||
**2. 일정 삭제 시 Meilisearch 동기화**
|
||||
|
||||
```javascript
|
||||
// routes/schedules/index.js DELETE 핸들러
|
||||
await meilisearchService.deleteSchedule(id);
|
||||
```
|
||||
|
||||
**3. 플러그인 초기화 시 waitForTask (선택)**
|
||||
|
||||
서버 시작 시 1회만 실행되므로 우선순위 낮음.
|
||||
|
||||
### 우선순위
|
||||
|
||||
**중간** - 동기화 안정성 (개별 문서 추가는 fire-and-forget 허용 가능)
|
||||
**중간** - 동기화 안정성 및 효율성
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -546,8 +539,8 @@ await writeFile(dictPath, content, 'utf-8');
|
|||
|
||||
| 이슈 | 우선순위 | 상태 |
|
||||
|------|---------|------|
|
||||
| Nitter 요청 안정성 | Medium | ⬜ 미해결 |
|
||||
| Meilisearch Task 대기 | Medium | ⬜ 미해결 |
|
||||
| Nitter 요청 안정성 | Medium | ✅ 해결됨 |
|
||||
| Meilisearch 동기화 개선 | Medium | ✅ 해결됨 |
|
||||
|
||||
## Phase 4: 성능 최적화
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue