여러 채널의 일괄 처리를 하나씩 돌리던 것을 동시에 돌리려 했다. 동시에 돌리면 상태가 깨지지 않는지부터 확인했다.
Table of contents
Open Table of contents
동시 실행의 정합성 네 겹
코드를 훑으니 안전장치가 네 겹이었다. 항목별 진행 기록이 남아 중간에 죽어도 그 지점부터 이어 갈 수 있었다.
변환 요청은 같은 대상에 여러 번 와도 한 번만 처리되고 갱신은 읽을 때의 갱신 시각을 조건에 넣어 그 사이에 누가 바꿨으면 아무 행도 안 바꾼다. 항목 하나의 예외가 그 실행 전체를 죽이지 않으므로 동시 실행의 결과가 하나가 이기고 나머지는 건너뛰는 형태로 수렴했다.
세어 보니 제약이 아니던 커넥션
다음 걱정은 채널마다 풀을 만들면 커넥션이 모자라지 않느냐였다. 상한과 역대 최대 사용량을 조회해 보니 최대치가 상한의 절반에도 못 미쳤다.
채널 수만큼 풀을 얹어도 여유가 컸으므로 커넥션은 제약이 아니었다. 세어 보기 전에는 이것이 가장 큰 걱정이었는데 조회 두 번으로 후보에서 빠졌다.
먼저 부러지는 자리
걱정한 둘이 모두 아니라고 나오면 걱정하지 않은 것을 찾아야 한다. 이 작업이 읽는 테이블에 본문을 통째로 담는 대용량 컬럼이 있고 테이블이 수십 기가였다.
채널을 동시에 돌리면 그만큼 큰 읽기가 배로 늘고 버퍼 풀에 이 테이블의 페이지가 밀려 들어온다. 이 작업이 느려지는 것이 아니라 다른 서비스 쿼리의 캐시가 밀려나 그쪽이 느려지는 것이 진짜 문제였다.
시간대와 재개 가능성
그래서 동시도의 상한을 커넥션이 아니라 읽기 여유로 잡고 시간대를 봤다. 심야에는 실행 중인 스레드가 거의 없어 여유가 크고 주간에는 경합이 생긴다.
여유가 없어지면 일부 채널을 일시 중지하기로 했는데 진행 기록이 있으므로 멈춰도 손실이 없다. 끝까지 돌려야 한다가 아니라 언제든 멈췄다 이어도 된다가 되면 정책이 단순해진다.
과거 실행이라는 근거
이 판단의 실증 근거는 과거 실행이었다. 전에 채널 열여섯 개를 열다섯 동시로 돌린 적이 있었고 그때 크래시가 열 건 났다.
기록을 보니 열 건 전부 재개로 복구됐고 데이터 손상은 없었다. 공격적인 동시성에서 실제로 늘어난 것은 크래시 빈도뿐이었고 안 깨진다는 것을 이론이 아니라 과거 결과로 확인한 것이 가장 강한 근거였다.
정리
- 동시 실행의 정합성은 진행 기록과 멱등과 낙관적 잠금과 예외 격리로 수렴한다
- 낙관적 잠금은 읽을 때의 갱신 시각을 조건에 넣는다
- 커넥션은 세어 보면 대개 제약이 아니다
- 걱정한 것이 다 아니면 걱정하지 않은 것을 찾는다
- 진짜 병목은 대용량 읽기가 배로 늘어나는 자리였다
- 이 작업이 아니라 다른 서비스 쿼리가 느려지는 것이 문제다
- 동시도는 읽기 여유가 있는 시간대로 나눠 정한다
- 재개가 가능하면 멈추는 것 자체가 정책이 된다