22P by xguru 4달전 | favorite | 댓글 3개
  • Mattermost는 Elasticsearch를 사용하여 데이터베이스 부하를 줄이고 훨씬 더 빠른 검색 결과를 제공함
    • Elasticsearch가 제대로 동작하려면 검색 대상이 되는 모든 데이터를 색인해야 함
    • 이미 색인된 데이터에 대해서는 이후 새 글과 파일의 색인 작업이 꽤 빠름
    • 그러나, 아주 큰 데이터베이스(1억 개 포스트)를 처음부터 완전히 색인하는 것은 매우 느림(18시간동안 절반도 못하고 점점 느려짐)
  • 데이터베이스 호출 별 소요 시간 그래프를 통해, PostStore.GetPostsBatchForIndexing 메서드의 SQL 쿼리가 문제인 것으로 파악됨
    • 해당 쿼리는 기본적으로 creation timestamp 기준으로 포스트를 정렬하고, 주어진 timestamp보다 새로운 N개의 포스트를 반환함
    • 이 쿼리를 반복 실행하여 모든 포스트가 색인될 때까지 indexing job이 수행됨
  • EXPLAIN (ANALYZE, BUFFERS)를 사용해 쿼리 실행 계획을 분석함:
    • Posts 테이블에 인덱스 스캔 수행 시 Filter 조건 적용을 위해 4천만 블록을 처리하고 있음 (309GB)
    • Channels 테이블과의 JOIN은 문제가 아님
    • WHERE 조건의 OR 절 중 Posts.CreateAt > ?1 부분만 적용하면 훨씬 빨라짐 (30ms)
    • 거기서 Posts.CreateAt = ?1 AND Posts.Id > ?2 조건을 적용하면 극도록 빨라짐 (0.047ms)
  • 원인 파악:
    • 원본 쿼리는 Posts의 모든 행을 훑으면서 Filter로 걸러내는 반면, 수정된 쿼리는 인덱스만 확인하고 필요한 행만 추출함
    • 시간이 지남에 따라 쿼리가 점점 느려진 이유는 점점 더 많은 행을 걸러내야 했기 때문임
  • 해결책:
    • PostgreSQL의 row constructor comparison 기능을 활용하여 (Posts.CreateAt, Posts.Id) > (?1, ?2) 조건으로 변경
    • 이렇게 변경한 쿼리는 34밀리초로 실행 시간이 대폭 줄어듦
    • 그런데 MySQL에서는 변경된 쿼리가 오히려 더 느리게 동작함. MySQL에서는 원본 쿼리가 더 빨랐기에 DB 별로 다른 쿼리 사용하도록 코드 분기 처리함
  • 배운 점들:
    • EXPLAINBUFFERS 옵션을 항상 사용할 것
    • Filter 대신 Index Cond를 활용하도록 할 것
    • PostgreSQL과 MySQL은 거의 항상 다르게 동작함을 전제할 것
  • 결론
    • 최적화 작업을 통해 쿼리 실행 시간을 1000배 이상 줄이는 성과를 얻음
    • 이 최적화는 Mattermost v9.7.0 및 v9.5 ESR 버전에 반영됨
    • 최적화 작업을 통해 많은 것을 배울 수 있었음

맨 마지막 글에도 있지만 본 글의 제목이 살짝 낚시성느낌이나...좀더 실질적으로 바꿔보자면

'실수를 통해 배운 postgresql 활용 사례'

일까요..

음... 개인적으로 이런 수준의 글을 특정 회사/제품을 걸고 쓴다면, 오히려 그 제품에 대한 신뢰도가 많이 낮아질 것 같습니다.
정리는 일목요연하지만, 그 안에 담긴 기술적인 가치가 조금 부족해보여 아쉽네요.

저도 이 글을 보고나서 오히려 신뢰도가 낮아졌습니다. 돈받고 팔고 있는 제품이 대용량 처리 테스트도 없이 기능을 출시한 거니까요. 저 정도의 간단한 인덱스는 기능 개발 단계에서 설정해야 하는거 아닌가 싶습니다. 소프트웨어 개발 절차가 많이 생략된거 같아 보입니다