본문 바로가기
Code/PHP

EUC-KR 환경에서 JSON이 통째로 사라진 이유 — 코드 한 줄의 인코딩 함정

by CoLife 2026. 5. 12.

Code & Life · PHP / 인코딩

EUC-KR 환경에서 JSON이 통째로 사라진 이유
— 코드 한 줄의 인코딩 함정

DB도 AJAX도 정상이었다. 알림도 왔다. 그런데 채팅 목록만 사라졌다. 범인은 코드 안에 조용히 박혀 있던 문자열 하나였다.

C

CoLife

20년 차 현직 개발자 · code & life

01

접수도, 저장도, 알림도 정상인데 — 목록만 없다

레거시 시스템을 다루는 개발자라면 EUC-KR 인코딩 문제가 낯설지 않다. 한글이 깨지거나, 엑셀 다운로드가 뭉개지거나, 특정 문자열만 이상하게 저장되는 사례는 꽤 흔하다. 그런데 이번에 겪은 문제는 조금 달랐다.

회사 인트라넷의 불편 접수 채팅 프로그램에서 이런 현상이 발생했다.

  • 문의 접수 → ✅ 정상
  • DB 저장 → ✅ 정상
  • 알림 발송 → ✅ 정상
  • 문의 목록 화면 → ❌ 아무것도 나오지 않음

접수도 되고 저장도 되는데 목록이 안 나온다? 처음엔 꽤 당황스러웠다. 어딘가 딱 한 군데만 고장 난 것 같은데, 어디인지 바로 보이지 않았다.

02

구조 파악 — AJAX → PHP → DB → JSON

해당 채팅 목록 화면의 구조는 단순했다.

채팅 목록 화면
→ AJAX 요청
→ PHP 파일 실행
→ DB 조회
→ 데이터 가공
JSON 응답
→ 화면에 목록 출력

오래 안정적으로 돌던 기능이었다. 그런데 최근 첨부파일 기능이 추가되면서 변화가 생겼다. 채팅 목록에는 각 문의별로 가장 최근 메시지 1개가 표시된다. 일반 메시지가 마지막이면 해당 텍스트를 보여주고, 첨부파일이 마지막이면 [파일 첨부] 문구를 보여주도록 처리했다.

이론상 문제될 것은 없어 보였다. 실제로도 기능 자체는 동작했다. 문제는 전혀 다른 지점에 있었다.

03

디버깅 — 데이터는 있는데 화면엔 없다

처음엔 DB 조회 쪽을 의심했다. AJAX로 호출되는 PHP 파일을 직접 뜯어봤는데, DB 조회 결과는 멀쩡했다. 문의 목록 데이터도 정상이고, 각 채팅방의 최근 메시지도 제대로 들어왔다.

그래서 중간중간에 디버깅 코드를 박아가며 데이터가 어느 지점까지 살아 있는지 확인했다.

echo '<pre>';
var_dump($data);
exit;

한 줄 한 줄 따라가보면 데이터는 분명히 있었다. DB 조회도 정상, 배열에 담기는 것도 정상, 반복문도 정상적으로 돌았다. 그런데 최종적으로 화면에서는 목록이 나오지 않았다.

💡 실마리

채팅 목록 10건 중 딱 1건만 다른 루트를 타고 있었다. 그 1건은 마지막 메시지가 텍스트가 아니라 파일 첨부인 문의였다.

04

원인 — 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 생성 과정에 끼어들면서 응답 전체가 깨진 것이다. 그래서 화면에서는 마치 데이터가 통째로 사라진 것처럼 보였다.

05

해결 — 인코딩 흐름을 하나로 통일

해결 자체는 어렵지 않았다. 방법은 두 가지다.

방법 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인지 반드시 확인해야 한다.

06

왜 테스트에서 못 잡았을까

이 버그는 테스트 과정에서도 충분히 놓칠 수 있는 케이스였다. 첨부파일 기능을 테스트할 때의 흐름을 생각해보면 이렇다.

1. 파일을 첨부한다 → 2. 파일이 잘 붙었는지 확인한다 → 3. "잘 되네!" 하고 넘어간다

만약 파일 첨부 직후 아무 텍스트 메시지라도 하나 더 보냈다면? 최근 메시지는 일반 텍스트가 되고, safe_iconv()를 정상적으로 거치게 된다. 버그가 발현되지 않는다.

버그가 발현되는 정확한 조건은 이것이다: 파일 첨부 후 추가 메시지 없이, 해당 문의가 목록에 표시될 때. 기능 자체는 정상이니 QA를 통과하기 쉬운 케이스다.

07

체크리스트 — 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을 박아가며 직접 흐름을 따라가야 보인다.

레거시 환경에서 새 기능을 추가할 때, 데이터 흐름뿐 아니라 인코딩 흐름도 같이 봐야 한다.

C

CoLife

20년 차 현직 개발자 · code & life

레거시 시스템부터 AI 도구까지, 현장에서 직접 겪은 이야기를 씁니다.
틀린 점이나 다른 경험이 있으면 댓글로 편하게 알려주세요.

🔍 비슷한 경험 있으신가요?

EUC-KR 환경에서 비슷한 인코딩 함정을 겪으신 분들의 이야기가 궁금합니다.
다른 해결 방법이나 더 나은 패턴이 있다면 댓글로 공유해 주세요!

💬 댓글로 의견 남기기