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#
- 2026-05-23T21:08:02Z — Aurora failover 시작 추정.
flush_geo_coordinate에서 첫 번째 read-only 에러 발생 - 2026-05-23T21:15:26Z —
QMJob::updateTaskIdToJob에서 두 번째 read-only 에러 (worker) - 2026-05-23T21:15:30Z — Cron job
finalize_delayed_geo_coordinate가 Record 129543 재시도 → 성공 - 2026-05-23T21:20:26Z —
Eventable::Events::Update#create_event에서 세 번째 read-only 에러 (API) - ~2026-05-23T21:20:26Z 이후 — 커넥션 풀 갱신 완료, 정상화
Error Log#
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:7—perform(record_id)호출 - Worker가
record.flush_geo_coordinate(accept_delay: false)호출 (line 31) app/models/concerns/record_geo_coordinate.rb:37—flush_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 해제 후 에러 로깅
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가 이 키를 감지하고 재시도를 수행한다:
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분 윈도우):
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 |
에러 발생 시점 로그:
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 재시도 로그:
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 에러 확인:
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_HOST가 default_environment_variables에 포함되어 worker에도 전달됨 |
이 env var는 app/services/cupix/compass/db_search/query_executor.rb에서만 사용 (:reading role로 분리). Worker의 flush_geo_coordinate는 RAILS_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을300→60으로 줄여 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.yml에checkout_timeout과 함께, ActiveRecord connection pool에서 checkout 시SELECT 1ping으로 stale connection을 사전 검출하는 middleware 도입. - S3 업로드와 DB update를 분리하여, S3 업로드 성공 후 DB update 실패 시 S3 데이터와 DB 상태 불일치를 방지하는 idempotent 설계 검토.
Monitoring#
- MySQL read-only 에러 발생 빈도 모니터링 추가:
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 배포 이력 확인 (commitacd798e5,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 코드에서:readingrole 전용으로만 사용됨 확인 (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:66 — reconnect: 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_endpoint가aws_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_HOSTenv var 사용 확인- RDS Proxy: infrastructure 전체에서
rds_proxy리소스 미사용 확인 - DNS 경로 전체 추적: App →
RAILS_DB_HOST(Route53 CNAME, TTL 300s) → Aurora Cluster Endpoint (자동 failover) → Writer Instance