ES /docs

AWS RDS MySQL failover — read-only mode during write

RCA: QMJob::updateTaskIdToJob MySQL read-only error

Overview#

What Happened#

2026-05-23 21:15~21:48 UTC 시간대에 cupixworks-any-compute-agent 서비스에서 ECS task를 성공적으로 시작한 후 job 상태를 업데이트하려 할 때, 하위 API(api-tesla.cupix.internal)로의 PUT 요청이 MySQL read-only 에러로 실패했다. 동일 시간대에 cupixworks-api, cupixworks-worker 등 여러 서비스에서 동일한 read-only 에러가 발생하여 RDS failover 이벤트로 인한 전체 쓰기 중단임을 확인했다.

Quick Facts#

Field Value
exception.class HttpError (agent-side), Cupix::Errors::Parameter (API-side)
exception.message Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement
top_frame qmJob.ts:183job_repository.rb:47
env production, us-west-2

Affected Teams#

Team / Domain Error Count Impact
ellisdon (cupix tenant) 2 Job 1085021, 1085024의 task_id 업데이트 실패 → ECS task 강제 중지

Timeline#

  1. 2026-05-23T21:08:02Zcupixworks-worker에서 최초 read-only 에러 감지 (flush_geo_coordinate)
  2. 2026-05-23T21:15:26Zcupixworks-any-compute-agent job 1085021 updateTaskIdToJob 실패 (최초 클러스터 발생)
  3. 2026-05-23T21:20:26Zcupixworks-api에서 Eventable::Events::Update read-only 에러
  4. 2026-05-23T21:48:20Zcupixworks-any-compute-agent job 1085024 updateTaskIdToJob 실패 (마지막 클러스터 발생)
  5. 2026-05-23T21:56:45Zcupixworks-api에서 Clusters, References 등 다수 엔드포인트 read-only 에러 지속

Error Log#

Datadog Logs

text
QMJob::updateTaskIdToJob | end - job id: 1085021, error: {"response":{"statusCode":400,"body":{"result":{"code":"ARG10001","type":"Cupix::Errors::Parameter","reason":"Invalid argument","message":"Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement"}}}, ...}

Impact#

  • Service: cupixworks-any-compute-agent
  • Team: varcomac
  • 발생 횟수: 2
  • 최초 발생: 2026-05-23T21:15:26.384Z
  • 최근 발생: 2026-05-23T21:48:20.299Z

영향을 받은 job ID: 1085021, 1085024 (둘 다 create_capture_3d_reconstruction 종류). ECS task가 성공적으로 시작된 후 task_id 업데이트가 실패하면 qmAws.stopTask()가 호출되어 해당 ECS task가 강제 중지된다. 사용자의 3D reconstruction 작업이 중단되었을 가능성이 있다.

Root Cause Summary#

AWS RDS MySQL 인스턴스가 failover 과정에서 read-only 모드로 전환되어 모든 write 쿼리가 거부되었다. cupixworks-any-compute-agent가 ECS task 시작 후 PUT /api/v1/jobs/:id API를 호출하여 processing_statustask_id를 업데이트하려 했으나, downstream API(api-tesla.cupix.internal)의 JobRepository#update에서 @model.save! 호출 시 MySQL read-only 에러가 발생했다. 이 에러는 Cupix::Errors::Parameter로 래핑되어 400 응답으로 반환되었고, agent는 이를 받아 해당 ECS task를 강제 중지했다.

Technical Analysis#

Code Path#

  1. Entry point: job.manager.ts:119_qmAws.runTask()로 ECS task 시작 성공
  2. Update call: job.manager.ts:120job.updateTaskIdToJob()로 job 상태 업데이트 시도
  3. API call: qmJob.ts:183-186this.cupixApi.job.update(jobId, { processing_status: 'run_aws_task', task_id: taskArn })
  4. API controller: jobs_controller.rb:13repository_instance.update(params)
  5. Failure point: job_repository.rb:47@model.save!에서 MySQL read-only 에러 발생
  6. Error handling: job_repository.rb:48-49StandardError를 catch하여 Cupix::Errors::Parameter(ARG10001)로 변환
  7. Agent error handling: qmJob.ts:191-194 — catch에서 stopTask() 호출 후 resolve(true) 반환
packages/cupix-tesla-compute-agent/src/model/qmJob.ts:163-196typescript
updateTaskIdToJob = (qmAws: QMAws, clusterName: string, tasks?: AWS.ECS.Task[]): Promise<boolean> => new Promise(resolve => {
    const jobId = this.id;
    // ...
    this.cupixApi.job.update(jobId, {
        processing_status: 'run_aws_task',
        task_id: taskArn
    })
        .then(() => {
            logger.info('QMJob::updateTaskIdToJob | end - job id: %d, task arn: %s', jobId, taskArn);
            resolve(true);
        })
        .catch(ec => {
            logger.error('QMJob::updateTaskIdToJob | end - job id: %d, error: %s', jobId, JSON.stringify(ec));
            qmAws.stopTask(clusterName, this, taskArn, `failed to update task id for job(${jobId})`);
            resolve(true);
        });
});
app/repositories/job_repository.rb:46-49ruby
begin
  @model.save!
rescue StandardError => e
  raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: 'Invalid argument', message: e.message)
end

기대 동작: save! 성공 → 200 응답 → agent가 task를 정상 실행 유지 실제 동작: MySQL read-only → save! 실패 → Cupix::Errors::Parameter로 래핑 → 400 응답 → agent가 task 강제 중지

문제점: API 측에서 인프라 에러(MySQL read-only)를 파라미터 유효성 에러(ARG10001, 400)로 잘못 분류하고 있다. Agent는 이를 복구 불가능한 클라이언트 에러로 인식하여 즉시 ECS task를 중지한다.

Log Evidence#

Datadog 쿼리 — compute agent 에러:

text
service:cupixworks-any-compute-agent status:error "QMJob::updateTaskIdToJob"

Agent 로그 (job 1085024 전체 흐름):

json
{"timestamp": "2026-05-24 06:48:03 KST", "message": "JobManager::createJobFromMessages | message id: d9c528f8-81b6-46a0-ba25-a83ebecfc5c1, body: \"{\\\"job\\\": {\\\"id\\\": 1085024}, ...}\""}
json
{"timestamp": "2026-05-24 06:48:19 KST", "message": "QMAws::runTask | begin - job id: 1085024, task def: cupix-capture-3d-reconstruction-production, instance_type: g6.4xlarge"}
json
{"timestamp": "2026-05-24 06:48:20 KST", "message": "QMJob::updateTaskIdToJob | begin - job id: 1085024, task length: 1"}
json
{"timestamp": "2026-05-24 06:48:20 KST", "status": "error", "message": "QMJob::updateTaskIdToJob | end - job id: 1085024, error: {\"response\":{\"statusCode\":400,\"body\":{\"result\":{\"code\":\"ARG10001\",...,\"message\":\"Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement\"}}}}"}

API 서비스의 동일 시간대 read-only 에러:

text
service:cupixworks-api "read-only" OR "read_only"
json
{"timestamp": "2026-05-24 06:56:45 KST", "message": "[500] POST /api/v1/clusters (Api::V1::ClustersController#create)", "error": {"message": "Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement", "class": "Cupix::Errors::System"}}

Worker 서비스의 동일 시간대 read-only 에러:

text
service:cupixworks-worker status:error "read-only"
json
{"timestamp": "2026-05-24 06:08:02 KST", "status": "error", "message": "flush_geo_coordinate - error - message: Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement"}

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 RDS failover로 인한 일시적 read-only 상태 동일 시간대(21:08~21:56 UTC)에 3개 서비스(api, worker, agent)에서 동일한 Mysql2::Error: read-only 에러 발생. 발생 건수 소량(agent 2건)이며 특정 시간대에 집중됨 Confirmed
H2 특정 job 파라미터 유효성 문제 API 응답 코드가 ARG10001 (Invalid argument)로 표시됨 에러 메시지가 Mysql2::Error: read-only로 DB 인프라 문제임을 명시. GET 요청은 정상 동작 확인 ([200] GET /api/v1/jobs/1085024 성공). 여러 서비스에서 동일 에러 발생은 파라미터 문제가 아님 Rejected
H3 Agent의 API 인증 만료 또는 권한 문제 인증 성공 후 GET 요청 정상, PUT만 실패. 에러 메시지가 권한이 아닌 MySQL read-only를 명시 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 없음. 이 에러는 RDS failover라는 일시적 인프라 이벤트에 의해 발생한 것으로, failover 완료 후 자연 해소됨.

단기 개선 (1주 이내)#

  • job_repository.rb:46-49: Mysql2::ErrorCupix::Errors::Parameter(400)가 아닌 Cupix::Errors::System(500 또는 503)으로 분류하도록 변경. 현재 StandardError catch가 모든 에러를 파라미터 에러로 래핑하고 있어, 일시적 인프라 에러를 영구적 클라이언트 에러로 잘못 전달한다.
  • qmJob.ts:191-194: updateTaskIdToJob catch 핸들러에서 HTTP 5xx (또는 특정 에러 메시지)를 감지하면 즉시 stopTask()를 호출하지 않고 재시도 로직을 추가. 현재는 모든 실패에 대해 ECS task를 즉시 중지하여 불필요한 작업 손실이 발생한다.

장기 개선 (재발 방지)#

  • RDS failover 시 Rails 측에서 자동 재연결/retry 메커니즘 도입 (예: ActiveRecord::ConnectionAdapters의 reconnect 설정 또는 middleware 수준 retry)
  • Compute agent에서 API 호출 실패 시 exponential backoff retry 패턴 적용 — 일시적 인프라 에러(503, read-only)에는 task를 즉시 중지하지 않고 일정 시간 재시도 후 실패 처리

Monitoring#

  • RDS failover 이벤트 발생 시 알림: AWS RDS Event Subscription으로 failover 감지
  • Datadog 쿼리 — read-only 에러 모니터:
text
service:cupixworks-api "read-only option" status:error
  • Agent의 updateTaskIdToJob 실패 후 stopTask 호출 빈도 모니터링:
text
service:cupixworks-any-compute-agent "stopTask" OR "failed to update task id"

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: standard
  • RDS failover는 드문 인프라 이벤트이며, 이 클러스터의 발생 건수도 2건으로 소량. 그러나 발생 시 ECS task가 불필요하게 중지되어 사용자 작업이 손실될 수 있으므로, retry 로직 추가를 권장.