ES /docs

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 해상도로 렌더링을 시도하다 실패했으며, execSyncstdio: '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#

  1. 2026-05-24 07:09:36 KSTBaseService::runByMessage floorplan ID 86959 처리 시작
  2. 2026-05-24 07:09:38 KSTFloorplanService::translateFloorplan 호출 (source: 35323.pdf, frame: 1, 16384x16384, 300 DPI, LOD: 6)
  3. 2026-05-24 07:09:40 KSTgm convert 명령 실패, GraphicsMagickManager::execute 에러 로그
  4. 2026-05-24 07:09:40 KSTBaseService::handlingMessageErrors — SQS 메시지 삭제 처리
  5. 2026-05-24 07:09:40 KST — workspace cleanup 수행

Error Log#

Datadog Logs

text
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.execSyncstdio: '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-181
  • GraphicsMagickManager::execute 호출: graphics-magick.manager.ts:24-51
  • ChildProcessManager::execute → child process fork: child-process.manager.ts:186-198
  • Failure point: GraphicsMagickProcess::execute (graphics-magick.process.ts:27-86) — child_process.execSync 실행 시점
packages/cupix-tesla-floorplan-agent/src/process/graphics-magick.process.ts:27-71typescript
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.execSyncstdio: 'ignore'로 인해 GM의 stderr 출력(실제 변환 에러 상세)이 버려진다. 또한 GM의 -log "%t [%d] %m::%f:%l %e" 포맷 문자열의 %d(GM internal depth 변수)가 Node.js logger의 printf-style 포맷팅에 의해 NaN으로 변환되어 표시된다.

packages/cupix-tesla-floorplan-agent/src/manager/graphics-magick.manager.ts:37-50typescript
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::runBaseService::handlingMessageErrors로 전파된다:

packages/base/src/base-service.ts:240-252typescript
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#

검색 쿼리:

text
service:cupixworks-any-floorplan-agent "86959"

시간순 로그 타임라인:

text
[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 로그에서 추출):

text
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:70execSyncstdio: '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 실패율 모니터링:
text
service:cupixworks-any-floorplan-agent status:error "GraphicsMagickManager::execute"
  • PDF 변환 성공/실패 비율 메트릭 추가 (custom metric 또는 StatsD)
  • 처리 시간 이상 감지: translateFloorplan 시작~완료 간 소요시간 기준 알림

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard