{"id":"IL-0310","org":"Hightouch","date":"2025-10-23","docs":[],"title":{"en":"An Aurora PostgreSQL failover to an upsized reader reversed twice and backend writes failed in read-only transactions","ko":"Aurora PostgreSQL 의 수동 장애 조치가 스스로 되돌아가 원래 writer 가 남고 백엔드의 쓰기 쿼리가 실패했다"},"domain":["database","aws"],"impact":{"scope":"Hightouch Events backend services could not run write queries during two failover attempts until they were restarted, and the planned database upsize failed until a controlled failover with all database writers scaled down","users":"unknown","region":"us-east-1"},"review":{"written_by":"session","reviewed_at":null,"reviewed_by":null},"lessons":{"en":"Hightouch's takeaways are that a migration can end in the desired state, the starting state or somewhere in between even with services one trusts, so traffic redirection and brief dependency outages should be planned for. The brief writer advertisement could only be found because queries to each instance were monitored in Datadog and the RDS database logs were available. Limiting how much any single component can affect the system helps both uptime and maintenance, and a staging load test, even of the exact procedure, did not reproduce the conditions that triggered the Aurora race condition.","ko":"믿을 만한 서비스에서 하는 마이그레이션이라도 목표 상태, 처음 상태, 그 중간 어느 상태로든 끝날 수 있다. 그러므로 트래픽을 다른 곳으로 돌리고 의존 서비스의 짧은 중단을 견딜 준비를 해 둔다. writer 가 잠깐 바뀐 일은 인스턴스별 쿼리 모니터링과 데이터베이스 로그가 있어서 찾을 수 있었다. 큰 분산 시스템에서는 구성 요소 하나가 시스템에 미치는 영향을 격리해 두면 가동 시간과 유지 보수 모두에 도움이 된다. 스테이징의 부하 시험은 운영 환경을 그대로 재현하지 못했고, 이번 레이스 컨디션을 일으킨 조건도 재현하지 못했다."},"related":[],"sources":[{"url":"https://hightouch.com/blog/uncovering-a-race-condition-in-aurora-rds","kind":"blog","archived":"https://web.archive.org/web/20260714154715/https://hightouch.com/blog/uncovering-a-race-condition-in-aurora-rds"}],"summary":{"en":"Hightouch Events uses Amazon Aurora PostgreSQL in us-east-1 as the metadata store for its Postgres-based queues, alongside Kafka and Kubernetes clusters. Aurora separates compute from a shared storage layer replicated across availability zones, and a failover promotes a reader, gives it write access to the storage and points the cluster endpoint at it. After the AWS us-east-1 outage of 20 October 2025 left a large backlog, Hightouch decided to upsize the database on 23 October without a scheduled maintenance window. The plan was to add a temporary reader (instance #3), upsize the existing reader (instance #2) and give it the highest failover priority, fail over to it with an expected downtime under 15s, then upsize the old writer (instance #1) as a reader and remove instance #3. AWS documentation supported the plan, and it had been tested in staging during a load test.\n\nThe failovers at 16:39 and 16:43 EDT both reversed. From the database logs Hightouch formed the hypothesis that during the failover window Aurora briefly let both instances process writes, the distributed storage layer rejected the concurrent writes, and both instances crashed: a race between demoting the old writer and accepting writes on the new one. To test this, Hightouch scaled down all services that write to the database, triggered the failover again and watched for storage runtime crashes. Without concurrent writes the failover completed, which strongly reinforced the hypothesis, and the cluster was upsized.\n\nAWS reviewed the findings and confirmed the bug: a signaling problem inside the demotion of the old writer left the writer unchanged after the failover, and nothing in Hightouch's configuration or usage triggered it. AWS said a fix is on its roadmap and, until then, recommends using Aurora failover only as needed and making sure no writes run against the database during a failover.","ko":"Hightouch Events 는 이벤트 처리에 Kafka 를 쓰고, 배치 메타데이터를 담는 큐 저장소로 Amazon Aurora PostgreSQL 을 쓴다. 10월 20일 us-east-1 AWS 장애 뒤 밀린 이벤트를 처리하면서 작은 Postgres 인스턴스가 한계에 몰렸고, 팀은 여유를 늘리려고 데이터베이스 크기를 키우기로 했다. Aurora 는 크기를 키울 때 장애 조치가 빨리 끝나므로, 팀은 10월 23일 이 작업을 예약된 점검 시간 없이 진행했다. 계획은 임시 reader 인 instance #3 을 더하고, 기존 reader 인 instance #2 를 목표 크기로 키워 장애 조치 우선순위를 가장 높게 준 뒤, 장애 조치로 instance #2 를 writer 로 올리는 것이었다. 예상 중단 시간은 15초 미만이었고, 같은 날 스테이징의 부하 시험에서는 이 절차가 문제없이 끝났다.\n\n16:39 EDT 와 16:43 에 실행한 장애 조치는 두 번 모두 잠깐 승격됐다가 되돌아갔고, 원래 writer 인 instance #1 이 primary 로 남았다. AWS 에서는 클러스터가 정상으로 보였지만 백엔드 서비스는 read-only transaction 오류로 쓰기를 하지 못했고, 팀은 서비스를 재시작해 정상으로 돌렸다. 데이터베이스 로그에서는 두 인스턴스 모두 쓰기 쿼리를 처리하던 프로세스가 signal 9 로 죽고 storage runtime process crash 로 내려간 기록이 나왔다. 팀은 장애 조치 동안 Aurora 가 잠깐 두 인스턴스 모두에 쓰기를 허용했고, 분산 저장 계층이 동시 쓰기를 거부해 두 인스턴스가 모두 죽었다는 가설을 세웠다. 팀은 이것을 원래 writer 를 강등하고 새 writer 를 승격하는 단계와 새 writer 가 쓰기를 받는 단계 사이의 레이스 컨디션으로 보았다.\n\n가설을 확인하려고 데이터베이스에 쓰는 서비스를 모두 스케일 다운한 뒤 장애 조치를 다시 실행했고, 이번에는 성공했다. AWS 는 내부 검토 뒤 근본 원인이 원래 writer 를 강등하는 과정의 내부 신호 문제였고, 그 때문에 장애 조치 뒤에도 writer 가 바뀌지 않았다고 확인했다. AWS 는 Hightouch 의 구성이나 사용 방식에 이 버그를 일으킬 특별한 점은 없었다고 했다. 수정은 AWS 로드맵에 있다. 그 전까지 AWS 가 권고하는 완화책은 장애 조치를 필요할 때만 쓰고 장애 조치 동안 데이터베이스에 쓰기가 실행되지 않게 하는 것이다. Hightouch 는 us-east-1 클러스터의 크기를 키우는 작업을 마쳤다."},"trigger":{"en":"A planned upsize of the Aurora cluster's instances, done without a maintenance window, after the AWS us-east-1 outage of 20 October had left an event backlog that strained the small Postgres instance. The manual failover to the upsized reader at 16:39 EDT started the failure.","ko":"2025-10-20 us-east-1 AWS 장애 뒤 밀린 이벤트를 처리하며 작은 Postgres 인스턴스가 한계에 몰리자, Hightouch 는 10월 23일 예약된 점검 시간 없이 Aurora 인스턴스 크기를 키우는 작업을 했다. 크기를 키운 reader 를 writer 로 올리려고 16:39 EDT 에 장애 조치를 실행했다."},"severity":"unknown","symptoms":["failover-failed","writes-rejected"],"timeline":{"detected_after":null,"resolved_after":null},"date_kind":"incident","detection":{"en":"The AWS Console showed after a refresh that the original writer was still primary, and backend application logs showed write errors on a read-only transaction. Per-instance query monitoring in Datadog and the RDS database logs showed the brief writer advertisement.","ko":"장애 조치를 실행한 뒤 AWS 콘솔 페이지를 새로 고치자 원래 writer 인 instance #1 이 여전히 primary 로 보였고, 백엔드 서비스가 쓰기 쿼리를 실행하지 못했다. 팀은 Datadog 에서 인스턴스마다 쿼리를 지켜보고 있었고 RDS 의 데이터베이스 로그를 받아 볼 수 있었기 때문에, writer 가 잠깐 바뀌었다가 돌아간 것을 찾아낼 수 있었다."},"components":["database","failover-manager"],"root_cause":"software-bug","remediation":[{"text":{"en":"Restarted the backend services, which cleared the write errors after each failed failover","ko":"백엔드 서비스를 재시작해 쓰기 오류를 없애고 정상 동작을 되찾았다."}},{"text":{"en":"Scaled down every service that writes to the database","ko":"데이터베이스에 쓰는 서비스를 모두 스케일 다운했다."}},{"text":{"en":"Triggered the failover again with no concurrent writes, which completed, and upsized the cluster in us-east-1","ko":"동시 쓰기가 없는 상태에서 장애 조치를 다시 실행해 성공했고, us-east-1 클러스터의 크기를 키웠다."}}],"action_items":[{"text":{"en":"Pause writers before any intentional failover, as written into the internal playbooks","ko":"내부 플레이북에 의도한 장애 조치 전에 쓰기 서비스를 먼저 멈추는 단계를 넣었다."}},{"text":{"en":"Monitor for unexpected flips of the instance that advertises the writer role","ko":"writer 로 표시되는 인스턴스가 예상치 않게 바뀌는 것을 잡는 모니터링을 더했다."}}],"contributing":["staging-not-representative"],"recurrence_of":null,"symptoms_text":{"en":"At 16:39 EDT on 23 October 2025, Hightouch triggered a failover of its Aurora PostgreSQL cluster in us-east-1 to a newly upsized reader. The AWS Console showed the usual parameter adjustments and instance restarts, but after the page refreshed the original writer was still the primary, and backend services could not run write queries although AWS showed the cluster as healthy.\n\nBackend application logs in some pods showed \"DatabaseError: cannot execute UPDATE in a read-only transaction\". Database metrics showed a spike in connection count, network traffic and commit throughput on the promoted reader (instance #2) during the failover, while read query traffic from the app did not change, so the extra traffic came from backend services that are meant to reach the writer through the cluster endpoint.\n\nThe database logs of both the promoted reader and the original writer showed the same sequence after startup: \"LOG: server process (PID 799) was terminated by signal 9: Killed\", \"DETAIL: Failed process was running: <write query from backend application>\", \"LOG: terminating any other active server processes\", \"FATAL: Can't handle storage runtime process crash\" and \"LOG: database system is shut down\".\n\nA second attempt at 16:43 ended the same way, with a brief promotion followed by an immediate reversal. Nothing else had changed: no code updates, no unusual queries and no traffic spikes. The same procedure had passed in a staging environment under load earlier that day, a review of the procedure found no mistake and an online search found no one with the same problem. Restarting the backend services cleared the write errors, but the upgrade had not taken place.","ko":"인스턴스 크기를 키우려고 Aurora 클러스터에서 장애 조치를 실행했다. 새 writer 로 올라가던 instance #2 는 잠깐 승격됐다가 곧 되돌아갔고, 원래 writer 인 instance #1 이 primary 로 남았다. AWS 콘솔에서는 클러스터가 모두 정상으로 보였지만 백엔드 서비스는 쓰기 쿼리를 실행하지 못했다.\n\n일부 파드의 백엔드 애플리케이션 로그에 DatabaseError: cannot execute UPDATE in a read-only transaction 오류가 찍혔다. 서비스를 재시작하자 오류가 사라졌다.\n\n데이터베이스 로그를 받아 보니 승격되던 reader 와 원래 writer 양쪽에 같은 줄이 같은 순서로 찍혀 있었다. LOG: database system is ready to accept connections 가 찍힌 뒤 LOG: server process (PID 799) was terminated by signal 9: Killed 가 나왔고, 죽은 프로세스는 백엔드가 보낸 쓰기 쿼리를 실행하고 있었다. 이어서 FATAL: Can't handle storage runtime process crash 와 LOG: database system is shut down 이 찍혔다.\n\n장애 조치 동안 read replica 인 instance #2 의 연결 수, 네트워크 트래픽, 커밋 처리량이 치솟았다. 앱에서 오는 읽기 쿼리 트래픽은 그대로였으므로, 늘어난 트래픽은 writer 에 연결되어야 할 백엔드 서비스에서 온 것이었다.\n\n16:39 EDT 첫 시도에 이어 16:43 에 다시 장애 조치를 실행했지만 똑같이 잠깐 승격됐다가 바로 되돌아갔다. 5분 사이에 두 번 실패했고, 그동안 코드 변경, 특이한 쿼리, 트래픽 급증은 없었다.\n\n팀은 먼저 절차에 실수가 있었는지 확인하고 같은 문제를 겪은 사례를 인터넷에서 찾았지만 아무것도 나오지 않았다. 같은 날 앞서 스테이징 환경의 부하 시험에서는 같은 절차가 문제없이 끝났다. 백엔드는 writer 를 가리키는 클러스터 엔드포인트로 접속한다. 팀은 세 가지 가능성을 따져 보았다. 클러스터가 연결을 끊지 않아 파드가 writer 변경을 몰랐을 가능성, 클러스터 엔드포인트가 reader 를 잘못 가리켰을 가능성, writer 에 연결됐지만 쓰기가 실행 중에 거부됐을 가능성이다. 팀은 이 가운데 뒤의 두 가지를 강하게 의심했다."},"updated_at":"2026-11-14T12:33:56.401350Z","url":"https://benchlet.com/incidentlib/app/cases/IL-0310","org_slug":"hightouch"}