블로그

정적 사이트, 재게시하는 동안에도 계속 열려 있습니다

Mythex에서 정적 사이트를 다시 게시할 때, 예전에는 새 빌드를 업로드하기 전에 라이브 파일을 먼저 삭제했습니다. 그래서 업로드가 진행되는 동안 일부 방문자는 404 페이지를 보거나, 스크립트가 빠진 페이지를 봐야 했습니다. 업로드가 실패하면 사이트가 비어 있거나 절반만 새 버전이 되기도 했습니다. 이제는 새 빌드가 기존 파일 위에 덮어써지며, 페이지가 마지막에 올라갑니다. 불필요한 파일은 몇 분 뒤에 정리됩니다. 두 번째 수정으로 재게시 내용은 1분 대신 몇 초 안에 보이게 되었습니다. 로고처럼 파일 이름이 고정된 파일은 최대 하루까지 걸릴 수 있었는데, 이것도 개선되었습니다.

정적 사이트가 제공되는 방식

정적 사이트는 파일로만 이루어진 사이트입니다. HTML 페이지, JavaScript, CSS, 이미지입니다. Mythex에서 정적 사이트를 게시하면 빌드 결과를 해당 사이트 폴더 아래 오브젝트 스토리지에 업로드하고, 엣지 네트워크를 통해 제공합니다. 엣지는 방문자와 가까운 서버로, 스토리지에서 파일을 가져와 일정 기간 복사본을 보관합니다. (처음이라면 CDN이 무엇인지 가이드에서 개념을 설명합니다.)

잘못되어 있던 것은 두 가지였습니다. 재게시하는 동안 파일이 바뀌는 순서, 그리고 오래된 복사본을 얼마나 오래 보관하느냐입니다.

문제 1: 업로드가 사이트를 먼저 비웠습니다

재게시는 사이트 폴더의 모든 것을 삭제한 뒤 새 빌드를 업로드했습니다. 업로드에 걸리는 시간 동안 — 로컬 테스트에서 파일 250개짜리 사이트에 12초 — 엣지에 캐시되어 있지 않은 요청을 보낸 방문자는 404를 받거나, 스크립트가 404를 반환하는 HTML 페이지를 받았습니다.

실패한 업로드는 더 나빴습니다. 폴더는 이미 비어 있었고, 두 업로드 경로 중 하나에서는 실패한 파일보다 새 index.html이 먼저 올라갈 수 있었습니다. 실패로 보고된 게시가 절반만 새 사이트를 라이브 상태로 남길 수 있었습니다.

수정: 덮어쓰고, 페이지는 마지막에, 정리는 나중에

  1. 먼저 지우지 않습니다. 새 빌드를 기존 빌드 위에 업로드합니다.
  2. 에셋을 페이지보다 먼저. 페이지는 필요한 스크립트와 스타일을 이름으로 참조하므로, 페이지를 가장 마지막에 올립니다. 새 페이지가 올라가기 전까지 방문자는 옛 페이지를 받는데, 그 페이지가 참조하는 옛 에셋은 여전히 존재합니다. 새 페이지가 올라오면, 그것이 참조하는 새 에셋은 이미 자리에 있습니다.
  3. 남은 파일은 나중에 정리합니다. 업로드 전에 사이트를 구성하는 파일 목록을 확보합니다. 업로드가 성공한 뒤 3분이 지나면, 그 목록에 있으면서 새 빌드가 다시 쓰지 않은 파일을 삭제합니다. 3분은 엣지와 브라우저에서 페이지가 캐시될 수 있는 60초보다 깁니다. 그래서 옛 페이지를 들고 있는 방문자도 그 페이지가 참조하는 에셋을 찾을 수 있습니다.
  4. 확실하지 않으면 아무것도 지우지 않습니다. 업로드 전 목록 확보가 실패하면 아무것도 정리하지 않습니다. 남은 파일은 해가 없고, 다음 게시에서 정리됩니다. 무엇이 있었는지 모른 채 삭제하면 결국 사이트가 비어 버립니다.

이제 업로드가 실패하면 이전 사이트는 정확히 이전 상태 그대로 남습니다.

실제 업로드 스크립트, 실제 업로드 라우트, 실제 스토리지 서빙을 사용하고, 엣지 캐시가 비어 있는 방문자를 가정해 로컬에서 종단간으로 테스트했습니다. 이전 코드에서는 재게시 중에 91번 중 55번의 방문이 깨졌고, 실패한 게시 중에는 92번 중 61번이 깨졌습니다. 새 코드에서는 두 업로드 경로, 실패한 업로드, 그 뒤의 게시를 모두 포함한 약 3,500번의 방문에서 0번이 깨졌고, 정리되는 파일도 정확히 남은 파일만이었습니다.

문제 2: 재게시가 보이기까지 너무 오래 걸렸습니다

업로드가 잘 끝났을 때도 변경 사항이 나타나는 데 시간이 걸렸습니다. 엣지는 페이지의 복사본을 60초 동안, 다른 모든 파일은 하루 동안 보관했고, 브라우저에도 같은 규칙을 전달했습니다. logo.png, 폰트, public/ 폴더의 모든 것처럼 이름이 고정된 파일은 재게시 후 최대 하루 동안 옛 버전을 보여 줄 수 있었습니다.

수정: 빌드 기준의 파일 복사본

엣지가 API에 사이트의 위치를 물어볼 때, 응답에는 게시가 반영될 때마다 바뀌는 빌드 마커가 이제 포함됩니다. 엣지는 복사본을 이 마커 아래에 저장합니다. 따라서 엣지의 30초짜리 조회가 만료되는 순간 게시는 완전히 새로운 캐시 키 집합이 됩니다. 옛 복사본은 다시는 찾아지지 않으므로, 엣지는 복사본을 하루 동안 보관해도 자신을 대체한 게시 이후에는 옛 복사본을 제공하지 않습니다.

브라우저에는 두 가지 다른 규칙을 적용합니다.

  • 해시가 붙은 빌드 결과물 — assets/index-CzLcxDO8.js처럼 내용이 바뀌면 이름도 바뀌는 파일 — 은 1년간 캐시되며 immutable로 표시됩니다. 그 이름으로는 절대 오래된 상태가 될 수 없습니다.
  • 그 밖의 모든 것 — 페이지와 로고처럼 이름이 고정된 파일 — 은 no-cache로 표시됩니다. “사용하기 전에 확인하라”는 뜻입니다. 변경이 없으면 이제 If-None-Match 확인을 지원하므로, 본문 없이 작은 304 Not Modified 응답이 돌아갑니다.

두 가지를 구분하려면 주의가 필요합니다. public/assets/에서 복사된 파일은 자신의 이름을 유지하는데, assets/logo.png를 1년 동안 캐시하면 옛 로고가 1년간 고정될 수 있습니다. 그래서 파일은 빌드 결과 폴더에 있고 그리고 이름에 평범한 단어가 아니라 해시처럼 보이는 세그먼트가 있을 때만 해시된 파일로 간주합니다.

로컬에서는 게시가 라이브가 된 후 6초 만에 새 페이지와 로고가 보였습니다. 이전에는 페이지 60초, 로고 하루였습니다.

작은 수정 두 가지

  • HEAD 요청으로 생긴 빈 페이지. HEAD 요청은 본문 없이 페이지의 헤더만 요청합니다. 우리는 그 빈 응답을 캐시하고 있었고, 그래서 다음 방문자는 빈 페이지를 받았습니다. 이제 HEAD 응답은 캐시하지 않습니다.
  • “아무것도 없음”을 위한 실제 페이지. 앱이 없는 주소는 “This published app is offline or the link was not found”라는 일반 텍스트와 502 오류 코드를 받았습니다. 이제는 “There's no app here”라는 제대로 된 페이지와 404 상태 코드, 그리고 소유자가 Mythex로 돌아갈 수 있는 링크를 받습니다. 알 수 없는 커스텀 도메인도 같은 페이지를 받습니다.

우리가 얻은 교훈

  • 순서도 정확성의 일부입니다. 같은 파일이라도 다른 순서로 업로드되면 깨진 사이트와 작동하는 사이트의 차이가 생깁니다.
  • 지우는 것은 늦게, 아는 것만. 업로드 전에 확보한 목록으로 정리하는 것은 안전하지만, 폴더를 비우고 업로드가 끝나기를 바라는 것은 안전하지 않습니다.
  • 시간이 아니라 버전으로 캐시하세요. 만료 시간은 콘텐츠가 언제 바뀔지에 대한 추측입니다. 빌드에 따라 바뀌는 키는 사실입니다.

관련 글: 게시한 앱이 재게시하는 동안에도 계속 열려 있습니다. 게시에 대한 자세한 내용은 문서에서 볼 수 있습니다.

Mythex · Pricing · AI로 무엇이든 만들기 · 웹사이트 및 앱 템플릿 · Mythex 비교 · AI 앱 개발 용어집 · 무료 AI 앱 개발 도구 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)