Skip to Content
Infra & DevOpsRSS 피드 누락의 원인이었던 네트워크 오류
☁️ Infra & DevOps2026년 3월 15일

RSS 피드 누락의 원인이었던 네트워크 오류

#rss#docker#network#caching
개인 웹사이트 구성2 / 4
홈페이지의 최신 글이 기본 카드로 바뀌는 문제를 조사했습니다. RSS 파싱 이전의 연결 실패를 확인하고 마지막 정상 결과 보관과 NAS 네트워크 설정을 수정한 과정입니다.

홈페이지의 최신 글 영역이 가끔 기본 카드 세 개로 바뀌었습니다. 처음에는 최신 글이 나왔다가 이후 조회에서 대체 카드가 표시되는 식이었습니다.

화면에서는 RSS 파싱이나 캐시가 잘못된 것처럼 보였지만, 로그에는 HTTP 응답 이전의 연결 실패가 남아 있었습니다. 이 글에서는 피드 조회의 어느 단계가 실패했는지 나눠 확인하고, 장애가 있을 때 보여줄 결과를 바꾼 과정을 정리합니다.

HTTP 응답 이전의 연결 실패

실패 로그의 fetch failed는 RSS 항목을 읽기 전에 발생했습니다. 응답 본문이 없었으므로 파서를 수정하는 것만으로 해결할 수 있는 상황은 아니었습니다.

컨테이너의 DNS와 외부 연결 경로를 확인하면서 NAS 방화벽과 Docker 네트워크 제약을 찾았습니다. 같은 이미지와 코드가 허용된 네트워크에서는 최신 글을 정상 반환했습니다.

조회 과정을 다음처럼 나누면 어느 부분을 확인할지 정하기 쉽습니다.

확인 지점확인 내용
요청 시작DNS와 외부 연결
HTTP 응답상태 코드와 본문
RSS 파싱문서 형식과 항목 추출
화면 응답최신값·이전값·기본 카드 선택

최근 수집 결과로 대체 표시

기존에는 외부 요청이 실패하면 기본 카드를 보여줬습니다. 이 처리를 바꿔 마지막으로 성공한 결과가 있으면 먼저 반환하고, 정상 이력이 없을 때만 기본 카드를 사용하도록 했습니다.

이전 결과를 보여주면 일시적인 조회 실패 때문에 최신 글 영역이 전혀 다른 목록으로 바뀌는 일을 줄일 수 있습니다. 다만 그 결과도 과거에 가져온 값이므로 네트워크 복구와 별도로 관리해야 합니다.

실패 단계가 드러나도록 로그도 보완했습니다. 임시로 브라우저에서 RSS를 직접 읽던 우회는 제거하고 클라이언트가 한 API를 사용하도록 유지했습니다. 서버와 브라우저에 서로 다른 조회 경로가 남지 않게 한 것입니다.

현재 소스의 대체 응답

현재 홈페이지의 src/lib/homeWriting.ts에는 ready, stale, unavailable 상태가 있습니다. 정상 응답은 최근 결과를 저장하고, 요청이나 파싱에 실패하면 마지막 정상값부터 찾습니다. 마지막 값도 없을 때 기본 카드로 내려갑니다.

실제 코드에서는 서버 인스턴스의 전역 캐시에 항목과 저장 시각을 보관합니다. 다음은 그 분기를 설명하기 위해 줄인 예시입니다.

typescript
example/feed-fallback.ts
type Item = { title: string; link: string }; type FeedCache = { items: Item[]; cachedAt: string }; function onFailure(cache: FeedCache | undefined, fallback: Item[]) { if (cache?.items.length) { return { items: cache.items, status: 'stale' as const }; } return { items: fallback, status: 'unavailable' as const }; }

정상 이력이 없는 첫 실행과 한 번 이상 성공한 뒤의 실패를 나눠볼 수 있습니다. 이 캐시는 인스턴스 안의 값이므로 서버를 다시 시작하거나 여러 인스턴스로 나누면 모두 같은 과거 결과를 공유하는 영구 저장소가 되지는 않습니다. 네트워크 문제를 해결하는 코드와도 역할이 다릅니다.

/api/writing은 기존 { items } 응답 형식을 유지합니다. 실제 route에서는 모든 카드가 fallback일 때 Cache-Control: no-store를, 그 외에는 공유 캐시와 재검증 시간을 전달합니다. 내부 함수가 관리하는 상태값이 API 응답에 그대로 포함되는 것은 아닙니다.

컨테이너 안쪽에서 같은 요청 실행

브라우저에서 RSS가 열린다는 것만으로 컨테이너의 연결 경로를 확인할 수는 없습니다. 문제를 조사할 때는 서비스가 실행되는 네트워크에서 같은 주소를 조회해야 했습니다.

다음은 Node가 들어 있는 컨테이너에서 HTTP 응답과 본문 수신을 확인하는 예시입니다. 컨테이너 이름은 환경에 맞게 바꿔 사용합니다.

bash
example/check-feed-in-container.sh
docker exec example-home node --input-type=module -e ' try { const response = await fetch("https://blog.ersione.com/rss.xml", { signal: AbortSignal.timeout(10000) }); console.log({ status: response.status, type: response.headers.get("content-type") }); console.log("body length", (await response.text()).length); } catch (error) { console.error(error.message); process.exitCode = 1; } '

이 단계에서 연결 자체가 실패하면 아직 RSS 파싱을 검사한 것이 아닙니다. 응답이 있다면 상태 코드와 문서 형식을 확인하고, 그다음 실제 항목 추출과 API 반환값을 비교합니다. 실패 위치를 건너뛰고 파서와 UI를 동시에 바꾸면 어떤 변경이 원인에 대한 조치였는지 알기 어려워집니다.

네트워크 복구 후 API 확인

실제 배포에 사용할 Compose 네트워크를 정리한 뒤 HTTP 200 응답, RSS 파싱, 최신 항목 반환을 순서대로 확인했습니다. 운영 API에서도 최신 글이 나오는지 확인했습니다.

마지막 정상값을 반환하는 처리는 화면의 대체 동작이고, 네트워크 설정 수정은 조회 실패의 원인에 대한 조치입니다. 두 변경을 나눠 두어야 이전 결과가 보인다는 이유로 원본 조회까지 정상이라고 판단하지 않을 수 있습니다.

Last updated on