📋 목차
접수도, 저장도, 알림도 정상인데 — 목록만 없다
레거시 시스템을 다루는 개발자라면 EUC-KR 인코딩 문제가 낯설지 않다. 한글이 깨지거나, 엑셀 다운로드가 뭉개지거나, 특정 문자열만 이상하게 저장되는 사례는 꽤 흔하다. 그런데 이번에 겪은 문제는 조금 달랐다.
회사 인트라넷의 불편 접수 채팅 프로그램에서 이런 현상이 발생했다.
- 문의 접수 → ✅ 정상
- DB 저장 → ✅ 정상
- 알림 발송 → ✅ 정상
- 문의 목록 화면 → ❌ 아무것도 나오지 않음
접수도 되고 저장도 되는데 목록이 안 나온다? 처음엔 꽤 당황스러웠다. 어딘가 딱 한 군데만 고장 난 것 같은데, 어디인지 바로 보이지 않았다.
구조 파악 — AJAX → PHP → DB → JSON
해당 채팅 목록 화면의 구조는 단순했다.
채팅 목록 화면
→ AJAX 요청
→ PHP 파일 실행
→ DB 조회
→ 데이터 가공
→ JSON 응답
→ 화면에 목록 출력
오래 안정적으로 돌던 기능이었다. 그런데 최근 첨부파일 기능이 추가되면서 변화가 생겼다. 채팅 목록에는 각 문의별로 가장 최근 메시지 1개가 표시된다. 일반 메시지가 마지막이면 해당 텍스트를 보여주고, 첨부파일이 마지막이면 [파일 첨부] 문구를 보여주도록 처리했다.
이론상 문제될 것은 없어 보였다. 실제로도 기능 자체는 동작했다. 문제는 전혀 다른 지점에 있었다.
디버깅 — 데이터는 있는데 화면엔 없다
처음엔 DB 조회 쪽을 의심했다. AJAX로 호출되는 PHP 파일을 직접 뜯어봤는데, DB 조회 결과는 멀쩡했다. 문의 목록 데이터도 정상이고, 각 채팅방의 최근 메시지도 제대로 들어왔다.
그래서 중간중간에 디버깅 코드를 박아가며 데이터가 어느 지점까지 살아 있는지 확인했다.
echo '<pre>';
var_dump($data);
exit;
한 줄 한 줄 따라가보면 데이터는 분명히 있었다. DB 조회도 정상, 배열에 담기는 것도 정상, 반복문도 정상적으로 돌았다. 그런데 최종적으로 화면에서는 목록이 나오지 않았다.
💡 실마리
채팅 목록 10건 중 딱 1건만 다른 루트를 타고 있었다. 그 1건은 마지막 메시지가 텍스트가 아니라 파일 첨부인 문의였다.
원인 — DB 밖에서 태어난 한글 문자열
문제의 코드는 이런 형태였다.
// 기존 코드 (문제 있음)
$display_msg = $row['last_msg']
? safe_iconv($row['last_msg'])
: ($row['file_name'] ? '[파일 첨부]' : '');
safe_iconv()는 EUC-KR 문자열을 UTF-8로 변환하는 함수다. 일반 메시지가 있으면 $row['last_msg']가 이 함수를 거쳐 JSON에 들어가기 전에 UTF-8로 정리된다. 그런데 첨부파일만 있는 경우는 달랐다.
⚠️ 핵심 문제
[파일 첨부]는 DB에서 온 값이 아니라 코드 안에 직접 적어둔 한글 문자열이다. 이 문자열은 safe_iconv()를 전혀 거치지 않았다. 레거시 EUC-KR 환경에서 PHP 파일 자체가 EUC-KR로 저장돼 있다면, 이 문자열은 UTF-8이 아닌 EUC-KR 바이트로 JSON에 섞인다. 결과적으로 json_encode()가 깨지고, 전체 JSON 응답이 무효화된다.
데이터가 중간에 유실된 게 아니었다. 유효하지 않은 인코딩 문자열이 JSON 생성 과정에 끼어들면서 응답 전체가 깨진 것이다. 그래서 화면에서는 마치 데이터가 통째로 사라진 것처럼 보였다.
해결 — 인코딩 흐름을 하나로 통일
해결 자체는 어렵지 않았다. 방법은 두 가지다.
방법 1 — 리터럴에도 동일하게 safe_iconv() 적용
$display_msg = $row['last_msg']
? safe_iconv($row['last_msg'])
: ($row['file_name'] ? safe_iconv('[파일 첨부]') : '');
방법 2 — 메시지 먼저 결정, 인코딩은 마지막 한 번만 (권장)
// 먼저 표시할 메시지 결정
$display_msg = $row['last_msg']
? $row['last_msg']
: ($row['file_name'] ? '[파일 첨부]' : '');
// 마지막에 한 번만 인코딩 변환
$display_msg = safe_iconv($display_msg);
✅ 왜 방법 2가 낫나?
DB에서 온 값이든, 코드에서 직접 만든 문자열이든 모든 응답 문자열이 동일한 인코딩 흐름을 타게 된다. 이후에 표시 문구가 추가되더라도 인코딩 누락 실수를 방지할 수 있다. 단, safe_iconv()가 EUC-KR 입력을 전제로 한다면, PHP 파일 인코딩이 EUC-KR인지 UTF-8인지 반드시 확인해야 한다.
왜 테스트에서 못 잡았을까
이 버그는 테스트 과정에서도 충분히 놓칠 수 있는 케이스였다. 첨부파일 기능을 테스트할 때의 흐름을 생각해보면 이렇다.
1. 파일을 첨부한다 → 2. 파일이 잘 붙었는지 확인한다 → 3. "잘 되네!" 하고 넘어간다
만약 파일 첨부 직후 아무 텍스트 메시지라도 하나 더 보냈다면? 최근 메시지는 일반 텍스트가 되고, safe_iconv()를 정상적으로 거치게 된다. 버그가 발현되지 않는다.
버그가 발현되는 정확한 조건은 이것이다: 파일 첨부 후 추가 메시지 없이, 해당 문의가 목록에 표시될 때. 기능 자체는 정상이니 QA를 통과하기 쉬운 케이스다.
체크리스트 — EUC-KR 환경 JSON 응답 전 확인사항
이번 일을 겪고 나서 정리한 점검 항목이다. 레거시 EUC-KR 환경에서 JSON 응답을 만들 때 최소한 이것들은 확인하자.
DB에서 가져온 문자열만 인코딩 변환하고 있지는 않은가?
코드에서 직접 만든 한글 문자열도 JSON 응답에 포함되는가?
모든 응답 문자열이 JSON 생성 전에 UTF-8로 정리되어 있는가?
json_encode() 이후 json_last_error_msg()를 확인했는가?
AJAX 응답 원문을 브라우저 네트워크 탭에서 직접 확인했는가?
특정 조건에서만 다른 데이터 가공 루트를 타는 값은 없는가? ← 이번 버그의 핵심
마무리 · 이번에 다시 느낀 것
레거시 인코딩 환경에서는 DB에서 가져온 데이터만 조심하면 되는 게 아니다. 코드에서 직접 만든 한글 문자열도 똑같이 관리 대상이다. 특히 JSON 응답을 만들 때는 필드 하나의 문자열만 잘못돼도 전체 응답이 깨질 수 있고, 화면에서는 전혀 다른 문제처럼 보인다.
그리고 또 하나. AI도 있고, 자동완성도 있고, 좋은 도구도 많지만, 어떤 문제는 여전히 echo, var_dump, exit을 박아가며 직접 흐름을 따라가야 보인다.
레거시 환경에서 새 기능을 추가할 때, 데이터 흐름뿐 아니라 인코딩 흐름도 같이 봐야 한다.
CoLife
20년 차 현직 개발자 · code & life
레거시 시스템부터 AI 도구까지, 현장에서 직접 겪은 이야기를 씁니다.
틀린 점이나 다른 경험이 있으면 댓글로 편하게 알려주세요.
🔍 비슷한 경험 있으신가요?
EUC-KR 환경에서 비슷한 인코딩 함정을 겪으신 분들의 이야기가 궁금합니다.
다른 해결 방법이나 더 나은 패턴이 있다면 댓글로 공유해 주세요!
'Code > PHP' 카테고리의 다른 글
| PHP ftp_put 고정장 전문 오프셋 깨짐 해결 — 범인은 FTP_ASCII 모드였다 (0) | 2026.04.22 |
|---|---|
| 반복문 속 쿼리가 서버를 죽인다 — PHP N+1 문제와 SQL 실행 최소화 실전 가이드 (0) | 2026.04.21 |
| [PHP] 화면엔 멀쩡한데 전송하면 깨진다? EUC-KR 고정 길이 전문 숨은 에러 완벽 해결 (0) | 2026.04.19 |
| PHP Imagick 멀티 TIFF 자동 변환이 서버를 다운시킨 이유와 해결법 (1) | 2026.04.15 |
| PHP 개발자의 에디터 변천사: 드림위버부터 AI CLI 환경까지 (0) | 2026.03.25 |