정적 사이트를 배포할 때 여러 파일을 서비스 디렉터리에 하나씩 복사하면, 복사 중 어느 시점의 파일 조합을 사용자가 읽는지 애매해진다. 이번에는 완성된 두 디렉터리를 먼저 만들고 웹 서버가 가리키는 링크만 교체하는 작은 실험을 했다.

두 결과물을 먼저 만들었다

준비된 결과물로 포인터 교체: 현재 배포, 후보 배포, 되돌리기
AI 생성 개념도 · 완성된 후보 결과물을 확인하고 공개 포인터를 바꾸는 배포 개념도입니다. 포인터 교체만으로 모든 파일 누락 문제가 해결되는 것은 아닙니다. 본문 근거와 적용 조건 · 그림을 누르면 원본 크기로 볼 수 있습니다.

release-a와 release-b에 각각 이름이 다른 version.txt를 만들었다. current는 처음에 release-a를 가리켰다. Nginx는 current를 통해 파일을 읽도록 설정했다. 첫 요청의 본문을 확인한 다음 새 링크 next를 만들어 current 자리로 교체하고 같은 URL을 다시 요청했다.

from pathlib import Path
import os

# 둘 다 같은 실험 디렉터리 안에 있는 경로
Path("next").symlink_to("release-b", target_is_directory=True)
os.replace("next", "current")
직접 관찰한 결과 · 2026-09-12
시점상태본문·조건
교체 전200release-a
교체 후200release-b
Nginx 재시작하지 않음동일 프로세스

바뀐 것은 링크이고 요청 주소는 같았다

첫 요청은 release-a, 교체 후 요청은 release-b를 반환했다. 두 요청 모두 200이었고 그 사이에 Nginx를 재시작하지 않았다. 이 결과는 해당 설정에서 새 요청이 교체된 링크를 따라 파일을 읽었다는 증거다. 전체 배포 시간이 0이거나 모든 접속이 끊기지 않았다는 측정은 아니다.

  1. 새 릴리스 준비

    HTML과 참조 자산, 이전 자산 보관 범위를 함께 검사한다.

  2. 전환

    이전 목적지를 기록한 뒤 완성된 릴리스로 링크를 교체한다.

  3. 요청 대조

    새 HTML·새 CSS·이전 HTML이 요청하는 CSS를 각각 확인한다.

  4. 복원

    문제 시 이전 링크로 돌아가고 남아 있는 새 HTML의 자산도 확인한다.

롤백은 삭제보다 포인터 복구부터

롤백은 삭제보다 포인터 복구부터의 판단 조건을 설명하는 개념 그림
AI 생성 개념도 · 롤백은 삭제보다 포인터 복구부터에 관한 설명용 그림입니다. 구체적인 조건·계산·확인 범위는 아래 본문을 따르며, 실제 화면이나 관찰·측정 자료가 아닙니다. 본문 근거와 적용 조건 · 그림을 누르면 원본 크기로 볼 수 있습니다.

새 결과물에 문제가 있으면 먼저 어느 버전을 서비스할지 결정한다. 이 방식에서는 이전 디렉터리를 보관했을 때 되돌릴 목표가 분명하다. 다만 데이터베이스 변경이나 외부 저장 데이터까지 링크 하나로 되돌아가지는 않는다. 정적 파일 배포에 적용한 아이디어를 모든 애플리케이션 배포로 확대하지 않는다.

실제 적용 전에는 파일시스템, 링크 권한, 웹 서버의 파일 캐시 설정을 확인한다. 다른 파일시스템으로 이동하거나 링크를 제한하는 환경에서는 이 예제를 그대로 사용할 수 없다. 이 글의 실행 스크립트는 모두 같은 임시 디렉터리 안에서 작업한다.

추가 확인: 이전 HTML이 요청하는 CSS는 어디에 있는가

최초 실험의 텍스트 파일 비교에서 한 걸음 더 나아가 두 릴리스에 서로 다른 CSS URL을 가진 HTML을 만들었다. release-a의 HTML은 /assets/a.css를, release-b의 HTML은 /assets/b.css를 참조한다. 먼저 a의 HTML을 받은 다음 current를 b로 바꾸고 a.css를 요청했다. 브라우저의 병렬 로딩을 측정한 것은 아니며, 문제가 생길 수 있는 요청 순서를 직접 구성한 것이다.

직접 관찰한 결과 · 2026-09-12
요청 시점HTTP 상태확인 결과
교체 전 HTML200release-a
교체 후 HTML200release-b
교체 직후 이전 CSS404없음
이전 CSS 보관 후200body{color:red}
a로 되돌린 HTML200교체 전 본문과 일치

b에는 처음에 a.css가 없어서 이전 HTML의 후속 요청이 404가 됐다. 이전 CSS를 b의 assets에도 보관한 뒤 같은 요청은 200이었고 본문은 body{color:red}로 일치했다. 최신 HTML이 보인다는 검사만으로는 이미 HTML을 받은 방문자의 자산 요청까지 보호하지 못했다.

배포 전후에 별도 폴더에서 결과를 저장하는 요청 예시
# 실제 배포 HTML에서 읽은 자산 URL로 바꾼다.
curl -sS -D page.headers -o page.html https://example.com/
curl -sS -D new.headers -o new.css https://example.com/assets/NEW_HASH.css
curl -sS -D old.headers -o old.css https://example.com/assets/OLD_HASH.css

NEW_HASH와 OLD_HASH는 예시다. 앞뒤 HTML이 실제로 참조하는 이름을 써야 하며, CSS 요청이 200이어도 본문이 홈 HTML이면 통과로 보지 않는다. 서버 내부 링크 전환과 외부 캐시 갱신은 다를 수 있으므로 이 요청은 방문자에게 제공되는 주소에서도 확인한다.

되돌릴 때도 새 HTML이 남아 있을 수 있다

9월 12일 실험에서는 current를 a로 되돌리고 HTML 본문이 최초 응답과 같음을 확인했다. 당시에는 b의 HTML을 이미 받은 독자가 롤백 뒤 b.css를 요청하는 반대 방향까지 시험하지 않았다. 이 빈틈을 10월 17일 별도 임시 환경에서 확인했다.

10월 17일 후속 실험: HTML 롤백은 성공해도 CSS는 실패했다

같은 Nginx 프로세스에서 a의 HTML을 받고 b로 전환했다. b에는 a.css를 미리 보관했기 때문에 이전 CSS 요청이 200이었다. 이어 b의 HTML과 b.css를 받은 뒤 원래의 a로 롤백했다. a의 HTML은 200으로 돌아왔지만 a에 없는 b.css 요청은 404였다. 방문자가 새 HTML을 받은 직후 되돌리기가 일어나는 순서를 직접 구성한 것이다.

직접 관찰한 결과 · 2026-10-17
요청 순서HTTP 상태확인 결과
b 전환 뒤 a.css200이전 자산 보관: red
b의 HTML과 b.css200release-b / blue
a로 롤백한 HTML200release-a
a 롤백 뒤 b.css404새 HTML의 자산 누락
b.css도 보관한 롤백 후보200blue / text/css

다음에는 원래 a를 수정하지 않고, a의 HTML·a.css와 b.css를 함께 준비한 a-retained 폴더로 전환했다. HTML은 원래 a와 같았고 b.css는 원래 b에서 받은 본문과 같은 body{color:blue}, Content-Type text/css로 돌아왔다. 링크가 이전 HTML을 가리키는지와 양쪽 HTML이 요구하는 자산을 제공하는지는 별도로 검사해야 했다.

이 결과를 배포에 적용한다면 되돌릴 후보에도 필요한 새 자산을 준비하거나, current와 독립된 버전별 자산 경로를 유지하는 설계가 필요하다. 이미 공개한 이전 릴리스 폴더를 즉석에서 수정하기보다 검사한 롤백 후보를 별도로 준비하면 어느 결과물을 검사했는지 남기기 쉽다. 이름이 같은 파일에 서로 다른 내용을 넣는 방식은 이번의 서로 다른 자산 URL 실험과 조건이 다르다.

롤백 검사통과 기준실패하면
이전 HTML GET상태 200과 기록해 둔 이전 본문 일치current 목적지·후보 내용·캐시 확인
이전 HTML의 CSS GET상태·Content-Type·CSS 본문 일치후보 파일과 읽기 권한 확인
새 HTML의 CSS GET롤백 후에도 필요한 새 자산 제공새 자산을 포함한 후보 또는 독립 자산 경로 준비

a.css와 b.css라는 이름은 차이를 보여주기 위한 짧은 실험 이름이다. 실제 빌드에서는 내용에 따른 해시 등으로 다른 콘텐츠에 다른 URL을 부여하고, 이전 HTML이 남을 수 있는 기간에 맞춰 자산을 보관할 수 있다. 모든 과거 자산을 영구 보관할 필요는 없지만 정리 시점은 CDN과 브라우저의 HTML 보관 조건까지 보고 정해야 한다.

편집자 의견

롤백 준비에서는 이전 HTML뿐 아니라 그 HTML이 요구하는 자산도 보존하는 쪽을 우선하겠습니다. 링크를 원자적으로 바꾸는 실험이 성공해도 브라우저가 이미 받은 문서의 CSS 경로는 별도의 문제로 남았기 때문입니다.

자산 이름을 릴리스마다 바꾼다면 이전 문서가 사용될 수 있는 기간을 고려해 보존 범위를 정하겠습니다. 디스크 정리의 편의만으로 이전 자산을 바로 지우기보다 실제 참조 관계를 확인하는 편이 낫습니다.

판단 근거: 추가 확인: 이전 HTML이 요청하는 CSS는 어디에 있는가

의견 추가 2026-10-23 · 본문 자료를 바탕으로 한 편집 판단

실험 환경과 원자료

nginx version: nginx/1.18.0 (Ubuntu) · Linux · Python 3.10.12
로컬 임시 디렉터리 · 단일 실행 · 실제 운영 트래픽 제외

관찰 시각: 2026-09-12T13:53:51.407530+00:00

Python 3과 Nginx가 있는 별도 실험 환경에서 실행합니다. 스크립트는 임시 폴더와 로컬 포트를 만들며, 종료할 때 자신이 시작한 프로세스만 정리합니다.

python3 run-lab.py --output observations.json

참고한 공식 문서

기존 자료 확인: 2026-10-22 · 원고 수정: 2026-10-29. 잘못된 내용은 정정 요청으로 알려주세요.

추가 실험의 환경과 원자료

관찰 시각: 2026-09-12T15:07:44.600003+00:00
os: Linux · python: 3.10.12 · nginx: nginx version: nginx/1.18.0 (Ubuntu)

별도 임시 디렉터리, 루프백 Nginx와 합성 응답 서버에서 순차 요청. 운영 설정과 외부 API는 사용하지 않음.

확인하지 않은 범위: 단일 실행. CDN, 브라우저 캐시, 동시 부하, 인증, NFS, 디스크 장애, 운영 데이터는 시험하지 않음.

Python 3이 있는 별도 Linux 실험 환경에서 실행합니다. Nginx 실험에는 Nginx도 필요합니다. 임시 파일과 스크립트가 만든 자식 프로세스만 사용하며, 기존 결과 파일은 덮어쓰지 않습니다.

python3 lab-nginx-boundaries.py --output new-observations.json

추가 실험의 환경과 원자료

관찰 시각: 2026-10-17T03:51:17.595688+00:00
os: Linux · python: 3.10.12 · nginx: nginx version: nginx/1.18.0 (Ubuntu)

동일 파일시스템의 임시 릴리스 a→b→a→a-retained를 순차 전환. HTTP GET과 CSS 본문·Content-Type 대조. 기존 웹 서버 설정과 운영 릴리스는 변경하지 않음.

확인하지 않은 범위: 브라우저 병렬 로딩, CDN, open_file_cache, 부하, DB 롤백, 안전한 자산 삭제 기간은 측정하지 않았다.

Python 3과 Nginx가 있는 별도 Linux 실험 환경에서 실행합니다. 임시 파일과 스크립트가 만든 자식 프로세스만 사용하며, 기존 결과 파일은 덮어쓰지 않습니다.

python3 lab-release-rollback-assets.py --output lab-release-rollback-assets-result.json

이 글의 보완 기록

  • · 전환·검증·복원 흐름과 신구 자산 URL을 검사하는 요청 예시를 추가했다.
  • · 롤백 뒤 새 CSS 404와 자산을 함께 준비한 후보의 200을 새 격리 실험으로 확인하고, 양방향 완료 조건을 추가했다. 기존 관찰 자료는 보존했다.
  • · 반복 배포 체크표 두 개를 걷어내고 준비→전환→확인→복원의 흐름과 양방향 자산 검사표를 중심으로 정리했습니다. 9월·10월 실험의 날짜와 원자료는 유지했습니다.
  • · 자산 실패·검증 한계를 반복하던 두 문단을 삭제했습니다. 순차 요청의 실제 관찰과 롤백 때 빠진 CSS 사례, 원자료는 유지했습니다.
  • · 본문 근거에 대한 편집자의 판단과 결론이 달라지는 조건을 추가했습니다. 기존 관찰일·공식 자료 확인일은 유지했습니다.
  • · 글별 적용 사례와 반례를 보강했습니다. 기존 관찰일과 원자료 확인일은 유지했습니다.