포스팅 설명을 위해 Tomcat설치 경로는 아래에 명시된 경로로 가정하고 이하 TOMCAT_HOME으로 명시
/engn001/tomcat/9.0/servers/ist_8180
catalina.sh
Catalina.out 파일 생성 설정은 catalina.sh에 있고 경로는 다음과 같음
TOMCATHOME/bin/catalina.sh
관리방법1: Catalina.out 제거
첫번째 관리 방법으로 catalina.sh 파일에서 Catalina.out 생성 부분을 수정하여 Catalina.out 파일 생성을 중지함.
수정 전
TOMCAT_HOME/logs에 Catalina.out 생성
if [ -z "$CATALINA_OUT" ] ; then
CATALINA_OUT="$CATALINA_BASE"/logs/catalina.out
fi
수정 후
catalina.out 생성 경로에 /dev/null 설정하여 생성을 차단
if [ -z "$CATALINA_OUT" ] ; then
CATALINA_OUT=/dev/null
fi
관리방법2: Rolling Catalina.out
두번째 방법은 OS의 기능을 활용하여 Catalina.out 파일을 Rolling 할 수 있음.
설정 프로세스는 아래와 같음
tomcat 파일 생성
디렉토리 이동 및 파일 생성
[root@ ]# cd /etc/logrotate.d/
[root@ logrotate.d]# vi tomcat
tomcat 파일 내부 스크립트 내용
/logs001/tomcat/9.0/ist_8180/server.log { // Catalina.out 로그파일 경로
copytruncate // 기존 파일 백업 및 삭제
daily // 로그파일을 날짜별로 Rolling
rotate 30 // 최대 30일까지만 생성
compress // 로그파일 gzip 압축
missingok // 로그파일 부재시 무시함
notifempty // 로그파일 부재시 신규 생성 하지 않음
dateext // 순환된 로그파일 날짜 확장자
}
crontab에 tomcat 파일 등록
파일 열기
[root@ logrotate.d]# vi /etc/crontab
crontab 작성
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
# For details see man 4 crontabs
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
0 0 * * * root run-parts/etc/cron.daily
처리 프로세스
Rolling 수행 프로세스는 먼저 /etc/crontab 스케쥴 동작 이 설정된 시간에 맞춰 일어나고 다음으로 /etc/logrotate.conf 실행 > /etc/logrotate.d 참조 및 최종적으로 tomcat 스크립트가 실행 됨
검증 방법
아래 스크립트를 수동 실행하여 설정이 잘 동작 하는지 확인
/usr/sbin/logrotate -f/etc/logrotate.conf
생성 결과 확인
기존 Catalina.out은 압축되고 신규로 Catalina.out 파일 생성 확인
etc-user etc-user 0 Feb 3 07:34 Catalina.out
etc-user etc-user 101491 Feb 3 07:03 Catalina.out-20200203.gz
더불어 vi /var/log/cron 로그를 확인 하면 아래와 같이 설정된 스케쥴 시점에 cron.daily 실행 로그를 확인 할 수 있음
Feb 4 00:40:01 CROND[5713]: (root) CMD (run-parts /etc/cron.daily)
Feb 4 00:40:01 run-parts(/etc/cron.daily)[5713]: starting logrotate
Feb 4 00:40:01 run-parts(/etc/cron.daily)[5723]: finished logrotate
Feb 4 00:40:01 run-parts(/etc/cron.daily)[5713]: starting man-db.cron
Feb 4 00:40:02 run-parts(/etc/cron.daily)[5736]: finished man-db.cron
Feb 4 00:40:02 run-parts(/etc/cron.daily)[5713]: starting mlocate
Feb 4 00:40:02 run-parts(/etc/cron.daily)[5747]: finished mlocate
Appendix: catalina.YYYY.MM-DD.log 제거
Tomcat은 Catalina.out 파일과 더불어 catalina.YYYY.MM-DD.log 형태의 로그가 생성 됨.
해당 로그는 1일 단위로 Rolling되지만 Tomcat에서 생성하는 로그 외에 응용로그(log4j2, logback)은 기록하지 못하므로 활용성이 낮음
아래 경로에 존재하는 logging.properties 파일을 삭제하여 생성을 막음.
경로1: 별도의 인스턴스 구분 없는 경우
TOMCAT_HOME/conf/logging.properties
경로2: 별도의 인스턴스를 사용하는 경우(인스턴스 경로가 TOMCAT_HOME/servers/ist_8180/ 인 경우)
RDBMS만큼의 정합성과 영속성을 보장할 필요가 없는 데이터들을 빠르게 처리하거나 일정 기간동안만 보관하고 있기 위한 용도로 레디스(Redis), memcached 등의 in-memory 기반 저장소가 많이 사용된다. 그중에서도 Redis는 빠른 성능을 유지하면서도 일정 수준 이상의 persistence를 제공하고, 다양한 native 데이터 구조들을 제공하여 효율적이고 편리한 사용이 가능하게 해주기 때문에 다양한 use-case들이 존재한다.
이 글은 실제 명령어를 날려서 레디스를 직접 사용해보는 것을 배우기 보다는 어떤 in-memory 저장소를 선택할지 고민하는 분들을 위해서 주요 운영 방식, 레디스의 내부 동작 방식 및 특징, 주요 클라이언트들에 대한 정보를 제공하는 쪽에 초점이 맞춰져 있다.
운영 모드 (Operation Modes)
레디스는 단일 인스턴스만으로도 충분히 운영이 가능하지만, 물리 머신이 가진 메모리의 한계를 초과하는 데이터를 저장하고 싶거나, failover에 대한 처리를 통해 HA를 보장하려면 센티넬이나 클러스터 등의 운영 모드를 선택해서 사용해야 한다. 각 모드들이 어떤 특성을 갖는지 좀더 자세히 알아보도록 하자.
단일 인스턴스(Single instance)
HA(High availibilty) 지원 안됨.
센티넬(Sentinel)
HA 지원
master/slave replication
sentinel process
redis와 별도의 process
여러개의 독립적인 sentinel process들이 서로 협동하여 운영된다 (SPOF 아님)
안정적 운영을 위해서는 최소 3개 이상의 sentinel instance 필요 (fail over를 위해 과반수 이상 vote 필요)
redis process가 실행되는 각 서버마다 각각 sentinel process를 띄워놓는 방법
redis process가 실행되는 서버와 별개로 redis에 액세스를 하는 application server들에 sentinel process를 띄워놓는것도 가능
등등 다양한 구성이 가능
지속적으로 master/slave 가 제대로 동작을 하고있는지 모니터링
master에 문제가 감지되면 자동으로 failover 수행
클라이언트는 어떻게 redis server에 연결해서 데이터를 조회하나?
먼저 sentinel에 연결해서 현재 master를 조회해야 한다.
클러스터(Cluster)
HA, sharding 지원
Sentinel과 동시에 사용하는 것이 아님! 완전히 별도의 솔루션.
dataset을 자동으로 여러 노드들에 나눠서 저장해준다.
Redis Cluster 기능을 지원하는 client를 써야만 데이터 액세스 시에 올바른 노드로 redirect가 가능하다.
Cluster node들의 동작 방식
serve clients 6379 (data port)
cluster bus 16379 (data port + 10000)
자체적인 바이너리 프로토콜을 통해 node-to-node 통신을 한다.
failure detection, configuration update, failover authorization 등을 수행
각 노드들은 클러스터에 속한 다른 노드들에 대한 정보를 모두 갖고있다.
Sharding 방식
최대 1000개의 노드로 샤딩해서 사용. 그 이상은 추천하지 않음
consistent hashing을 사용하지 않는대신 hashslot이라는 개념을 도입
hashslot
결정방법 CRC16(key) mod 16384를
CRC16을 이용하면 16384개의 슬롯에 균일하게 잘 분배됨
노드별로 자유롭게 hash slot을 할당 가능
예)
Node A contains hash slots from 0 to 5500.
Node B contains hash slots from 5501 to 11000.
Node C contains hash slots from 11001 to 16383.
운영 중단 없이 hash slots을 다른 노드로 이동시키는 것이 가능
add/remove nodes
노드별 hashslot 할당량 조정
multiple key operations 을 수행하려면 모든 키값이 같은 hashslot에 들어와야 한다.
이를 보장하기위해 hashtag 라는 개념 도입
{} 안에있는 값으로만 hash 계산
{foo}_my_key
{foo}_your_key
Replication & failover
failover를 위해 클러스터의 각 노드를 N대로 구성가능
master(1대) / slave(N-1대)
async replication (master → slave replication 과정에서 ack을 받지 않음)
데이터 손실 가능성 존재
master가 client요청을 받아서 ack을 완료한 후, 해당 요청에 대한 replication이 slave로 전파되기 전에 master가 죽는 경우 존재
클라이언트는 클러스터에 어떻게 연결해서 데이터를 조회하나?
redis client는 클러스터 내의 어떤 노드에 쿼리를 날려도 된다(슬레이브에도 가능).
ex) GET my_key
쿼리를 받은 노드가 해당 쿼리를 분석
해당 키를 자기 자신이 갖고있다면 바로 찾아서 값을 리턴
그렇지 않은경우 해당 키를 저장하고 있는 노드의 정보를 리턴 (클라이언트는 이 정보를 토대로 쿼리를 다시 보내야함)
ex) MOVED 3999 127.0.0.1:6381
메모리 동작 방식
key가 만료되거나 삭제되어 redis가 메모리를 해제하더라도, OS에서 해당 분량만큼 바로 메모리가 확보되진 않음
꼭 redis에만 해당되는 이야기는 아님
5GB중 3GB의 데이터를 메모리에서 해제 -> OS 메모리 사용량은 여전히 5GB
하지만 다시 데이터를 add하면 logically freed된 영역에 잡히므로 실제 메모리 5GB를 넘지는 않는다.
따라서 peak memory usage 기준으로 잡아야 한다.
대부분 5GB 정도 사용하고 가끔 10GB 가 필요하더라도, 10GB 메모리를 이상의 머신이 필요.
maxmemory 설정을 해두는게 좋음 (하지 않으면 무한히 메모리 사용하다가 머신 전체가 죽을 가능성)
maxmemory 설정시의 eviction policy
no-eviction (추가 memory를 사용하는 write command에 대해 에러 리턴)
allkeys-lru (전체 아이템 중에서 LRU)
volatile-lru (expire 되는 아이템 중에서 LRU)
volatile-ttl (expire 되는 아이템 중 TTL이 얼마 안남은 녀석 순으로)
RDB persistence를 사용한다면 maxmemory를 실제 가용 메모리의 45%정도로 설정하는것을 추천. 스냅샷을 찍을때 현재 사용중인 메모리의 양만큼 필요하다. (5%는 오버헤드에 대비한 마진)
사용하고 있지 않다면 가용 메모리의 95%정도로
동일 key-value 데이터를 저장한다고 가정했을 때, Cluster Mode를 사용할 경우 Single instance 보다 1.5~2배 정도 메모리를 더 사용하는것에 주의해야 한다.
Redis cluster의 경우 내부적으로 cluster안에 저장된 key를 hashslot으로 맵핑하기 위한 테이블을 가지고 있기 때문에 추가적인 memory overhead가 발생한다.
이때문에 key의 숫자가 많아질수록 이러한 현상이 더 두드러진다
4.x 버전에 이와 관련한 메모리 최적화 기능이 들어가서 3.x 버전보다는 더 적게 메모리를 사용하지만, 여전히 Single instance보다는 많은 메모리를 필요로 한다.
데이터 영속성 (Data Persistence)
memcached의 경우 데이터가 메모리에만 저장되기 때문에 프로세스가 재기동되면 메모리상의 데이터는 모두 유실된다. 하지만 redis의 경우 기본적으로 disk persistence가 설정되어있기 때문에, 프로세스를 재시작 하더라도 셧다운 되기 전의 마지막 상태와 거의 동일한 (약간의 손실은 있을 수 있다) 상태로 돌려 놓을 수 있다.
RDB persistence
일정 인터벌로 point-in-time snapshots을 생성
compact한 단일 파일로 저장됨 백업하기 좋음
부모 프로세스는 자식 프로세스를 fork. fork된 프로세스에서 모든 persist I/O처리
restart 시간이 빠르다
H/W 사양에 따라 다르겠지만 보통 메모리 사용량 1GB당 10~20 초정도 로드타임
멈췄을때 데이터 유실 가능성이 더 높다.
몇 분 정도의 데이터 유실 가능성을 감내 할 수 있다면 RDB를 쓰는것을 추천
AOF (append only file)
모든 write operation 을 log로 남기고 서버 재시작 시점에 replay
데이터 양이 많아지면 해당 시점의 데이터셋을 만들어낼 수 있도록하는 minimum log들만 남기는 compaction을 진행
언어별로 레디스 클라이언트 라이브러리들이 다양하게 존재하고 있다. 그중에서 자바언어로 만들어진 가장 많이 사용되는 3가지를 뽑아서 비교해 보았다. async style API를 사용하는 경우 처리량(throughput)을 높일 수 있지만 오히려 개별 요청에 대한 응답 지연(latency)은 sync API 보다 더 느려질 수 있으니 상황에 맞게 선택해서 사용하는 것이 좋다.
Redis란? 정말 단순하게 표현하면 key-value 형태의 값을 저장할 수 있는 하나의 In-Memory DB라고 생각하시면 됩니다.
사용용도는 정말 많습니다. Springboot에서는 세션클러스터링을 위해 Redis를 사용하기도 하고, Cache를 위해서 사용하기도 하며, 여러 서버에서 공통으로 공유해야하는 정보를 담는 용도로 사용하기도 합니다. 필자는 챗봇을 개발중인데, 이중화된 서버에서 세션값을 공유하기위한 용도로 Redis를 사용하려고 합니다. 우선 이번 포스팅에서는 Redis 설치 및 설정, 그리고 간단한 사용법에 대한 포스팅입니다.
Redis Server의 최대 메모리 사용양을 설정한다. 만약 memory에 한계가 도달했다면 Redis는 선택된 eviction policy(제거정책)에 따라 key들을 제거한다. 만약 제거에 실패한다면 read-only에 대한 명령만 수행할 수있게 된다.
maxmemory-policy [policy]
max memory 도달시 취할 정책을 설정한다 1) volatile-ru : expire가 설정된 key들 중 LRU 알고리즘을 이용해 선택된 key를 제거한다. 2) alleys-lru : 모든 keys에 대해 LRU 알고리즘을 적용해 제거한다. 3) volatile-random : expire가 설정된 key들 중 임의의 key를 제거한다. 4) alleys-random : 모든 keys중 랜덤하게 제거한다. 5) volatile-ttl : expire time이 가장 적게 남은 key를 제거한다. 6) noeviction : 어떠한 key도 제거하지 않는다. read-only작업만 허용하고 write와 그 밖에 명령은 error를 내뿜는다.
appendonly [yes||no]
Redis는 기본적으로 비동기로 disk에 dataset의 dump를 남긴다. 이 방법은 최근 데이터가 손실되어도 큰 문제가 없다면 사용해도 좋지만, 하나라도 데이터의 유실을 원치 않는 상황이라면 yes로 설정하여 요청받는 모든 write를 disk에 쓰도록하면 된다.(default file appendonly.aof)
appendfilename [file name]
append only 파일 이름을 설정한다.
여기까지 몇가지 설정들을 다루어봤다. 실제 위에 설정들 일부를 사용하여 Master-Slave 관계를 구성해볼 것이다.
우선 MasterNode의 설정파일로 사용할 redis.conf이다.
>bind 127.0.0.1
>port 6379
>requirepass yourpassword
>daemonize yes
이정도 설정으로 나머지는 기본설정을 사용한다.
이제는 나머지 SlaveNode의 설정파일 2개이다.
>bind 127.0.0.1
>port 6380(slave -1) / port 6381(slave-2)
>replicaof 127.0.0.1 6379
>masterauth yourpassword
>daemonize yes
로 구성한다.
이제 순차적으로 실행시켜본다
>$REDIS/src/redis-server ../redis.conf
>$REDIS/src/redis-server ../redis-slave1.conf
>$REDIS/src/redis-server ../redis-slave2.conf
Master&Slave를 순차적으로 실행 시킨 로그이다. Connection이 맺어지고 있는 것이 보인다.
그리고 실제로 데이터의 복제가 잘 이루어지는지 보기 위해 몇개의 창을 더 띄워서
cli로 데이터를 삽입,조회해본다.
복사가 잘된것을 확인할 수 있다. 그렇다면 혹시나 Master와의 관계를 끊기 위해
Master 노드를 중지시켰다면 어떻게 될까?
SlaveNode들의 로그를 보면 connection refused 로그가 계속해서 떨어진다.
하지만 아까 보았던 설정에서 replica-server-stale-data의 설정에 따라 Slave들의 행동이
달라질것이다.
마지막으로 다시 MasterNode를 띄워보면
Slave들이 다시 Master에 connection 된것을 볼수있다.
여기까지는 좋다. 데이터 복제를 위해 replica도 구성했고, 근데 하나찜찜한 것이있다. Master가 죽으면 알아서 Slave중에서 Master를 선출하면 좋을 텐데.... 방법이 있다!
바로 sentinel을 이용하여 클러스터를 구성해주는 것이다. sentinel은 아쉽게도 다음 포스팅에서 다뤄볼 예정이다.
유저 요청에 대한 선 처리 : 유저의 요청이 WAS에 도달하기 전에 다양한 처리를 할 수 있다. 웹 애플리케이션 방화벽(WAF)를 설치하거나, 유저의 요청을 다른 위치로 보내도록 제어 할 수 있다.
캐싱 : 웹 서비스는 이미지, CSS, 자바스크립트 같은 정적인 페이지를 가지고 있다. 이런 정적 컨텐츠들을 NginX에서 대신 처리하는 것으로 응답 속도를 높일 수 있으며, WAS에 대한 부담을 줄일 수 있다. 컨텐츠들을 메모리에 캐시할 경우 서비스 할 경우 고성능의 웹 서비스를 만들 수도 있다.
웹 서비스의 성능을 높일 수 있는 가장 확실하고 손쉬운 방법은컨텐츠 캐시다. 요즘 웹 서버는 매우 바쁘다. 유저의 요청을 받아서 데이터베이스를 조회해서 즉석에서 HTML 페이지를 만들고 이미지와 CSS, 자바스크립트 등 다양한 오브젝트들을 함께 응답해야 한다. 웹 서버가 해야 하는 일이 늘어나면서리버스 프락시를 이용, 두 개 이상의WAS(Web Application Server)가 요청을 분산해서 처리하도록 구성을 한다.
유저가 요청을 내리면 WAS로 바로가지 않고, 리버스 프락시 서버를 한번 거치게 된다. 요청을 분산하기 위해서 프락시 서버를 한번 더 거쳐야 하는데, 모든 컨텐츠가 굳이 프락시 서버를 거칠 필요가 있을까 ?변하지 않는 컨텐츠의 경우에는 그냥 프락시 서버에서 직접 응답을 하게 하면 더 빠른 서비스를 제공 할 수 있지 않을까 ? 요즘 왠만한 웹 서비스들은 동적으로 컨텐츠를 서비스 한다. 하지만 조금만 들여다 보면, 많은 컨텐츠가 정적(변하지 않음)이라는 것을 알 수 있다. 이미지, CSS, Javascript, 동영상, PDF 뿐만 아니라 HTML 문서의 상당수도 정적이다. 유저 정보를 표시하는 HTML 문서는 (데이터베이스 등을 조회해서) 동적으로 만들어져야 하겠으나, 메뉴얼, 사이트 소개, 메뉴 등은 변하지 않는 컨텐츠들이다. 이들 컨텐츠들을 프락시 서버에서 서비스 한다면 더 빠르고 효율적으로 서비스 할 수 있을 것이다. 아래 그림을 보자.
리버스 프락시에서 정적인 페이지를 대신 응답한다. 이렇게 구성해서 얻을 수 있는 성능상의 잇점을 살펴보자.
네트워크 홉 이 줄어든다. WAS까지 요청을 보내지 않아도 된다. 1만큼의 네트워크 홉을 줄일 수 있다.
파일처리에 최적화 할 수 있다. 캐시 서버는 파일에 대한 "읽기/쓰기" 연산, 그 중에서도 읽기 연산을 주로 한다. 목적이 특정되므로 이에 맞게 최적화 할 수 있다. 당연하지만 NginX나 Apache 같은 전용 웹서버는 다양한 일을 하는 WAS(Django, RoR, Node)보다 빠르고 효율적으로 작동한다.
최적화가 쉽다. 정적 컨텐츠와 동적 컨텐츠를 분리 함으로써, 그에 맞는 최적화 방법을 사용 할 수 있다. 정적 컨텐츠를 Redis나 memcache, 램디스크 등으로 캐시할 수 있다.
테스트를 위해서 WAS 서버를 구성했다. 이 서버는 HTML, CSS, 자바스크립트, 이미지등을 서비스한다. NginX로 만들기로 했다. 아래는 설정파일이다.
# cat /etc/nginx/sites-available/default
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
location ~* \.(?:manifest|appcache|html?|xml|json)$ {
expires -1;
}
location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc)$ {
expires 1M;
access_log off;
add_header Cache-Control "public";
}
location ~* \.(?:css|js)$ {
expires 1y;
access_log off;
add_header Cache-Control "public";
}
}
이 설정파일은 기본적인 캐시 설정을 포함하고 있다. manifest, appcache, html, xml, json 파일들은 캐시를 하지 않는다. jpg,gif,gz 과 같은 파일들은 한달의 만료기간동안 캐시를 한다. css와 js 파일들은 1년을 캐시한다.curl로 index.html을 요청해보자.
WAS 서버 설정이 끝났다. 이제 캐시서버를 만든다. 이 캐시서버는 WAS 서버를 로드밸런싱하면서 동시에 주요 컨텐츠들을 캐싱하는 역할을 한다. 유저의 요청은 캐시서버를 거치는데, 요청 결과가 캐시되어 있다면 (WAS까지 요청을 보내지 않고)캐시서버가 즉시 결과를 응답한다.역시 NginX 서버를 설치하고 아래와 같이 설정했다.
# cat /etc/nginx/site-available/default
server {
listen 80 default_server;
root /var/www/;
index index.html index.htm;
charset utf-8;
location / {
include proxy_params;
proxy_pass http://192.168.56.30;
}
}
우선은 간단한 reverse proxy로 설정했다. 캐시서버는192.168.56.31:80에서 유저 요청을 기다린다. 만약 유저 요청이 도착하면, proxy_pass에 설정된192.168.56.30으로 요청을 전달한다. curl을 이용해서 /css/foundation.css를 요청해 보자. 캐시서버가 컨텐츠를 캐시하지 않고 있기 때문에, 이 요청은 WAS까지 전달된다.
캐시파일이 저장되는 위치와 저장 방식을 설정하기 위해서 사용한다.levels는 캐시파일을 어떻게 저장할지를 결정한다. 만약 levels를 설정하지 않는다면, 캐시파일은 현재 디렉토리에 저장이 된다. level을 설정할 경우, 서브디렉토리에 md5 해시된 파일이름으로 컨텐츠를 저장한다. level=1:2는 첫번째 단계의 디렉토리는 한글자, 두번째 단계의 디렉토리는 두 글자로 명명하라는 의미다. 예를 들어/tmp/nginx/a/bc디렉토리에 캐시 파일이 저장된다.keys_zone은 이 캐시를 가리키는 이름이다.my_zone은 캐시와 메타파일을 저장하기 위해서 10M의 공간을 할당 했다.
inactive지시어를 사용 하면, 일정 시간동안 접근이 없는 캐시파일을 할 수 있다. 위 설정에서는 한시간(60 minutes)동안 사용하지 않는 캐시파일은 삭제하도록 설정했다.
Joinc는Go로 직접 개발한 CMS(자칭 gowiki)로 운영하고 있다. 모니위키를 기반으로 하고 있기 때문에, 위키문법의 문서를HTML로 변환하는데 상당한 자원이 들어간다. 성능을 올리기 위해서 gowiki 서버 앞에 nginx 캐시서버를 두기로 했다.
/w 로 시작하는 URL의 컨텐츠는 정적인 컨텐츠이므로 캐시하기로 했다. 예를 들어 처음/w/FrontPage를 요청하면, Gowiki로 요청이 전달된다. Gowiki는 FrontPage 문서를 HTML로 변환해서 응답한다. 이 응답은 NginX 서버가 캐시를 하므로, 다음번 요청 부터는 캐시에 있는 데이터를 응답한다.동적인 컨텐츠는/api로 요청을 한다. 코드 실행기가 대표적인 경우인데, 이런 컨텐츠는 캐시하면 안 된다. 그리고 css, js, png, jpg 등도 캐시하기로 했다. NginX의 설정은 다음과 같다.
일단 캐시가된 컨텐츠의 경우, 컨텐츠의 내용이 바뀌더라도 여전히 캐시의 내용을 읽어오는 문제가 있다. 위키 특성상 문서를 자주 수정하게 되는데, 수정한 내용이 보이지 않는 것은 심각한 문제가 될 수 있다. 문제를 해결하기 위해서는 문서의 수정 후 해당 컨텐츠의 캐시를 찾아서 삭제하는 코드를 만들어야 했다. 직접 만들기 귀찮아서nginx-cache-purge라는 프로그램을 다운로드 해서 사용했다. 그럭저럭 잘 작동하는 것 같다. 예를 들어서 /w/man/12/nginx 문서를 수정했다면./nginx-cache-purge /w/man/12/nginx /tmp/nginx수행하면 된다.혹은 컨텐츠의 key를 md5 한 값을 데이터베이스에 저장해서 문서 수정시 삭제하는 방법이 있다. 위 설정에 의하면 /w/man/12/nginx의 key는 "httpGETwww.joinc.co.kr/w/man/12/nginx/static"이다. 이걸 md5 한 값은 아래와 같다.
모든 페이지들이 메뉴와 wiki 본문 으로 구성이 된다. 메뉴는 유저의 상태에 따라서 구성이 달라진다. 로그인 하기 전이라면 로그인 관련 메뉴를 출력하고,로그인 후라면 문서 편집과 같은 메뉴들이 출력된다. 컨텐츠가 동적으로 바뀌는 셈이다. 반면 Wiki 문서 내용은 정적 컨텐츠다. 만약 페이지 전체를 캐시를 해버리면, 메뉴 출력에 문제가 생길 것이다. 이 문제를 해결해야 했다.나는쿠기(cookie)를 이용해서 이 문제를 해결 했다. 아래와 같이 proxy_cache_key에cookie_login을 설정했다.
유저가 로그인에 성공하면 SetCookie("login", "true")로 login 쿠키를 설정한다. 로그아웃 하면 SetCookie("login","")로 쿠키를 삭제한다. 이렇게 하면 동일한 페이지라고 하더라도 로그인과 로그아웃 상태별로 캐시파일을 만들 수 있다. 간단하기는 하지만 캐시파일이 두배가 된다는 단점이 있다. 메뉴부분을 모두 자바스크립트로 처리하는 방법도 있는데, "개인 사이트에서 캐시공간이 늘어나 봤자 얼마나 늘어나겠나 ?"라는 생각에 패스하기로 했다(귀찮아서 패스했다는 얘기다).