ES /docs

flush_geo_coordinate - error - message: Mysql2::Error: The MySQL server is running with the --read-o

RCA: flush_geo_coordinate - Mysql2::Error: read-only

Overview#

What Happened#

2026-05-23 21:08:02 UTC에 cupixworks-worker 서비스의 flush_geo_coordinate 메서드가 Record ID 129543에 대해 S3 업로드 완료 후 MySQL에 geo_coordinate_url_updated_at 타임스탬프를 기록하려 할 때, MySQL 서버가 read-only 모드로 전환되어 있어 쓰기가 실패했다. 단일 발생 이벤트로, 약 7분 후 cron 재시도에서 정상 처리되었다.

Quick Facts#

Field Value
exception.class Mysql2::Error
exception.message The MySQL server is running with the --read-only option so it cannot execute this statement
top_frame app/models/concerns/record_geo_coordinate.rb:83
env production, us-west-2
deploy production-us-west-2-20260519T0920Z0-3e770a15-cupixworks

Timeline#

  1. 2026-05-23T21:08:02Z — Aurora failover 시작 추정. flush_geo_coordinate에서 첫 번째 read-only 에러 발생
  2. 2026-05-23T21:15:26ZQMJob::updateTaskIdToJob에서 두 번째 read-only 에러 (worker)
  3. 2026-05-23T21:15:30Z — Cron job finalize_delayed_geo_coordinate가 Record 129543 재시도 → 성공
  4. 2026-05-23T21:20:26ZEventable::Events::Update#create_event에서 세 번째 read-only 에러 (API)
  5. ~2026-05-23T21:20:26Z 이후 — 커넥션 풀 갱신 완료, 정상화

Error Log#

Datadog Logs

text
flush_geo_coordinate - error - message: Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement

Impact#

  • Services: cupixworks-worker, cupixworks-api
  • 총 read-only 에러: 3건 (worker 2건, API 1건)
  • 영향 시간: 약 12분 (21:08 ~ 21:20 UTC)
  • 최초 발생: 2026-05-23T21:08:02.658Z
  • 최근 발생: 2026-05-23T21:20:26.310Z

Aurora failover로 인해 12분간 일부 DB 커넥션이 read-only 상태에 머물러 3개 코드 경로에서 쓰기 실패. flush_geo_coordinate (Record 129543)는 Cron 재시도로 7분 내 자동 복구됨. QMJob, Eventable 에러도 각각의 retry 메커니즘으로 복구된 것으로 추정.

Root Cause Summary#

Aurora MySQL 클러스터에서 자동 failover가 발생하여, 기존 Writer 인스턴스가 Reader로 강등되면서 약 12분간(21:08~21:20 UTC) 일부 커넥션이 read-only 상태의 구 Writer에 연결된 채로 쓰기를 시도하여 실패했다. 총 3건의 read-only 에러가 cupixworks-worker(2건)와 cupixworks-api(1건) 양쪽에서 서로 다른 코드 경로에서 발생하였으며, 이는 전형적인 Aurora failover 패턴이다. 2026-03-10에 rds_replica_count = 2로 Read Replica가 추가되어 Multi-AZ failover가 활성화된 상태이나, 해당 인프라 변경은 에러 발생 2.5개월 전이며 직접적 트리거는 아니다.

Endpoint 사용 현황: AWS 권장대로 Aurora Cluster Endpoint (aws_rds_cluster.this.endpoint)를 사용 중이며, 개별 instance endpoint는 사용하지 않는다. 그러나 Route53 CNAME(TTL 300초)이 cluster endpoint를 한 번 더 래핑하고 있어, failover 시 DNS 갱신에 최대 5분의 추가 지연이 발생한다. 또한 reconnect: true 설정은 reactive(실패 후 재연결)이므로, 기존 connection pool의 stale connection이 failover 직후에는 계속 read-only 구 Writer에 연결된다.

Technical Analysis#

Code Path#

  • Entry point: app/workers/flush_record_geo_coordinate_worker.rb:7perform(record_id) 호출
  • Worker가 record.flush_geo_coordinate(accept_delay: false) 호출 (line 31)
  • app/models/concerns/record_geo_coordinate.rb:37flush_geo_coordinate 시작
  • Line 38: Redis lock 획득
  • Lines 45-81: S3에 geo coordinate JSON 스트림 업로드 (batch#0, batch#1 완료)
  • Failure point: Line 83: update(geo_coordinate_url_updated_at: DateTime.now) — MySQL 쓰기 시도 시 read-only 에러
  • Lines 90-94: rescue 블록에서 lock 해제 후 에러 로깅
app/models/concerns/record_geo_coordinate.rb:82-97ruby
    update(geo_coordinate_url_updated_at: DateTime.now)  # ← 실패 지점

    Cupix::Logger.info("flush_geo_coordinate - finished - duration: ...")

    _flush_delayed_geo_coordinate

    fresh_fresh_state!
  rescue StandardError => e
    unlock_flushing_geo_coordinate

    Cupix::Logger.error("flush_geo_coordinate - error - message: #{e.message}", ...)
    false
  else
    unlock_flushing_geo_coordinate
  end

에러 발생 시 rescue 블록이 실행되어 Redis lock은 해제되지만, _delayed_flush_geo_coordinate 캐시 키는 남아있다. 이후 cron job finalize_delayed_geo_coordinate가 이 키를 감지하고 재시도를 수행한다:

lib/cupix/cron/record.rb:16-39ruby
def finalize_delayed_geo_coordinate
  flush_geo_coordinate_keys = []
  Rails.cache.redis_instance.scan_each(match: 'FlushGeoCoordinate::Record::*', count: 1000) do |key|
    flush_geo_coordinate_keys << key
  end
  # ...
  cache_mvalues.each do |key, value|
    record = ::Record.find(record_id)
    record.flush_geo_coordinate  # 재시도
    Rails.cache.redis.del(key)
  end
end

Log Evidence#

Datadog 쿼리 (전체 서비스, 45분 윈도우):

text
status:error "read-only" OR "readonly"
Time: 2026-05-23T20:45:00Z to 2026-05-23T21:30:00Z

결과: 3건의 read-only 에러가 2개 서비스, 3개 코드 경로에서 12분간 발생:

시각 (UTC) 서비스 코드 경로
21:08:02 cupixworks-worker Record#flush_geo_coordinate
21:15:26 cupixworks-worker QMJob::updateTaskIdToJob (API 400 응답)
21:20:26 cupixworks-api Eventable::Events::Update#create_event

에러 발생 시점 로그:

text
2026-05-23T21:08:02Z [info] flush_geo_coordinate for Record ID: 129543 - batch#0
2026-05-23T21:08:02Z [info] flush_geo_coordinate for Record ID: 129543 - batch#1
2026-05-23T21:08:02Z [error] flush_geo_coordinate - error - message: Mysql2::Error: The MySQL server is running with the --read-only option so it cannot execute this statement

Cron 재시도 로그:

text
2026-05-23T21:15:30Z [info] [Cron][Record] Flushing 1 delayed geo coordinates - record_ids: 129543
2026-05-23T21:15:32Z [info] flush_geo_coordinate for Record ID: 129543 - batch#0
2026-05-23T21:15:34Z [info] flush_geo_coordinate for Record ID: 129543 - batch#1
2026-05-23T21:15:34Z [info] [Cron][Record] Flushing delayed geo coordinate: 129543 - duration for flushing: 4 sec

Connection 에러 확인:

text
service:cupixworks-worker ("Lost connection" OR "Gone away" OR "reconnect" OR "failover")
Time: 2026-05-23T20:45:00Z to 2026-05-23T21:30:00Z
Result: 0 logs

명시적 connection drop이 없는 것은 Aurora failover의 특성 — TCP 커넥션은 유지되지만 구 Writer가 Reader로 강등되어 --read-only 상태가 되는 패턴과 일치한다.

Hypotheses Considered#

# Hypothesis Evidence for Evidence against Verdict
H1 Aurora 자동 failover로 인한 일시적 read-only 전환 3건 에러가 2개 서비스, 3개 코드 경로에서 12분간 발생 — 전형적 failover 패턴. TCP 커넥션은 유지되나 구 Writer가 Reader로 강등. 커넥션 풀 갱신 후 자동 복구됨. 없음 Confirmed
H2 최근 배포에서 read-only DB 생성이 직접 원인 2026-03-10에 rds_replica_count = 2 추가 (cupix-infrastructure commit acd798e5). Read Replica 존재가 failover 가능성을 활성화함. Replica 추가는 에러 발생 2.5개월 전. Replica 추가 자체는 failover를 유발하지 않음. Worker의 RAILS_DB_HOST는 cluster endpoint(Writer)를 가리키며, reader endpoint와 무관. Partially Confirmed
H3 Worker가 DB_SEARCH_REPLICA_HOST (reader endpoint)에 연결되어 쓰기 시도 DB_SEARCH_REPLICA_HOSTdefault_environment_variables에 포함되어 worker에도 전달됨 이 env var는 app/services/cupix/compass/db_search/query_executor.rb에서만 사용 (:reading role로 분리). Worker의 flush_geo_coordinateRAILS_DB_HOST (cluster endpoint)로 연결 Rejected
H4 MySQL SET GLOBAL read_only=1 수동 설정 read-only 에러 메시지 일치 12분간만 영향, 자동 해소. 수동이면 더 광범위한 영향 예상 Rejected

Fix Recommendation#

즉시 조치 (Critical)#

  • 현재 조치 불필요. 단일 발생 이벤트이며, 기존 cron 재시도 메커니즘(finalize_delayed_geo_coordinate)이 정상 작동하여 7분 내 자동 복구됨.

단기 개선 (1주 이내)#

  • Route53 CNAME TTL 단축: route53-internal/main.tf:36의 TTL을 30060으로 줄여 failover 시 DNS 갱신 지연을 5분→1분으로 단축. AWS에서 Cluster Endpoint 사용 시 CNAME TTL은 짧을수록 failover 복구가 빠르다.
  • flush_geo_coordinate 메서드(line 83)에서 Mysql2::Error 발생 시 에러를 로깅하되 warn 레벨로 기록하고, Sidekiq retry (현재 retry: 1) 활용을 고려. 현재 rescue 블록에서 에러를 삼키고 false를 반환하므로 Sidekiq retry가 트리거되지 않음.
  • 대안: rescue 블록에서 connection-level 에러(read-only, lost connection)를 별도 처리하여 즉시 retry하거나, _delayed_flush_geo_coordinate를 명시적으로 설정하여 cron 재시도를 보장.

장기 개선 (재발 방지)#

  • RDS Proxy 도입 검토: AWS 권장대로 Cluster Endpoint를 사용 중이지만, RDS Proxy를 추가하면 failover 시 기존 connection을 자동으로 새 Writer로 라우팅하여 application-level reconnect 없이 복구 가능. Connection pool 관리를 RDS Proxy에 위임하면 12분→수초 수준으로 복구 시간 단축.
  • Connection pool 검증 추가: Rails database.ymlcheckout_timeout과 함께, ActiveRecord connection pool에서 checkout 시 SELECT 1 ping으로 stale connection을 사전 검출하는 middleware 도입.
  • S3 업로드와 DB update를 분리하여, S3 업로드 성공 후 DB update 실패 시 S3 데이터와 DB 상태 불일치를 방지하는 idempotent 설계 검토.

Monitoring#

  • MySQL read-only 에러 발생 빈도 모니터링 추가:
text
service:cupixworks-worker status:error "read-only option"
  • RDS failover 이벤트를 Datadog Event로 수집하여 에러와 correlation 확인.

Risk Assessment#

  • Risk level: low
  • 예상 복잡도: trivial
  • Aurora failover로 인한 일시적 인프라 이벤트. 코드 결함이 아닌 DB 상태 변경이 원인. 기존 retry 메커니즘이 정상 동작함. 단, 커넥션 풀 갱신에 12분 소요된 점은 개선 가능.

Revision History#

Revision 1#

Feedback: "readonly 에러가 왜 나는거임? 이거 혹시 최근 배포에 read only db 생성 이런 이슈가 있었는지 찾아바"

판정:

피드백 항목 판정 근거
read-only 에러 원인 상세 분석 수용 기존 보고서는 "단 1건, API 0건"으로 축소 보고했으나, 45분 윈도우 재검색 결과 3건 에러 / 2개 서비스 / 12분간 지속 확인. Aurora failover 시 TCP 커넥션은 유지되나 구 Writer가 Reader로 강등되어 --read-only 상태가 됨. 커넥션 풀이 갱신될 때까지 쓰기 실패 패턴 — "Lost connection" 로그 0건이 이를 뒷받침.
최근 배포에 read-only DB 생성 이슈 확인 부분 수용 cupix-infrastructure commit acd798e5 (2026-03-10): rds_replica_count = 2 추가로 모든 리전 production에 Read Replica 배포됨. 이것이 Aurora Multi-AZ failover를 가능하게 한 인프라 전제 조건이나, 에러 발생(05-23)과 2.5개월 간격이므로 직접 트리거는 아님. Worker의 RAILS_DB_HOST는 Route53 CNAME → rds_cluster_endpoint (Writer endpoint)를 가리키며(cupix-service/route53-internal/main.tf:38, cupix-service/main.tf:234), reader endpoint와 분리되어 있음.

변경 사항:

  • Root Cause Summary: "단 1건" → "3건/2서비스/12분" 으로 정정, Aurora failover 메커니즘 상세 설명 추가
  • Timeline: failover 전체 스팬(21:08~21:20) 반영, 3개 에러 시점 명시
  • Log Evidence: 45분 윈도우 재검색 결과 테이블 추가, connection drop 부재가 failover 특성임을 설명
  • Hypotheses: H2를 "최근 배포의 replica 추가"로 변경, H3(DB_SEARCH_REPLICA_HOST 가설) 추가 후 거부
  • Impact: 서비스 2개, 에러 3건, 12분 영향으로 정정

추가 조사 내용:

  • cupix-infrastructure 레포: RDS replica 배포 이력 확인 (commit acd798e5, cupix-aws2/production/terragrunt.hcl)
  • cupix-infrastructure 레포: Route53 내부 DNS → rds_cluster_endpoint (Writer) 매핑 확인 (route53-internal/main.tf:32-39)
  • cupix-infrastructure 레포: DB_SEARCH_REPLICA_HOST가 API/Worker 공통 env에 포함되나, Tesla 코드에서 :reading role 전용으로만 사용됨 확인 (api-eb/main.tf:52)
  • Datadog 로그: 45분 윈도우에서 3건의 cross-service read-only 에러 발견, connection drop 로그 0건 확인

Revision 2#

Feedback: "AWS Aurora Endpoints 문서(https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.Overview.Endpoints.html)를 사용하라고 AWS에서 권장했다는데, 이런 경우(read-only failover)가 많이 발생해서 우리도 이렇게 사용하고 있는지 확인 요청"

판정:

피드백 항목 판정 근거
Aurora Cluster Endpoint 사용 여부 확인 수용 확인 결과: AWS 권장대로 Cluster Endpoint를 이미 사용 중. cupix-infrastructure/cupix-service/_cupix-modules/rds/outputs.tf:17-20에서 aws_rds_cluster.this.endpoint (Cluster Endpoint)를 output으로 내보내고, main.tf:234에서 database_endpoint = module.rds.rds_cluster_endpoint로 Route53에 전달. route53-internal/main.tf:32-38에서 CNAME으로 래핑. api-eb/main.tf:12에서 RAILS_DB_HOST = var.db_host (= Route53 internal domain). 개별 instance endpoint는 앱에서 사용하지 않음. 단, Route53 CNAME TTL=300s가 failover 복구를 지연시키는 요인으로 확인됨.
빈번한 read-only 에러 재발 우려 부분 수용 Cluster Endpoint를 사용 중이므로 endpoint 설정 자체는 정상이나, TTL 300초의 CNAME 래핑(route53-internal/main.tf:36)과 reactive-only reconnect(tesla/config/database.yml:66reconnect: true는 실패 후에만 재연결)가 복구 시간을 12분으로 늘린 원인. RDS Proxy 미사용 확인 (infrastructure에서 rds_proxy 리소스 없음). TTL 단축(60초) + RDS Proxy 도입으로 재발 시 영향 최소화 가능.

변경 사항:

  • Root Cause Summary: Aurora Cluster Endpoint 사용 현황 확인 결과 추가 (AWS 권장 준수 중이나, Route53 TTL 300초와 reactive reconnect가 복구 지연 원인)
  • Fix Recommendation: Route53 TTL 단축(300→60초)을 단기 개선에 추가, RDS Proxy 도입을 장기 개선에 추가, Connection pool 검증 middleware 추가

추가 조사 내용:

  • cupix-infrastructure/cupix-service/_cupix-modules/rds/outputs.tf: rds_cluster_endpointaws_rds_cluster.this.endpoint (Cluster Endpoint) 확인
  • cupix-infrastructure/cupix-service/main.tf:234: database_endpoint = module.rds.rds_cluster_endpoint로 Route53에 전달 확인
  • cupix-infrastructure/cupix-service/route53-internal/main.tf:32-38: CNAME으로 cluster endpoint 래핑, TTL=300초 확인
  • cupix-infrastructure/cupix-service/api-eb/main.tf:12: RAILS_DB_HOST = var.db_host (Route53 internal domain) 확인
  • tesla/config/database.yml:62-72: production 환경에서 reconnect: true, pool 50, RAILS_DB_HOST env var 사용 확인
  • RDS Proxy: infrastructure 전체에서 rds_proxy 리소스 미사용 확인
  • DNS 경로 전체 추적: App → RAILS_DB_HOST (Route53 CNAME, TTL 300s) → Aurora Cluster Endpoint (자동 failover) → Writer Instance