Sprockets 매니페스트 문제점 및 해결 방안
-
Sprockets는
public/assets/.sprockets-manifest-#{SecureRandom.hex(16)}.json과 같이 무작위 이름의 매니페스트 파일을 생성하여, 매 빌드마다 파일명이 변경되고 Git에 새로운 파일로 인식됩니다. -
매니페스트 내의 각 애셋 항목은 파일 시스템의
mtime(수정 시간)을 포함하며, 내용 변경 여부와 관계없이 파일이 터치될 때마다mtime이 갱신되어 매니페스트 내용이 변경됩니다. -
해결책:
config/initializers/sprockets.rb에Sprockets::Manifest를 몽키패치하여 매니페스트 파일명을 고정(".sprockets-manifest-#{'0' * 32}.json")하고, 모든 애셋의mtime을 에포크 시간(Time.at(0).utc)으로 설정하여 결정론적 매니페스트를 생성합니다.
GZip 압축 문제점 및 해결 방안
-
Sprockets는 기본적으로 압축 가능한 애셋의 GZip(.gz) 복사본을 생성하며, 이 GZip 파일의 헤더에 원본 파일의
mtime을 포함합니다. -
원본 파일의
mtime이 변경되면, 압축된 내용(payload)은 동일하더라도 GZip 파일 자체의 바이너리 콘텐츠가 달라져 Git이나 Docker에서 새로운 파일로 인식되어 저장소 크기를 불필요하게 증가시킵니다. -
ActiveSupport::Gzip.compress또한 압축 시 타임스탬프를 포함하여 비결정적 출력을 생성하는 문제가 있습니다. -
해결책:
Sprockets::Utils::Gzip을 몽키패치하여compress메서드 호출 시mtime파라미터를 ‘알 수 없음’을 의미하는 0으로 설정합니다. -
대안: Nginx의
ngx_http_gzip_static_module과 같은 특정 모듈에서만.gz파일을 직접 활용하므로, 대부분의 환경(Apache, Puma, CDN)에서는 이 기능이 필요 없을 수 있습니다.config.assets.gzip = false설정을 통해 GZip 애셋 생성을 완전히 비활성화하는 것을 고려할 수 있습니다. (이 설정은 문서화되지 않았음)
mtime 사용의 근본적인 의문
- GZip 파일 내부에 저장되는
mtime값이 웹 애셋 전달 과정에서 실제로 중요하게 사용되는지에 대한 의문을 제기합니다.Last-ModifiedHTTP 헤더를 통해 이미 원본 파일의 수정 시간이 전달되므로, GZip 파일 내부의mtime은 불필요하게 파일 내용을 변경하고 캐시 무효화를 유발할 가능성이 있습니다.