웹 서비스에서 스태틱 파일(이미지, CSS, JavaScript 등)을 브라우저가 캐시하여 사용하는 것은 성능을 최적화하는 중요한 기법입니다. 하지만 파일이 업데이트되었을 때, 기존에 캐시된 버전이 계속 사용되면 사용자는 최신 버전을 볼 수 없는 문제가 발생합니다. 이 문제를 해결하기 위해 캐시 버스터(Caching Busting)을 사용합니다.
브라우저는 성능 최적화를 위해 이미 로드된 스태틱 파일을 캐시합니다. 이 과정에서 업데이트 되어야 하는데에도 불구하고 업데이트가 되지않는 문제가 발생합니다.
기존에는 이 문제를 해결하기 위해 참조하는 곳에서 쿼리스트링 추가해서 호출하는 방식을 사용했습니다. 배포하기 전에 js및 css파일에서 static파일의 경로 뒤에 배포할 때의 timestamp를 일괄적으로 붙였습니다.
이로 인해 발생하는 문제는 다음과 같습니다:
업데이트되지 않은 파일도 새로 불러옴: 배포마다 js, css 파일에 타임스탬프를 일괄적으로 붙였기 때문에, 업데이트되지 않은 파일도 매번 새로운 파일처럼 요청하게 되었습니다. 이로 인해 불필요하게 서버에 요청이 늘어나고, 캐시를 우회하는 방식이 자주 사용되면서 성능이 저하될 수 있었습니다.
캐시 클리어의 비효율성: 사용자가 캐시를 클리어하고 새로운 파일을 다운로드하는 것이 아니라, 타임스탬프를 통해 우회하는 방식이었기 때문에 캐시의 정확한 관리가 어려웠습니다. 특히, 불필요한 파일이 캐시된 상태로 남을 수 있어 결국 사용자 경험에 악영향을 미쳤습니다.
예: style.css?v=1.0, app.js?v=2.0
파일이 변경될 때마다 버전 번호를 바꿔서 새로운 버전으로 요청을 유도합니다.
장점:
단점:
예: style.abc123.css, app.456def.js
빌드 과정에서 파일 내용의 해시값을 계산하여, 파일 이름에 포함시킴으로써 내용이 바뀔 때마다 새로운 이름으로 요청을 유도합니다.
장점:
단점:
기존 서비스에서는 서버비용 절감 문제로 해당 이슈가 가시화되었고,
기존 프로젝트는 django를 이용하여 프론트서버가 구성되어있었기에
cdn과 연동할 수 있으며, 기존 django와 호환하기 좋은 django플로그인을
최대한 활용하고자 하였다
추가적으로 기존 로직이 html파일에서만 static파일을 요청하지 않고
js,css에서도 요청하는 곳이 있어서 이 것을 처리할 때 유용하게 쓸 수 있는 메타데이터(JSON) 파일을 같이 생성해주는 플러그인을 선택했다
pip install django-staticfiles-finder
# settings.py
INSTALLED_APPS = [
...
'staticfiles_finder', # 추가
...
]
STATICFILES_FINDERS = [
'staticfiles_finder.finders.HashingFileSystemFinder', # 파일 경로 해시값 생성
...
]
# collectstatic 명령어 실행 시, 파일을 해시값을 포함시켜 저장하도록 설정
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'
]
{
"img/logo.png": "img/logo.a3b2c4d.png",
"css/style.css": "css/style.a3b2c4d.css",
"js/app.js": "js/app.5d6f8e9.js"
}
위 방법만으로는 django 템플릿이 적용되는 html이외의 js,css에서의 static에 대한 경로처리는 제대로 되지 않는다
그래서 이에 대한 처리를 위한 스크립트를 작성해서 배포하기 전에 동작하게 해두었다.
import os
import json
# manifest.json 파일 경로
MANIFEST_PATH = 'static/manifest.json'
def load_manifest(manifest_path):
"""Load manifest.json file and return its content."""
with open(manifest_path, 'r') as file:
return json.load(file)
def get_files_in_static_directory(directory):
"""Get all .js and .css file paths in the static/ directory."""
files_to_process = []
for root, _, files in os.walk(directory):
for file in files:
if file.endswith(('.js', '.css')):
files_to_process.append(os.path.join(root, file))
return files_to_process
def update_file_with_manifest(file_path, manifest):
"""Update file content by replacing old paths with new ones from the manifest."""
with open(file_path, 'r') as file:
file_content = file.read()
for old_path, new_path in manifest.items():
file_content = file_content.replace(old_path, new_path)
with open(file_path, 'w') as file:
file.write(file_content)
def update_static_paths():
"""Main logic to update static paths in all .js and .css files."""
# Load the manifest file
manifest = load_manifest(MANIFEST_PATH)
# Get all .js and .css files in the static/ directory
files_to_process = get_files_in_static_directory('static')
if not files_to_process:
print("No files to process.")
else:
for file_path in files_to_process:
update_file_with_manifest(file_path, manifest)
print("File updates completed!")
if __name__ == "__main__":
update_static_paths()
위의 캐시 버스터 적용 방법을 통해 얻은 주요 이점은 다음과 같습니다:
불필요한 서버 요청 감소: 파일 업데이트가 이루어졌을 때, 캐시 버스터를 통해 파일 경로를 자동으로 갱신함으로써 불필요한 서버 요청을 줄일 수 있었습니다. 이전에는 파일 이름에 타임스탬프를 매번 추가하여, 업데이트되지 않은 파일도 서버에서 새로 요청하게 되는 문제를 겪었습니다. 이제 해시값을 파일명에 포함시켜 파일 내용 변경 시에만 새로운 파일로 요청되도록 했습니다.
성능 최적화: CDN을 통해 배포된 스태틱 파일들이 정확히 캐시되고 갱신되므로, 클라이언트는 항상 최신 버전의 파일을 사용하게 됩니다. 이로 인해 캐시와 CDN 관리가 원활해져 성능이 향상되었습니다.
자동화된 빌드 시스템 활용: django-staticfiles-finder 플러그인과 manifest.json을 사용하여, 빌드 시 자동으로 해시값을 포함한 파일명을 생성하고, 이를 HTML, JS, CSS 파일 내에서 참조하도록 했습니다. 이로 인해 수동으로 파일을 갱신하고 관리하는 작업에서 벗어나, 자동화된 방식으로 일관된 캐시 관리를 할 수 있게 되었습니다.
확장성 있는 캐시 관리: 기존에는 타임스탬프 방식이였기에 여러 파일에 대해 버전 번호를 관리하는 데 어려움이 있었고, 캐시를 명확히 제어하는 데 한계가 있었습니다. 해시값을 사용함으로써 각각의 파일에 대해 독립적인 캐시 제어가 가능해졌습니다.
결과적으로, 캐시 버스터를 통해 캐시 관리가 보다 명확하고 효율적이 되었으며, 서버의 과도한 요청을 방지하고, 최신 파일을 항상 제공할 수 있는 환경을 구축할 수 있었습니다.