← Ascendy EN

backend

변환했다고 믿었지만 이름만 바꿨다 — 그리고 실패한 작업은 목록에서 사라졌다

· Ascendy Engineering


TL;DR

소스 노트. backend 팀 인테이크(docs/intake/from-backend/2026-06-04-rename-not-convert.md)를 정제한 글이다. 외부 이미지 API의 벤더·엔드포인트·내부 에러코드는 일반화했다(HEIC·EXIF·pillow-heif는 공개 기술이라 그대로). 같은 “조용한 실패” 가족인 존재하지 않는 모델을 가리킨 silent 로그, placeholder 기본값이 설정 실패를 가린 이야기, 이중 쓰기가 가린 1차 쓰기 실패와 이어진다.

조용히 사라지는 카드

제보는 프론트에서 왔다. 벌크 사진 보정이 통째로 안 된다고. 그런데 증상이 묘했다.

시작하면 진행 카드가 잠깐 떴다가, 몇 초 뒤 사라진다. 새로고침하면 영영 안 보인다. 토스트도, 에러 메시지도, 빨간 X도 없다. 사용자 입장에선 “분명 눌렀는데 아무 일도 안 일어난” 것이다.

DB를 열어보니 그림이 달랐다. 최근 작업이 전부 failed, 아이템마다 외부 이미지 API의 invalid image file 류 400이 찍혀 있었다. 작업은 요란하게 실패하고 있었다 — 그 실패가 화면에 닿지 않았을 뿐.

원인은 두 겹이었고, 둘은 서로 다른 파일에 있었다.

결함 ① — “변환”이 실은 이름 바꾸기였다

한 보정 경로가 원본 이미지 바이트를 그대로 외부 API에 보내면서, 파일 객체의 이름만 이렇게 붙이고 있었다:

file.name = "input.jpg"   # 바이트는 그대로, 이름표만 JPEG

이게 함정이다. 파일 객체에 .jpg 이름을 붙이는 건 디코더에게 주는 힌트일 뿐, 내용 변환이 아니다. 아이폰 기본 포맷인 HEIC가 들어오면, 내용은 여전히 HEIC다. 그리고 엄격한 외부 이미지 API는 확장자나 이름이 아니라 매직바이트로 실제 포맷을 검사한다. 이름표 위장은 통하지 않는다 → invalid image file.

더 얄궂은 건, 같은 코드베이스의 다른 보정 엔진들은 멀쩡했다는 것이다. 걔들은 보내기 전에 디코드→재인코드 정규화를 거쳤다. 이 한 경로만 그 정규화를 건너뛰고 있었다. “비슷하니까”라며 새 백엔드를 추가할 때 공통 전처리를 공유하지 않으면, 이렇게 한 경로만 조용히 갈라진다.

거기에 하나 더. HEIC 디코더 라이브러리(pillow-heif)는 의존성에 이미 깔려 있었다. 그런데 register 호출이 어디에도 없었다 — 설치는 됐지만 호출되지 않는 죽은 코드였다. “패키지가 깔려 있으니 되겠지”는 보장이 아니다.

결함 ② — 실패한 작업이 목록에서 사라졌다

①만으로도 나빴다. 하지만 진짜 악질은 두 번째였다.

검토-대기 목록을 가져오는 쿼리가 이렇게 필터링하고 있었다:

SELECT ... FROM job WHERE status IN ('running', 'completed');

failed가 빠져 있다. 그래서 작업이 실패로 떨어지는 바로 그 순간 목록에서 사라졌다. 카드가 증발한 이유가 이것이다. 상세 API는 실패 작업을 정상적으로 반환했는데 — 정작 사용자가 처음 보는 목록이 그걸 가렸다. 실패를 볼 표면 자체가 없었던 것이다.

이게 silent failure의 핵심 구조다. 결함 ①은 “실패를 만들고”, 결함 ②는 “실패를 숨긴다”. 그리고 둘은 보통 다른 파일, 다른 사람의 머릿속에 있다. ①을 고쳐도 ②가 남으면, 다음 실패도 똑같이 조용히 사라진다.

fix — 세 갈래

  1. pre-flight 정규화. 외부 호출 직전에 항상 정규화를 끼웠다 — HEIC 디코더 등록 + 디코드 + EXIF 방향 보정(exif_transpose — 이걸 빼면 아이폰 세로 사진이 메타데이터가 사라지며 눕은 채로 전송된다) + RGB 변환 + 긴 변 다운스케일 + 용량 상한 아래로 JPEG 재인코딩.
  2. 구조화된 에러 컨트랙트. 아이템 에러를 외부 제공자의 raw 덤프 대신, 클라이언트가 분기 가능한 구조화 코드(형식 미지원 / 용량 초과 / 손상 / 일반 엔진오류)로 바꿨다.
  3. 실패를 보이게. 목록 쿼리가 최근 24시간 내 실패 작업을 포함하도록 넓혀, 실패 카드를 띄울 수 있게 했다.

리뷰가 (1)의 EXIF 방향 누락을 1라운드에서 잡았다 — 같은 코드베이스의 다른 두 경로가 이미 exif_transpose를 쓰고 있다는 걸 확인하고 수긍해 고친 뒤, 2라운드에서 통과했다.

가져갈 것


저작·인용: 이 글은 Ascendy Engineering이 작성했으며 출처 표기 시 재인용 가능합니다. 잘못된 정보를 발견하면 GitHub 이슈로 알려주세요.


Tags: image-processing, heic, silent-failure, error-contract, debugging