들어가며
데이터베이스의 인덱스를 잘 활용하면 놀라운 조회 성능 개선을 이룰 수 있습니다. 하지만 운영되고 있는 서비스의 데이터베이스를 다룰 때는 각별히 조심해서 써야 합니다.
지금부터 제가 들려드릴 이야기는 만 1년이 안 된 신입 개발자 시절 저지른 실수에 대한 회고입니다. 부디 이 이야기가 여러분에게도 공감받고 작게나마 도움이 되었으면 좋겠습니다.
Index란?
데이터베이스 테이블에 지정한 Column을 기준으로 메모리 영역에 일종의 목차를 생성하는 것.
Index를 사용하는 이유
데이터 조회(SELECT)의 성능을 향상하기 위해.
Index의 비용 비대칭성
인덱스를 유지 관리하는 것은 비용이 듭니다. 왜냐하면 데이터가 테이블에 삽입, 업데이트 또는 삭제되면 해당 인덱스도 따라 업데이트되어야 하므로 추가로 시간, 메모리를 사용하기 때문입니다.
다시 말해 INSERT, UPDATE, DELETE(Command)의 성능을 희생하고 대신 SELECT(Query)의 성능을 향상하기 위해서 인덱스를 사용하게 됩니다. 이를 Command와 Query 비용의 비대칭성아라고 합니다. 따라서 인덱스 생성 결정은 향상된 조회 성능과 인덱스 유지 관리 비용 사이의 균형을 고려해서 결정해야 합니다.
지금부터는 GuardiOne Turbo 제품의 인덱스 생성 관련해서 제가 일으킨 장애에 대해 알아보겠습니다.
인덱스 설정 장애 리포트
인덱스 설정 업무를 하게 된 배경은 다음과 같습니다.
- 과거의 성공 경험 - 인덱스가 없던 데이터 ETL 관련 테이블에 인덱스 설정 후 코드 연산 속도 100배 향상 (300초 → 3초)
- 데이터 화면이 늦게 뜬다는 고객의 민원
1, 2의 이유로 API가 조회하는 Feature 테이블에 이미 단일 인덱스가 설정되어 있지만, 추가적인 속도 개선을 위해 복합 인덱스 설정이 필요하다는 결정이 있었습니다.
그리고 복합 인덱스 설정을 한 다음 날 아침, 고객으로부터 화면에서 보이는 데이터가 업데이트되지 않았다는 연락을 받았습니다. DB 도구로 확인해 보니 DB 테이블이 조회되지 않았습니다. 장애 원인은 인덱스 설정으로 인한 DB 트랜잭션 과부하였습니다.
인덱스 설정으로 인해 멀쩡하던 데이터가 들어오지 않는 건 큰일입니다. 당황한 저는 문제를 일으킨 장본인인 내가 책임져야 한다는 생각에 점심도 거르고 용량이 작은 테이블부터 인덱스 설정 롤백을 시도했습니다. 하지만 설상가상으로 인덱스 삭제 또한 오래 걸려서 DB 과부하로 모든 API 요청이 거절되어 화면이 제대로 나오지 않았습니다.
주변 동료들의 도움을 받기 위해 위 상황을 알리고 같은 팀 동료의 도움으로 위 상황을 해결할 수 있었습니다. PostgreSQL의 내장 함수를 통해 DB lock 걸린 프로세스를 강제 종료하고 인덱스 설정을 롤백하여 문제를 해결할 수 있었습니다.
SELECT *
FROM pg_catalog.pg_stat_activity;
SELECT t.relname, l.locktype, page, virtualtransaction, pid, mode, granted
FROM pg_catalog.pg_locks l, pg_catalog.pg_stat_all_tables t
WHERE l.relation = t.relid
ORDER BY relation ASC;
SELECT pg_cancel_backend(8100);
SELECT pg_terminate_backend(20500);
pg_cancel_backend()
함수를 통해
특정 백엔드 프로세스의 실행을 취소할 수 있기 때문에,
백엔드 프로세스의 PID를 pg_cancel_backend()
에 대한 인수로 제공해서
해당 프로세스를 종료했습니다.
회고
원프레딕트에선 스프린트가 종료될 때마다 회고를 통해 아쉬웠던 점은 개선하고, 잘한 점은 서로 칭찬하고 유지하려 모두 노력하고 있습니다.
이번 사고에서도 예방책에 관해 스프린트 회고 때 많은 의견이 있었습니다.
- 운영 중인 서비스와 관련한 작업을 할 때는 경각심 갖기
- 운영 데이터베이스 조작 전 PO나 팀원에게 공유하기
- 운영 데이터베이스를 조작하는 권한에 대한 내규나 프로세스 만들기
모두 다 좋은 의견입니다.
여기에 더해서 저는 비판적으로 일하기
의 중요성을 배웠습니다.
앞서 말씀드렸듯이 테이블마다 필요한 단일 인덱스는 이미 설정되어 있었습니다.
복합 인덱스 설정이 크게 조회 성능이 되지 않을 수 있는 점과
Command의 비용이 더 들 수 있다는 점을 간과했다는 점이 아쉬웠습니다.
업무 지시를 받은 상황이라도 그것이 정말로 도움이 되는지,
어떤 가치를 줄 수 있는지를 주체적으로 생각해야 했다는 점을 배웠습니다.
이 주제로 백엔드 작당모의주1에서 발표했을 때 저는 조금 긴장했습니다. 회사에서 자신의 성과가 아닌 실수를 드러내는 건 용기가 필요한 일이니까요. 하지만 발표를 마치고 동료들이 저마다 자신이 겪었던 실수를 공유하고 누구도 비난하지 않는 분위기를 보며 원프레딕트의 개발 조직은 좋은 문화를 가지고 있다는 확신을 얻게 되었습니다.
실수하지 않는 사람은 없습니다. 여러 사람이 모인 회사 안에서 아무런 사건 사고가 없다면 그건 오히려 사람들이 비난이 두려워 자신의 실수를 감추고 있다는 방증일 것입니다. 원프레딕트에서는 실수가 있어도 그것을 숨기지 않으며 그를 통해 배우고 더 영리한 조직이 되고자 노력합니다. 원프레딕트와 함께 성장하고 싶은 분은 언제든 문을 두드려 주세요!
- 원프레딕트의 모든 백엔드 엔지니어는 매주 목요일에 기술 주제를 나누는데, 이 모임의 이름이 ‘백엔드 작당모의’입니다.↩