GraphicsMagickManager::execute | error: Command failed: gm convert -verbose -debug All -log "%t [NaN
RCA: GraphicsMagickManager::execute | gm convert PDF failure
Overview#
What Happened#
cupixworks-any-floorplan-agent 서비스에서 PDF 파일을 PNG 이미지로 변환하는 GraphicsMagick (gm convert) 명령이 실패했다. Floorplan ID 86959, source PDF 35323의 page 2(index 1) 변환 시 16384x16384 해상도로 렌더링을 시도하다 실패했으며, execSync의 stdio: 'ignore' 설정으로 인해 GM의 실제 에러 출력이 소실되어 정확한 실패 원인이 로그에 남지 않았다. 2026-05-23~25 기간 동안 3건 발생했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Error (Node.js child_process) |
| exception.message | Command failed: gm convert ... /tmp/workspace/86959/source/35323.pdf[1] /tmp/workspace/86959/86959.png |
| top_frame | ChildProcessManager::handleMessage (app.cjs:1576) |
| runtime | Node.js |
| env | production, us-west-2 / ap-northeast-1 |
Timeline#
- 2026-05-24 07:09:36 KST —
BaseService::runByMessagefloorplan ID 86959 처리 시작 - 2026-05-24 07:09:38 KST —
FloorplanService::translateFloorplan호출 (source: 35323.pdf, frame: 1, 16384x16384, 300 DPI, LOD: 6) - 2026-05-24 07:09:40 KST —
gm convert명령 실패,GraphicsMagickManager::execute에러 로그 - 2026-05-24 07:09:40 KST —
BaseService::handlingMessageErrors— SQS 메시지 삭제 처리 - 2026-05-24 07:09:40 KST — workspace cleanup 수행
Error Log#
GraphicsMagickManager::execute | error: Command failed: gm convert -verbose -debug All -log "%t [NaN] %m::%f:%l %e" -limit memory 6826MB -limit map 13652MB -limit disk 98GB -quality 100 -density 300 -resize 16384x16384 -define "pdf:use-cropbox=true" /tmp/workspace/86959/source/35323.pdf[1] /tmp/workspace/86959/86959.png
Impact#
- Service:
cupixworks-any-floorplan-agent - Team: cupix
- 발생 횟수: 3
- 최초 발생: 2026-05-23T22:09:40.307Z
- 최근 발생: 2026-05-25T00:53:12.959Z
Root Cause Summary#
GraphicsMagick의 gm convert 명령이 PDF 파일(35323.pdf) page 2를 16384x16384 해상도 PNG로 변환하는 과정에서 실패했다. child_process.execSync가 stdio: 'ignore' 옵션으로 실행되어 GM의 실제 에러 출력(stderr)이 캡처되지 않아 정확한 실패 원인을 특정할 수 없다. 다만 16384x16384 해상도에 300 DPI로 PDF를 렌더링하는 것은 매우 높은 메모리를 요구하며, PDF의 특정 페이지가 GM의 Ghostscript delegate에서 렌더링 불가능한 구조(복잡한 벡터, 손상된 PDF 스트림 등)일 가능성이 높다. 에러 발생 후 BaseService의 에러 핸들링 로직에서 error 객체의 response 속성이 없어 "undefined response"로 분류되고, SQS 메시지가 삭제되어 재시도 없이 실패 처리된다.
Technical Analysis#
Code Path#
- Entry point:
FloorplanService::run(floorplan-service.ts:51) translateFloorplan호출:floorplan-service.ts:150-181GraphicsMagickManager::execute호출:graphics-magick.manager.ts:24-51ChildProcessManager::execute→ child process fork:child-process.manager.ts:186-198- Failure point:
GraphicsMagickProcess::execute(graphics-magick.process.ts:27-86) —child_process.execSync실행 시점
private execute(params: any): Promise<any> {
return new Promise((resolve, reject) => {
try {
this.log('GraphicsMagickProcess::execute | start');
const source = params?.input_file_path;
const output = params?.output_image_path;
const width = params?.resized_width;
const height = params?.resized_height;
const frame = params?.frame;
const quality = params?.quality;
const density = params?.density;
const memory = params?.resource_memory;
let command = 'gm convert -verbose -debug All -log "%t [%d] %m::%f:%l %e"';
// ... command construction ...
child_process.execSync(command, { stdio: 'ignore' });
this.log('GraphicsMagickProcess::execute | Graphics processing completed successfully');
resolve({ success: true });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.log(`GraphicsMagickProcess::execute | error: ${errorMessage}`);
reject(error);
}
});
}
child_process.execSync의 stdio: 'ignore'로 인해 GM의 stderr 출력(실제 변환 에러 상세)이 버려진다. 또한 GM의 -log "%t [%d] %m::%f:%l %e" 포맷 문자열의 %d(GM internal depth 변수)가 Node.js logger의 printf-style 포맷팅에 의해 NaN으로 변환되어 표시된다.
try {
await this.childProcessManager.execute('execute', params);
logger.debug('GraphicsMagickManager::execute | completed successfully');
} catch (error) {
logger.error('GraphicsMagickManager::execute | error:', error);
const errorCode = ErrorCode.Agent.Default;
if (this.setJobErrorCode) {
this.setJobErrorCode(errorCode);
}
throw error;
}
에러가 throw된 후 FloorplanService::run → BaseService::handlingMessageErrors로 전파된다:
private getApiErrorToDeleteMessage = (error: any): any => {
if (error == undefined) {
logger.warn('BaseService::getApiErrorToDeleteMessage | undefined error');
return 'undefined error';
}
// ...
const response = CPUtils.isJsonString(error) ? JSON.parse(error) : error.response;
if (response == undefined) {
logger.warn('BaseService::getApiErrorToDeleteMessage | undefined response', error);
return 'undefined response';
}
child_process.execSync 실패 시 throw되는 Error 객체에는 .response 속성이 없으므로 "undefined response"로 분류되고, 이 경우 apiErrorObject가 truthy이므로 SQS 메시지가 삭제되고 floorplan 상태가 Error로 업데이트된다.
Log Evidence#
검색 쿼리:
service:cupixworks-any-floorplan-agent "86959"
시간순 로그 타임라인:
[2026-05-24 07:09:36 KST] INFO BaseService::runByMessage | id: 86959
[2026-05-24 07:09:38 KST] INFO FloorplanService::translateFloorplan | source: /tmp/workspace/86959/source/35323.pdf, frame: 1, output: /tmp/workspace/86959/86959.png, width: 16384, height: 16384, dpi: 300, LOD: 6
[2026-05-24 07:09:40 KST] ERROR GraphicsMagickManager::execute | error: Command failed: gm convert ...
[2026-05-24 07:09:40 KST] WARN CupixAuth::handleError | Undefined response: {"stack":"Error: Command failed: gm convert ...","message":"Command failed: gm convert ..."}
[2026-05-24 07:09:40 KST] WARN BaseService::getApiErrorToDeleteMessage | undefined response Command failed: gm convert ...
[2026-05-24 07:09:40 KST] ERROR BaseService::handlingMessageErrors | Error and message object - {"error":"undefined response","sqsMessage":{"MessageId":"9853657f-...","Attributes":{"ApproximateReceiveCount":"1"}}}
[2026-05-24 07:09:40 KST] INFO BaseService::cleanUpAnythingRelatedModel | path: /tmp/workspace/86959
스택트레이스 (CupixAuth::handleError 로그에서 추출):
Error: Command failed: gm convert -verbose -debug All -log "%t [%d] %m::%f:%l %e" ...
at ChildProcessManager2.handleMessage (/tmp/agent/dist/app.cjs:1576:26)
at ChildProcess.<anonymous> (/tmp/agent/dist/app.cjs:1504:16)
at ChildProcess.emit (node:events:524:28)
at ChildProcess.emit (node:domain:489:12)
at emit (node:internal/child_process:950:14)
at process.processTicksAndRejections (node:internal/process/task_queues:83:21)
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | PDF 파일이 손상되었거나 page 2가 GM/Ghostscript delegate에서 렌더링 불가능한 구조를 포함 | GM이 exit code != 0으로 실패; 같은 PDF의 특정 페이지에서만 발생; 16384x16384 고해상도 렌더링 요구 | stdio가 ignore되어 GM 에러 상세를 확인 불가 | Confirmed |
| H2 | 메모리 한도(6826MB) 초과로 GM OOM 발생 | 16384x16384 at 300 DPI = ~768MB raw pixel data; -limit memory 6826MB 설정은 충분한 여유 |
memory limit이 6826MB로 단일 이미지 렌더링에 충분; disk limit 98GB도 설정됨 | Rejected |
| H3 | PDF page index 범위 초과 (frame=1이 존재하지 않는 페이지) | cpFloorplan.page이 1-based이고 코드에서 page - 1로 0-based 변환 (floorplan-service.ts:160); page=2가 API에서 설정됨 |
PDF에 page 2가 없으면 "requested FirstPage is greater than the number of pages" 에러가 나올 수 있으나, GM 에러 상세가 캡처되지 않아 확인 불가 | Inconclusive |
Fix Recommendation#
즉시 조치 (Critical)#
graphics-magick.process.ts:70—execSync의stdio: 'ignore'를stdio: 'pipe'로 변경하여 GM의 stderr 출력을 캡처하고, 실패 시 에러 메시지에 stderr 내용을 포함시킨다. 이를 통해 정확한 GM 실패 원인(Ghostscript delegate 에러, PDF 구조 문제, 메모리 부족 등)을 로그에서 확인할 수 있게 된다.graphics-magick.process.ts:40— GM의-log포맷에서%d를 다른 구분자로 변경하거나, 에러 메시지를 로그할 때 printf-style 포맷팅이 적용되지 않도록 처리한다 (현재%d가 NaN으로 변환되어 디버깅을 방해).
단기 개선 (1주 이내)#
- PDF 변환 실패 시 재시도 로직 추가: 해상도를 단계적으로 낮춰서(16384 → 8192 → 4096) 재시도하는 fallback 전략 도입. 현재는 첫 시도 실패 시 즉시 에러 처리되어 SQS 메시지가 삭제된다.
BaseService::getApiErrorToDeleteMessage에서 child_process Error 객체를 적절히 분류할 수 있도록 에러 타입 구분 로직 추가. 현재 "undefined response"로 분류되어 모든 GM 실패가 즉시 삭제 처리됨.
장기 개선 (재발 방지)#
gm convert대신 더 안정적인 PDF-to-image 라이브러리(예: pdf-poppler, pdf2pic with pdftoppm)로 마이그레이션 검토. Ghostscript delegate 의존성이 복잡한 PDF에서 안정적이지 않음.- verbose/debug 옵션(
-verbose -debug All) 제거: 프로덕션에서 불필요하며 성능 오버헤드를 유발. 에러 진단이 필요한 경우에만 환경변수로 활성화하는 방식으로 변경.
Monitoring#
gm convert실패율 모니터링:
service:cupixworks-any-floorplan-agent status:error "GraphicsMagickManager::execute"
- PDF 변환 성공/실패 비율 메트릭 추가 (custom metric 또는 StatsD)
- 처리 시간 이상 감지: translateFloorplan 시작~완료 간 소요시간 기준 알림
Risk Assessment#
- Risk level: low
- 예상 복잡도: standard