서버를 한 대 더 붙이고 나니 로그인이 자꾸 풀렸다.
Table of contents
Open Table of contents
세션이 각 서버의 파일에 있었다
앞단에서 요청을 두 대에 번갈아 보내는데 세션은 각 서버의 session.save_path 아래 파일로 저장되고 있었다. 한 대에서 로그인하면 그 서버에만 파일이 생기고 다음 요청이 다른 대로 가면 로그인이 안 된 상태가 된다.
한 대일 때는 문제가 없던 구조였다. 파일이 그 서버 안에만 있다는 사실이 대수롭지 않았기 때문이다. 대가 늘면서 그 전제가 깨졌다.
붙여 두는 방법과 공유하는 방법
앞단에서 같은 사람을 같은 서버로 계속 보내는 설정이 있었다. 그렇게 하면 세션이 맞는다. 그런데 한 서버가 죽으면 거기 붙어 있던 사람이 전부 로그아웃된다. 한 대가 죽어도 서비스가 이어지게 하려고 대를 늘렸는데 그 이유가 살지 않는다.
세션을 양쪽이 함께 보는 자리에 두기로 하고 sessions 표에 넣었다.
CREATE TABLE sessions (
id VARCHAR(40) PRIMARY KEY,
ip_address VARCHAR(45),
timestamp INT,
data TEXT
);
CodeIgniter 에 이미 그 기능이 있어서 sess_use_database 한 줄만 켰고 코드는 안 고쳤다. 서버가 둘이어도 로그인이 유지되고 한 대가 죽어도 다른 쪽에서 이어졌다.
DB가 바빠졌다
며칠 뒤 DB 부하가 늘었다. 요청 하나가 SELECT 와 UPDATE 를 한 번씩 하므로 모든 요청이 DB 를 친다.
조회보다 갱신이 컸다. last_activity 를 매 요청마다 쓰는데 읽기는 여럿이 동시에 되지만 쓰기는 행 잠금이 걸린다.
설정을 보니 sess_time_to_update 가 있었다. 기본값이 300 초라 원래도 5분에 한 번만 쓰게 돼 있었는데 우리 쪽에서 그것을 짧게 바꿔 둔 상태였다. 기본값으로 되돌리니 쓰기가 크게 줄었다.
만료된 세션 행이 쌓이는 것도 있었다. COUNT(*) 로 세어 보니 수십만 건이었다.
프레임워크의 정리는 확률로 돈다. gc_probability 가 5 라 요청 100 번 중 5 번쯤 그 자리에서 DELETE 가 도는 구조다. 평소에는 안 보이다가 몰릴 때 응답이 늘어졌다. 그래서 crontab 에 건 배치로 뺐다.
DELETE FROM sessions WHERE timestamp < ? LIMIT 1000;
한 번에 다 지우려니 오래 걸리고 그동안 InnoDB 가 그만큼의 행을 잠갔다. LIMIT 1000 으로 나눠 반복하게 하니 사이에 다른 요청이 들어갈 틈이 생겼다.
미루면서 조건을 적어 뒀다
DB 말고 memcached 에 두는 방법도 있었다. 그쪽이 빠른데 서버를 하나 더 띄워 관리해야 한다. 이 시점에는 그 부담이 커서 DB 로 버티기로 했다.
대신 언제 바꿀지를 적어 뒀다. sessions 쿼리가 전체에서 큰 비중이 되거나 sess_time_to_update 를 늘려도 부하가 안 잡히면 그때 옮긴다.
안 하는 것과 미루는 것은 다르다는 것이 여기서 정리됐다. 조건을 적어 두면 다시 볼 시점이 정해지고 적어 두지 않으면 그냥 잊는다.
정리
- 서버가 여러 대면
session.save_path아래 파일 세션이 안 맞는다 - 같은 서버로 붙여 두면 한 대가 죽을 때 전부 풀린다
sess_use_database로 옮기면 해결되는데 매 요청마다 친다sess_time_to_update기본값 300 초면 쓰기가 크게 준다gc_probability로 도는 정리는 몰릴 때가 있으니crontab으로 뺀다- 한 번에 지우면 오래 잠기니
LIMIT 1000으로 나눈다 memcached가 빠르지만 관리 부담이 있으면 미룰 수 있다- 미룰 때는 다시 볼 조건을 적어 둔다