각 설문조사마다 좋아요를 할 수 있고 설문 조사 목록을 보여줄 때 답변 수와 좋아요수를 표시한다
이를 위해 survey, answer, liked 테이블이 존재한다.
목록을 표시할 때 설문에 답한 회원 수와 좋아요를 한 회원 수를 표시한다는 요건을 위한 쿼리
SELECT s.id, s.subject, (SELECT COUNT(*) FROM answer a WHERE a.surveyId = s.id) as answerCnt, (SELECT COUNT(*) FROM liked a WHERE l.surveyId = s.id) as likeCntFROM survey sORDER BY id DESCLIMIT 30;
논리적으로 정확하지만 성능에 이슈가 존재할 수 있다
30개의 설문이 있을 때 설문마다 평균 답변자 수가 10만명이고 좋아요를 한 회원 수가 1만이라 가정해보자. 위 쿼리 실행시 다음과 같이 실행된다.
목록 조회
답변자 수 세는 쿼리 30번: 각 쿼리는 10만개 센다
좋아요 수 세는 쿼리 30번: 각 쿼리 1만개 센다
합치면 논리적으로 61번 쿼리가 실행된다
쿼리 시간: 0.01 + 0.1 * 30 + 0.05 * 30 = 4.51
4.51 초는 온라인 서비스에서 매우 긴 시간
게다가 트래픽이 몰리면 더욱 조회 시간이 느려질것이다.
이를 개선하려면 집계 쿼리를 안쓰고 미리 계산해서 별도 컬럼에 저장해 두면됨
이 예에서는 servey 테이블에 답변자 수와 좋아요 수를 저장할 컬럼 추가하고, 이 컬럼에 값을 계산해 넣는 방식으로 구현
다음과 같은 쿼리를 쓰는거지
-- answer table에 답변 추가INSERT INTO answer VALUES (...)-- servey table의 answerCnt 값을 1 증가시킨다.UPDATE survey SET answerCnt = answerCnt + 1WHERE surveyId = 아이디;-- 좋아요도 비슷. 취소했다면 likedCnt - 1 하면됨DELETE FROM liked WHERE answerId = 설문ID AND memberId = 회원ID;UPDATE servey SET likedCnt = likedCnt - 1 WHERE surveyId = 설문ID
페이지 기준 목록 조회 대신 ID 기준 목록 조회 방식 사용하기
게시글 데이터가 10만개 있다 가정
화면에 10개씩 보여주면 전체 페이지수는 1만개가 된다
첫 페이지 읽기 위한 쿼리는 다음과 같다
SELECT id, subject, writer, regdtFROM articleORDER BY id DESCLIMIT 10 OFFSET 0;
다음은 마지막 1만 번째 페이지를 읽어오는 쿼리
SELECT id, subject, writer, regdtFROM articleORDER BY id DESCLIMIT 10 OFFSET 99990;
이 쿼리를 실행시 99,991번째 id부터 바로 조회하면 좋겠지만...
DB는 어떤 id가 99.991 번째인지 모른다
그래서 역순으로 id를 99,990개 세고 나서 10개 데이터를 조회
데이터를 세는 시간만큼 실행시간이 증가하는 것
인덱스에 포함되어 있지 않은 칼럼은 더 느리다
다음 쿼리는 삭제되지 않은 게시글 목록을 조회한다.
SELECT id, subject, writer, regdtFROM articleWHERE deleted = falseORDER BY id DESCLIMIT 10 OFFSET 10000;
deleted 칼럼이 false인지 비교할 목적으로 데이터를 읽어오는 시간을 줄이기 위해, deleted 칼럼을 인덱스로 사용할 수 도 있다
하지만 여전히 지정한 오프셋만큼 데이터를 세는시간이 남는다.
지정한 오프셋으로 이동하기 위해 데이터를 세는 시간을 줄이는 방법은 특정 ID를 기준으로 조회하는 것!
예를 들어 마지막으로 다음 쿼리로 조회한 데이터의 id가 9985라 하면
SELECT * FROM articleWHERE deleted = false;ORDER BY id DESCLIMIT 10;
다음 10개를 읽을때는 앞서 읽어온 마지막 id를 사용해서 조회하면된다
SELECT * FROM articleWHERE id < 9985 AND deleted = false;ORDER BY id DESCLIMIT 10;
id는 인덱스이므로 바로 9984 찾는다
오프셋을 썻을때의 데이터 세는 시간이 생략되는 것!
프론트 개발자가 다음에 읽어올 데이터 존재하는지 알려주는 속성을 응답결과에 포함시켜달라하면
1개만더 읽어 판단하면 된다
예를 들어 10개를 주면될때는 11개로 읽고 11개면 다음에 읽을게 존재하는것
이건 비즈니스로직으로 판단하면될 듯
SELECT * FROM articleWHERE id < 1001 AND deleted = falseORDER BY id DESCLIMIT 11;
조회 범위를 시간 기준으로 제한하기
조회 성능 개선 방법중 하나는 조회 범위를 시간 기준으로 제한하는 것
최신 데이터 위주로 조회하게 기능을 변경하면 DB성능 또한 향상
DB는 성능향상을 위해 메모리 캐시를 쓴다
조회가 발생하면 이를 메모리에 캐시해 다음 동일 요청시 더 빨리 응답
일반적으로 최신 데이터가 더 많이 조회되기에 캐시에 최신 데이터가 적재될 확률이 높다
전체 개수 세지 않기
목록을 표시하는 기능은 전체 개수를 함께 표시하는 경우가 많다
조건에 해당하는 데이터 개수를 구하기 위해서는 count 함수를 써야된다
예를 들어, 이름에 특정 단어가 포함된 회원 목록 조회하려면 다음 두 쿼리 써야된다.
SELECT id, ...FROM memberWHERE name LIKE '지은%'ORDER BY id DESCLIMIT 20;SELECT COUNT(*)FROM memberWHERE name LIKE '지은%';
데이터 적을땐 노상관이지만 많아지면 조건에 해당하는 모든 데이터 탐색해야된서 느려진다
커버링 인덱스를 써도 전체 인덱스를 스캔해야되며, 커버링 인덱스가 아닌 경우에는 실제 데이터를 전부 읽어야됨
해결방법은? 바로 소통!
오래된 데이터 삭제 및 분리 보관하기
과거 데이터를 삭제할 수 있는 예로 로그인 시도 내역을 들 수 있다
새로운 지역에서 로그인 시도 발생 탐지같은걸 개발하려면 로그인 시도 내역을 일정 기간 보관해야함
로그인 시도 내역같은건 장기간 보관이 필요없다
이상 징후 탐지를 위한거라 최근 몇달치만 있어도 충분
별도 캐시 서버 구성하기
캐시 도입시 코드를 수정해야하지만, 코드 수정에 드는 비용대비 캐시로 증가시킬 수 있는 처리량이 크다면...