IL-0516 Matrix.org Foundation · 2025-06-27database
matrix.org 홈서버의 PostgreSQL 인덱스가 손상되어 정리 작업이 쓰고 있는 방 상태를 지우고 방이 멈췄다
Element 의 백엔드·SRE 팀은 Matrix.org Foundation 을 대신해 matrix.org 홈서버를 운영한다. 이 홈서버는 큰 PostgreSQL 인스턴스를 쓰고, Synapse 는 방 상태의 스냅샷인 state group 을 event_to_state_groups, state_groups_state, state_group_edges 테이블에 저장한다. 대부분의 state group 은 이전 state group 과의 차이만 저장하고 100개쯤마다 전체 스냅샷을 둔다. 그런데 state_groups_state 의 인덱스 state_groups_state_type_idx 일부가 손상돼 있었다. 인덱스 항목이 힙의 빈 자리를 가리켰고, Postgres 는 빈 자리를 가리키는 인덱스 항목을 무시하므로 처음에는 아무 영향이 없었다. 손상은 2021년 1월 이후 2025년 7월 전에 일어났고, 적어도 1년 전, 아마 훨씬 오래전이었다.
Postgres 가 그 빈 자리를 새 state group 에 쓰자 옛 state group 의 인덱스 항목이 새 state group 의 데이터를 가리키게 됐다. 쓰이지 않는 오래된 데이터를 지우는 백그라운드 정리 작업이 최근 손상된 영역을 다루기 시작했고, 옛 state group 을 지우면서 관계없는 방의 살아 있는 state group 데이터까지 지웠다. 로그에서 "No create event" 는 2025-06-27 부터 나타났고, 그 방들에서는 메시지 보내기와 들어가기가 "No create event in auth events" 로 실패했다. 사용자 보고로 문제를 안 팀은 정리 작업을 껐다. 이어 6월 26일 백업을 Amazon EC2 의 새 Postgres 인스턴스에 복원하기 시작했고, 사용 중인 state group 의 상태 삭제를 거부하는 제약을 걸었다. 7월 3일 정리 작업을 다시 켜자 제약에 걸렸고, 직접 쿼리로 인덱스 손상을 확인했다. warm standby 서버와 복원한 백업에도 같은 손상이 있었고, 표본 검사로는 147M 부터 541M 사이의 state group 수백만 개가 영향을 받았다.
팀은 4 TB 가 넘는 인덱스를 밤새 다시 만들었다. 이어 정리 작업이 다룬 범위의 state group 을 복원한 백업에서 조회해 피해 후보를 뽑고, 운영 데이터베이스와 대조해 지워졌지만 아직 쓰이는 state group 을 찾아 다시 넣었다. 팀은 7월 5일에 스크립트를 돌리기 시작했고, 거의 일주일 뒤인 7월 11일에 대부분의 방이 복구됐다고 알렸다. 인덱스가 왜 손상됐는지는 밝히지 못했다. Synapse 나 PgCat 은 원인일 수 없고, 2021년 1월에 쓰던 Postgres 10.12 이후의 릴리스 노트에서도 맞는 버그를 찾지 못했다. 팀은 하드웨어 문제를 가장 그럴듯한 원인으로 보고, 2021년 10월 주 데이터베이스 서버의 디스크 고장을 가능성 하나로 들었지만 이 설명에 완전히 만족하지는 않았다.
- 심각도
- –
- 영향 범위
- Rooms on the matrix.org homeserver whose state groups lost data: sending messages and joining failed
- 영향 사용자
- –
- 지역
- –
- 감지까지
- –
- 복구까지
- 14d
증상
matrix.org 홈서버 사용자들이 방이 그냥 멈춘다고 보고했다. 새 메시지를 보내거나 새 멤버가 방에 들어가는 일이 이유 없이 실패했고, 오류 문구가 보일 때는 대개 "No create event in auth events" 였다.
문제가 보고된 방들을 살펴보니 state group 이 손상돼 있었다. state group 은 Synapse 가 방 상태가 바뀌지 않은 이벤트들을 한데 묶어 그 시점의 방 상태를 저장하는 단위다. 어떤 state group 은 상태가 완전히 비어 있었는데, 방의 create 이벤트는 바뀌지 않고 늘 방 상태에 들어 있어야 하므로 이는 분명한 이상 신호였다. Synapse 는 새 이벤트를 받을 때 방 상태에 create 이벤트가 있는지 확인하고, 없으면 "No create event in auth events" 로 거부한다.
Synapse 프로세스 하나의 로그에서 "No create event" 를 세어 보니 2025-06-24 부터 06-26 까지는 0건이었고, 06-27 에 48건, 06-28 에 1100건, 06-29 에 3610건, 06-30 에 6902건으로 늘었다. 2025-06-29 쯤에는 멀쩡했을 state group 이 나중에 손상된 것도 찾았다. 새 state group 을 잘못 만드는 것이 아니라 어딘가에서 기존 state group 을 망가뜨리고 있었다.
팀은 state group 을 바꿀 수 있는 곳을 하나씩 의심했다. state compressor 는 matrix.org 에서 돌지 않아 제외했다. 6월 27일 앞뒤의 변경을 다시 찾았지만 아무것도 나오지 않았고, Synapse 를 옛 버전으로 롤백하는 것도 생각했지만 무엇이 바뀌었는지 몰라 어디까지 되돌릴지 정할 수 없었다.
사용 중인 state group 의 상태 삭제를 막는 제약을 건 뒤 정리 작업을 다시 켜자 곧바로 Postgres 로그에 2025-07-03 12:30:38.250 UTC [matrix background_worker1] ERROR: Deleting state_groups_state row when it still exists in state_groups_edges: prev_state_group = 963361509 가 찍혔다. Synapse 로그에는 전혀 다른 state group 을 지운다고 나와 있었다. 팀은 Synapse, Python Postgres 드라이버, PgCat 을 차례로 의심하며 tcpdump 까지 썼고, 잡힌 DELETE 쿼리 가운데 state group 963361509 에 닿는 것은 없었다.
state group 483128098 하나를 SELECT state_group, room_id FROM state_groups_state WHERE state_group = 483128098 로 읽자 다른 방의 state group 963361875 행이 섞여 3 rows 가 나왔다. 인덱스만으로 답할 수 있는 쿼리는 그럴듯한 결과를 냈지만, 힙을 읽어야 하는 쿼리는 엉뚱한 행을 냈다. pg_amcheck 는 이 인덱스에서 아무 문제도 찾지 못했다.
원인
- 촉발
- matrix.org 의 PostgreSQL 테이블 state_groups_state 에서 오래전에 손상된 인덱스 영역에 Postgres 가 새 행을 채우기 시작했고, 쓰이지 않는 오래된 데이터를 지우는 백그라운드 정리 작업이 최근 그 영역을 다루기 시작했다.
감지
사용자의 버그 보고와 공개 Matrix 방, 다른 소셜 미디어의 비슷한 보고로 문제를 알았다. 보고가 더 쌓이자 팀이 조사를 시작했다.
조치
- 예방 조치로 쓰이지 않는 state group 을 지우는 정리 작업을 잠시 껐다.
- 데이터를 백업에서 되살려야 할 것으로 보고 6월 26일 데이터베이스 백업을 Amazon EC2 의 새 Postgres 인스턴스에 복원하기 시작했다.
- 아직 쓰이는 state group 의 상태를 지우려는 SQL 쿼리를 거부하는 Postgres 제약을 걸어 손상이 더 번지지 않게 했다.
- 4 TB 가 넘는 손상된 인덱스를 밤새 다시 만들었다.
- 정리 작업이 다룬 범위의 state group 을 복원한 백업에서 조회해 피해 후보를 뽑고, 운영 데이터베이스와 대조해 지워졌지만 아직 쓰이는 state group 을 찾아 운영 데이터베이스에 하나씩 다시 넣었다.
후속 조치
–
교훈
이 손상은 몇 달이나 몇 년 동안 아무도 모르게 있을 수 있었다. 인덱스가 힙의 빈 자리를 가리키는 동안에는 드러나지 않았고, Postgres 가 그 자리에 새 행을 채우기 시작한 뒤에야 문제가 보였다. pg_amcheck 는 이런 손상을 찾지 못했지만, 테이블의 모든 행이 인덱스에 있다는 것은 확인해 줘서 인덱스 행이 빠진 것이 아니라 남는 행이 있다는 것을 알 수 있었다.
팀은 이런 장애가 큰 규모의 서비스를 운영하면 가끔 일어나고, 미리 계획할 수 없으며, 기능 개발이나 버그 수정에 쓸 사람의 시간과 노력을 크게 잡아먹는다고 정리했다.
출처
관련 사례
IL-0311주 PostgreSQL 데이터베이스가 트랜잭션 ID wraparound 보호로 읽기 전용이 되어 웹사이트와 백엔드 서비스가 멈췄다BattleMetrics · 2022같은 원인 · 공통 데이터베이스
내 클러스터에도 있는지 확인하기
AI 도구와 스크립트용
- 이 사례의 JSON: https://incidentlib-api.benchlet.com/cases/IL-0516
- 가장 비슷한 사례들: https://incidentlib-api.benchlet.com/cases/IL-0516/similar
- MCP 서버(Streamable HTTP, 키 없음):
https://incidentlib-mcp.benchlet.com/mcp
API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.