설정 파일 두 개가 겹쳐 접속 로그가 두 번 쌓이고 있었다, 일주일 199MB의 31.4%
어제 압축 설정을 고치느라 아파치 설정 파일을 열었다가, 그 김에 나머지 설정도 실제 응답과 하나씩 맞춰 봤습니다. 두 군데가 어긋나 있었습니다. 접속 로그는 모든 요청을 두 번 기록하고 있었고, 정적 파일에는 1년짜리 캐시가 걸려 있는데도 이미지 전송량의 36%가 재요청이었습니다.
설정 파일만 읽었을 때는 둘 다 정상으로 보입니다. 로그 쪽에는 “성능을 위한 로그 최적화”라는 주석까지 달려 있습니다. 어긋난 것은 파일 안이 아니라 파일과 파일 사이였습니다.
접속 로그가 일주일에 199MB, 그중 31.4%가 같은 내용
아파치는 로그를 어디에 어떤 형식으로 남길지를 CustomLog 한 줄로 정합니다. 이 서버에는 그 줄이 두 개 있었습니다. 하나는 기본 설정 파일에, 하나는 나중에 추가된 설정 파일에 있었고, 둘 다 목적지가 같은 파일이었습니다.
httpd.conf : CustomLog "logs/access_log" combined
logging.conf : CustomLog /var/log/httpd/access_log common env=!dontlog
아파치는 CustomLog를 여러 번 만나면 순서대로 전부 실행합니다. 하나를 만나면 나머지를 무시하는 방식이 아닙니다. 그래서 요청 하나가 들어올 때마다 같은 파일에 두 줄이 쌓였습니다.
지난주 로그 파일 하나를 통째로 읽어 두 형식을 구분해 세어 봤습니다. 두 형식은 뒤에 참조 주소와 브라우저 이름이 붙는지로 구분됩니다.
| 구분 | 줄 수 | 바이트 | 비중 |
|---|---|---|---|
| 전체 (8월 30일 ~ 9월 5일) | 1,306,263 | 199,258,033 | 100% |
| combined 형식 | 668,921 | 136,685,160 | 68.6% |
| common 형식 | 637,340 | 62,572,465 | 31.4% |
일주일에 199MB가 쌓이는데 그중 62.5MB는 바로 위나 아래 줄과 같은 요청을 다시 적은 것입니다. 게다가 다시 적은 쪽이 정보가 더 적습니다. common 형식에는 참조 주소도, 브라우저 이름도 없습니다. 이 서버는 로그를 4주 보관하므로 같은 낭비가 네 벌 쌓여 있고, /var/log/httpd 전체가 757MB입니다.
줄 수가 정확히 맞지 않는 이유
두 형식의 줄 수가 668,921과 637,340으로 31,581줄 차이 납니다. 같은 요청을 두 번 적는다면 같아야 할 값입니다. 차이는 나중에 추가된 쪽에만 붙어 있는 env=!dontlog 조건에서 나옵니다.
SetEnvIf Request_URI ".(jpg|jpeg|png|gif|css|js|ico|webp|woff2|woff|ttf|svg)$" dontlog
이미지와 스타일시트, 스크립트 요청은 로그에 남기지 말라는 규칙입니다. 이 조건은 조건이 적힌 그 CustomLog에만 걸립니다. 기본 설정 파일의 CustomLog에는 조건이 없으니 그대로 다 남습니다. 확인해 보면 계산이 맞습니다.
| 항목 | 줄 수 |
|---|---|
| combined 전체 | 668,921 |
| 그중 정적 파일 요청 | 31,720 |
| 정적 파일을 뺀 나머지 | 637,201 |
| common 전체 (실제) | 637,340 |
| 차이 | 139 (0.02%) |
139줄은 요청이 처리되는 도중에 조건이 정해지는 경우 등으로 생기는 오차 범위입니다. 정리하면 이렇습니다. 로그를 줄이려고 추가한 설정은 정적 파일을 빼는 데는 성공했지만, 그 규칙이 걸리지 않는 원래 로그가 그대로 남아 있어서 결과적으로 파일을 31.4% 키웠습니다. 의도한 것과 정확히 반대 방향입니다.
고친 방법과 확인
둘 중 하나를 꺼야 합니다. 어느 쪽을 남길지는 정적 파일 로그를 볼 일이 있는지에 달려 있습니다. 저는 남기는 쪽을 택했습니다. 바로 다음에 나오는 캐시 이야기가 전부 이미지 요청 기록에서 나온 것이기 때문입니다. 그래서 정보가 적은 common 쪽을 껐습니다.
# /etc/httpd/conf.d/logging.conf
# 2026-09-07: httpd.conf 222줄의 CustomLog combined 와 겹쳐
# 모든 요청이 두 번 기록되던 것을 끔
#CustomLog /var/log/httpd/access_log common env=!dontlog
문법 검사 후 아파치를 다시 읽히고, 같은 주소를 세 번 요청한 뒤 로그 끝을 봤습니다. 세 줄이었습니다. 고치기 전이라면 여섯 줄이었을 자리입니다. 바꾸기 전 파일은 /root/logging.conf.bak.20260907에 두었습니다.
1년짜리 캐시가 걸린 파일이 다시 내려간 비율
이 서버의 캐시 설정은 정적 파일에 public, max-age=31536000, immutable을 붙입니다. 1년 동안 유효하고, 그 사이에는 바뀌지 않으니 서버에 다시 물어볼 필요도 없다는 뜻입니다. 두 사이트의 글 여섯 편을 열어 거기 붙어 있는 파일 주소를 모두 뽑고, 각각에 대해 처음 받을 때와 다시 받을 때를 나눠 재 봤습니다.
| 항목 | 값 |
|---|---|
| 수집한 파일 주소 | 69개 (이 서버 것 67개) |
| 처음 받을 때 합계 | 13,002,934바이트 |
| 변경 여부를 물었을 때 응답 | 67개 모두 304, 본문 0바이트 |
| ETag가 붙은 응답 | 0개 |
| 업로드 이미지 | 43개 (12,638,320바이트, 97.2%) |
| 주소에 판번호가 붙은 파일 | 24개 (테마·코어 파일만) |
설정 자체는 제대로 동작합니다. 물어보면 67개 전부 “안 바뀌었다”고 답하고 본문을 보내지 않습니다. 문제는 그다음입니다. 실제 로그에서 이미지 요청이 어떻게 들어왔는지를 봤습니다.
| 기간 | 이미지 요청 | 전송 바이트 | 재요청 비중 | 304 응답 |
|---|---|---|---|---|
| 8월 30일 ~ 9월 5일 | 1,137건 | 401,467,004 | 36.0% | 8건 |
| 9월 6일 ~ 9월 7일 | 646건 | 181,252,735 | 40.7% | 51건 |
재요청은 같은 주소를 같은 IP가 두 번 이상 받아 간 경우입니다. 일주일 치로 748쌍 중 389건이 재요청이었고, 바이트로는 144,507,997바이트, 전체의 36.0%였습니다. 1년 동안 다시 받지 말라고 적어 보낸 파일들입니다.
제 요청을 빼도 결론은 같습니다
가장 많이 요청한 IP 하나가 일주일에 220건으로 두 번째보다 세 배 이상 많았습니다. 제가 사이트를 확인하며 만든 요청일 가능성이 큽니다. 그래서 그 IP를 빼고 다시 셌습니다.
| 구분 | 첫 요청 | 재요청 | 재요청 바이트 비중 |
|---|---|---|---|
| 전체 | 748건 / 256,959,007바이트 | 389건 / 144,507,997바이트 | 36.0% |
| 최다 IP 제외 | 649건 / 225,928,883바이트 | 268건 / 106,923,136바이트 | 32.1% |
32.1%로 내려갔을 뿐 성격은 그대로입니다. 그리고 더 분명한 것이 하나 더 있습니다. 응답을 브라우저와 봇으로 나눠 보면, 304 응답 여덟 건은 전부 봇이 받아 갔고 브라우저가 받은 304는 두 기간 모두 0건이었습니다. 브라우저는 캐시가 살아 있으면 아예 요청을 보내지 않고, 캐시가 없으면 조건 없이 전부 받아 갑니다. 그 사이 단계가 없습니다.
즉 재요청 36%는 캐시 헤더가 무시당한 것이 아니라, 캐시가 남아 있지 않은 방문이 그만큼이라는 뜻에 가깝습니다. 시크릿 창, 다른 기기, 며칠 만의 재방문, 그리고 캐시를 쓰지 않는 크롤러입니다. immutable은 이미 캐시를 가진 사람에게만 효과가 있는 지시였습니다.
어제 바꾼 PNG 열 장은 어떻게 됐나
여기서 어제 한 일과 연결됩니다. 어제 PNG 열 장을 다시 저장해 12.7MB를 2.6MB로 줄였습니다. 파일 이름과 주소는 그대로 두고 내용만 바꿨습니다. 그런데 그 주소들에는 1년짜리 immutable이 붙어 있습니다.
| 파일 | 마지막 수정 | 현재 크기 | 캐시 지시 |
|---|---|---|---|
| image-260703192436.png | 9월 6일 | 281,654 | immutable, 1년 |
| image-260706122607.png | 9월 6일 | 267,473 | immutable, 1년 |
| image-260709190357.png | 9월 6일 | 261,433 | immutable, 1년 |
| 나머지 일곱 장 | 9월 6일 | 242,964 ~ 264,265 | immutable, 1년 |
9월 6일 이전에 그 이미지를 받아 간 방문자의 브라우저는 최대 1년 동안 옛 파일을 그대로 씁니다. 서버에 물어보지도 않습니다. 파일을 바꿔 놓고 바뀐 것을 알릴 방법을 스스로 막아 둔 셈입니다.
이번에는 결과적으로 문제가 없습니다. 어제 한 작업이 화소를 한 바이트도 바꾸지 않는 무손실 재저장이었기 때문입니다. 옛 파일과 새 파일은 크기만 다르고 화면에 그려지는 그림은 같습니다. 옛 캐시를 붙들고 있는 방문자는 조금 더 큰 파일을 가지고 있을 뿐 잘못된 것을 보고 있지는 않습니다.
다만 이건 설계가 옳았던 것이 아니라 이번 작업의 성격이 마침 그랬던 것입니다. 만약 어제 한 일이 이미지 안의 글자를 고치는 것이었다면, 아무리 파일을 바꿔도 이전 방문자에게는 1년 동안 옛 화면이 보였을 것입니다. 그리고 그 사실은 서버 어디에서도 오류로 나타나지 않습니다.
이미지 주소에는 판번호가 없습니다
테마와 코어의 스타일시트·스크립트에는 워드프레스가 주소 뒤에 판번호를 붙여 줍니다. 수집한 69개 중 24개가 그렇습니다. 판번호가 바뀌면 브라우저에게는 다른 주소이므로 새로 받습니다. 1년짜리 immutable과 판번호는 그렇게 짝을 이룹니다.
업로드한 이미지에는 그 장치가 없습니다. 43개 전부 판번호 없는 맨 주소입니다. 워드프레스는 이미지 주소에 판번호를 붙이지 않기 때문입니다. 같은 캐시 지시가 한쪽에는 안전하고 다른 쪽에는 위험한 이유가 여기 있습니다. 이미지 내용을 바꿔야 할 때 쓸 수 있는 방법은 파일 이름을 바꾸는 것뿐입니다.
남은 로그 199MB에는 무엇이 들어 있었나
로그를 절반으로 줄이고 나서, 남은 절반이 무엇인지도 세어 봤습니다. 일주일 치 combined 기록 668,921줄을 응답 코드별로 나눈 결과입니다.
| 응답 코드 | 건수 | 비중 |
|---|---|---|
| 200 정상 | 247,092 | 36.9% |
| 404 없음 | 211,212 | 31.6% |
| 302 임시 이동 | 167,658 | 25.1% |
| 301 영구 이동 | 39,893 | 6.0% |
| 500 서버 오류 | 1,125 | 0.2% |
| 403 거부 | 839 | 0.1% |

정상 응답보다 404가 아주 조금 적을 뿐입니다. 404만으로 로그에서 37,627,722바이트를 차지합니다. 주소는 16,233종, 보낸 곳은 4,340개 IP였습니다. 가장 많이 찍힌 주소들을 보면 성격이 분명합니다.
- /wp/ 와 /wordpress/ 와 /blog/ — 워드프레스가 하위 폴더에 깔려 있는지 찍어 보는 요청
- /wp/index.php, /wordpress/wp-json/batch/v1 — 같은 추측 주소에 딸린 후속 요청
- /robots.txt — 4,055건, 이 서버에 얹힌 다른 사이트들의 몫
- /free/null — 예전 게시판 주소 형태를 그대로 훑는 요청
상위 IP 세 개가 각각 정확히 10,146건으로 같습니다. 같은 목록을 같은 순서로 도는 도구가 주소만 바꿔 가며 돌고 있다는 뜻입니다. 이런 요청은 막아도 로그에는 남습니다. 403으로 바뀔 뿐입니다.
로그에 사이트 이름이 없습니다
여기서 더 들어가려다 막혔습니다. 이 서버에는 여러 사이트가 얹혀 있는데, 로그 형식에 사이트 이름을 남기는 항목이 없습니다. 그래서 404 211,212건이 어느 사이트로 들어온 것인지 구분할 수 없습니다.
robots.txt 404가 4,055건이지만 두 블로그의 robots.txt는 둘 다 정상으로 응답합니다. 직접 요청해 확인했습니다. 그러니 그 4,055건은 다른 사이트의 것인데, 어느 사이트인지는 이 로그로 알 수 없습니다. 일주일에 199MB를 쌓아 두고도 “어느 사이트에서 404가 나는가”라는 가장 기본적인 질문에 답할 수 없는 상태입니다. 로그 형식은 다음에 손볼 자리로 남겨 둡니다.
정리
오늘 잰 두 가지는 성격이 같습니다. 설정 파일 한 개만 보면 둘 다 맞습니다. 로그 최적화 설정은 정적 파일을 빼도록 정확히 쓰여 있고, 캐시 설정은 정적 파일에 1년을 붙이도록 정확히 쓰여 있습니다. 어긋난 것은 그 설정이 다른 설정과 겹쳤을 때, 그리고 그 설정이 붙는 대상이 판번호 없는 주소였을 때입니다.
그래서 설정 파일을 읽는 것만으로는 찾을 수 없었습니다. 로그 이중 기록은 로그 파일을 형식별로 세어 보고 나서, 캐시 문제는 전송 바이트를 재던 방식을 접속 기록에 그대로 적용하고 나서 드러났습니다. 설정은 의도를 적어 놓은 것이고, 실제로 무슨 일이 일어났는지는 로그와 응답에만 남아 있습니다.
오늘 고친 것은 로그 한 줄입니다. 일주일에 62.5MB, 4주로 250MB가 줄어듭니다. 캐시 쪽은 고치지 않았습니다. 이미지 캐시 기간을 줄이면 재요청 36%가 그대로 더 무거워지고, 판번호를 붙이려면 발행 과정을 손대야 합니다. 어느 쪽이 나은지는 아직 잴 것이 남아 있습니다. 다만 지금 상태가 “1년 동안 안 바뀐다고 약속했지만 실은 바뀔 수 있는 주소”라는 것은 기록해 둡니다.