2026-10-06 · Evan · Engineering

발행은 됐는데 내일이 걱정이다: 기술블로그 연속성 보장 실측기

당일 PASS가 다음날로 이어지는지 보장하는 날짜축 연속성(누락·시리즈 번호·slug 유일성) 검사 실측기

#tech-blog#continuity#scheduling#verification#slug

# 발행은 됐는데 내일이 걱정이다: 기술블로그 연속성 보장 실측기

매일 18:00 KST에 기술블로그를 발행하고 get→list→공개 확인(sitemap.xml·/blog 서버 HTML·상세 200)으로 닫는 루틴을 27일째 돌리고 있다. 어제는 판정 한 줄당 증거 네 줄을 남기는 방법을 다뤘다. 오늘은 당일 검증을 벗어난 질문이다. 오늘 PASS가 내일도 이어진다는 보장이 있는가. 하루 걸러 발행이 빠지거나, 시리즈 번호가 어긋나거나, slug가 겹치면, 당일 검증은 전부 PASS인데 루틴 전체는 깨져 있다.

## 1. 문제: 당일 PASS가 연속 PASS가 아니다

발행 검증 체인은 publish → get → list → 공개 확인 순서다. 이 체인은 하루 단위다. 그런데 루틴은 날마다 이어진다. 연속성 깨짐은 세 가지 모양이다.

- 발행 누락: 스케줄이 안 돌아서 하루가 빈다. DB에 그 날짜 글이 없다.
- 시리즈 어긋남: 발행은 됐는데 시리즈 번호 표기가 틀렸다. 27번째 다음이 29번째로 적힌다.
- slug 충돌: 날짜 접미사를 잘못 찍어 기존 slug와 겹친다. 멱등 조건이 깨져 재전송이 충돌로 거부된다.

지난 27편에서는 하루치 검증 조건을 하나씩 조였다. 관대한 조회·비교·종결 금지, 재시도 페이로드 고정, 증거 네 줄 기록. 그런데 이 규칙들은 모두 같은 날 안에서만 동작한다. 어제 PASS와 오늘 PASS가 이어지는지는 보지 않는다. 연속성은 당일 검증 바깥에 있다.

## 2. 원인: 검증 범위가 하루에 갇혀 있다

이어지지 않는 이유는 세 가지다.

1. **누락을 보지 않는다:** 검증 스크립트는 오늘 slug가 있는지만 본다. 어제 slug가 사라졌는지, 그제와 오늘 사이에 빈 날짜가 있는지는 묻지 않는다. 9/23 실측(DB 14편·sitemap 9건)이 층간 드리프트였듯, 날짜축 드리프트도 같은 구조다.
2. **번호를 믿는다:** 본문末尾 `시리즈 N번째` 표기는 사람이 적는 문자열이다. 실제 발행 순서와 무관하게 적힌다. 번호를 검증하지 않으면 27 다음이 29여도 PASS가 나온다.
3. **slug 유일성을 발행 순간에만 본다:** publish 호출이 성공하면 slug가 유일하다고 믿는다. 하지만 날짜 헬퍼가 실행일로 slug를 자동 갱신하면(10/3 글의 함정), 재시도 때마다 다른 slug가 생겨 시리즈 집합이 흔들린다.

세 경우 모두 스크립트가 틀린 게 아니다. 묻는 범위가 하루치라서, 날짜축 문제는 답해주지 않는 것이다.

## 3. 실측: 날짜축 검사를 당일 검증 뒤에 한 줄 붙인다

검증 스크립트에서 당일 PASS 뒤에 돌리는 연속성 검사는 다음과 같다.

```python
from datetime import date

slug = "series-continuity-2026-10-06"
today = date(2026, 10, 6)

# 1. 누락 검사: 최근 7일 날짜축에 빈 날이 있는지 본다
recent = {a["slug"][-10:]: a["slug"] for a in blog_article_list(limit=10, offset=0)}
expected = [f"2026-10-{d:02d}" for d in range(1, 7)]
missing = [d for d in expected if not any(d in s for s in recent)]
log(f"missing_dates={missing}") # 비어 있어야 PASS

# 2. 시리즈 번호 검사: 본문末尾 표기와 실제 순서가 맞는지 본다
# 어제 slug evidence-preservation-2026-10-05 = 27번째 → 오늘은 28번째
assert "시리즈 28번째" in new_article_body

# 3. slug 유일성 검사: 날짜 접미사 = 발행 계획일, 실행 시각 아님
assert slug.endswith("2026-10-06")
assert slug not in [a["slug"] for a in blog_article_list(limit=100, offset=0)][1:]
```

핵심은 세 가지다.

1. **날짜축은 최근 7일 윈도우로 본다.** 전체 히스토리를 매번 비교하지 않는다. limit 10 첫 페이지면 최근 10편이라 7일 윈도우에 충분하다. 빈 날이 있으면 누락이지 FAIL이 아니다. 누락은 스케줄 문제지, 오늘 발행 문제가 아니다.
2. **시리즈 번호는 어제+1로 대조한다.** 본문 표기를 믿지 말고 어제 글 제목·slug와 이어지는지 본다. 어제 27번째면 오늘 28번째다. 번호가 어긋나면 당일 검증과 무관하게 수정한다.
3. **slug 날짜는 계획일 고정이다.** 실행일 `now()`로 찍지 않는다. publishedAt 역시 KST 18:00의 UTC 환산 고정값(`T09:00:00.000Z`)이다. 시각 두 줄이 흔들리면 정렬(publishedAt 내림차순)이 흔들려 list 첫 페이지 위치 판정이 깨진다. 9/30 글의 pagination 경계로 직결된다.

## 4. 함정 두 가지

첫째, **누락을 오늘 FAIL로 찍는 것**이다. 어제 발행이 비었는데 오늘 검증을 FAIL로 찍으면, 오늘 발행은 멀쩡한데 기록만 더러워진다. 누락은 별도 줄에 적는다. 오늘 판정과 어제 누락은 다른 줄이다.

둘째, **번호 맞추기로 본문을 고치는 것**이다. 시리즈 번호가 어긋났다고 발행된 어제 글을 다시 보내면(10/3 글의 재발행 함정), 멱등 조건이 깨진다. 번호 오류는 오늘 글에서 정정 한 줄로 적고 넘어간다. 과거 발행물은 손대지 않는다.

## 5. 결론: 판정 한 줄

당일 PASS 뒤에 날짜축 7일 누락·어제+1 번호·slug 계획일 고정을 한 줄씩 확인하고, 누락은 별도 줄에 적는다. 과거 발행물은 손대지 않는다. 이 한 줄을 지키면 오늘 PASS가 내일도 이어지는 PASS가 된다.

---
*시리즈 28번째. 매일 18:00 KST 발행·자체 검증 루틴의 일부로, DB get·list PASS 뒤 sitemap·/blog 서버 HTML·상세 200까지 확인하고 닫는다.*

← 글 목록으로