Skip to content
isdnetworks
Go back

새 글을 계속 보여 주는 게시판

경기 중에 글이 몰리는 게시판이라 새 글이 계속 올라왔다. 사용자가 새로고침을 계속 누르고 있었다. 그래서 jQueryAJAX 로 일정 시간마다 새 글이 있는지 묻게 했다.

Table of contents

Open Table of contents

요청 수가 크게 늘었다

처음에는 이렇게 뒀다.

setInterval(function() {
    $.get('/board/new?after=' + lastId, ...);
}, 5000);

$.get 이 5초마다 lastId 뒤의 글이 있는지 묻는다. 사람이 많으니 요청이 크게 늘었다.

동시 접속 1,000명 × 5초마다 = 초당 200

그런데 응답을 보니 대부분이 새 글 없음이었다. 빈 응답을 받으려고 계속 묻고 있는 셈이었다.

줄일 자리가 두 군데 있었다. 응답을 가볍게 하는 것과 묻는 횟수 자체를 줄이는 것이다. 한쪽만 손대면 절반만 나아진다.

응답을 개수만 주게 바꿨다

먼저 목록 대신 개수만 돌려주게 했다.

$count = $model->count_after($lastId);
echo json_encode(['n' => $count]);

count_after() 의 결과를 json_encode 로 싸서 n 하나만 보낸다. 그 값이 0이 아닐 때만 목록을 가져간다. 세는 쿼리도 봤다.

SELECT COUNT(*) FROM posts WHERE id > ?

posts 의 기본 키 조건이라 MySQL 이 금방 답해서 따로 손댈 것이 없었다. 응답이 가벼워지니 같은 요청 수에서도 부하가 줄었다.

다만 요청 수 자체는 그대로였다. 요청 수를 줄이는 것은 응답을 손보는 것과 다른 방법이 필요했다.

한가하면 덜 묻게 했다

새 글이 없는 응답이 이어지면 간격을 늘리게 했다. setInterval 로는 간격을 못 바꿔서 setTimeout 을 다시 거는 형태로 고쳤다.

새 글 있음    →  5초
없음 여러 번  →  10초, 20초

새 글이 오면 다시 짧아진다. 경기 중에는 자주 묻고 한가할 때는 드물게 묻는 형태가 됐다.

여기에 화면이 보이지 않으면 아예 묻지 않게 했다.

document.addEventListener('visibilitychange', ...);

visibilitychange 가 그때 막 들어오던 참이라 이름이 브라우저마다 달랐다. document.hiddenwebkitHiddenmsHidden 셋을 다 보고 하나라도 참이면 멈추게 했다.

세어 보니 안 보는 탭이 꽤 많았다. 여러 탭을 열어 두는 사용자가 많았고 안 보는 화면에서 오는 요청은 아무에게도 쓸모가 없었다. 이 조건 하나가 가장 크게 줄였다.

개수 조회는 1초만 캐시했다. 같은 1초 안의 요청은 한 번만 계산한다. 정확도가 1초 늦어지는데 게시판이라 그 차이가 문제가 안 됐다.

결과 — 쓰기 쪽에서 얻은 두 가지

읽기는 이렇게 줄였는데 쓰기는 사용자가 하는 것이라 줄일 수 없었다. 대신 같은 사람이 너무 빠르게 연속으로 쓰는 것을 막았다.

if (마지막_작성 + 3 > now()) { 거부; }

3초를 정하는 데는 실제 작성 간격 분포를 봤다. 정상 사용자는 대부분 그보다 길었다. 감으로 정하면 너무 짧거나 길어지는데 분포를 보면 정상 사용자가 안 걸리는 선이 나온다.

이 제한이 부하만 줄인 것이 아니었다. 쓰기 요청이 줄고 도배도 함께 줄어서 두 목적에 맞았다.

서버가 먼저 알려 주는 방식도 찾아봤는데 그것은 연결을 계속 잡고 있어야 했다. 동시 접속 1,000명이면 연결도 1,000개다. 이번에는 묻는 방식으로 버티고 언제 바꿀지만 적어 뒀다. 동시 접속이 크게 늘거나 1초 지연이 문제가 되면 다시 본다.

정리


Share this post on:

Previous Post
용도마다 무엇을 쓸지 정했다
Next Post
큐를 넣을 자리인지 다시 봤다