2008년 6월 2일 월요일

daybreaker.info 첫화면의 새 글 보여주기

얼마 전에 제 개인 웹사이트인 daybreaker.info 대문을 Django 기반으로 변경했습니다. 아직은 데이터베이스를 쓸 만한 껀덕지도 별로 없지만 몇몇 생각하고 있는 것들이 있어 생각난 김에 바꿨지요.

그러고나서 단순하게 메뉴와 프로필 링크만 달랑 달려있는 현재 대문을 좀더 생동감 있게 바꾸었으면 하는 생각이 들어 mootools와 django를 이용하여 간단하게 최신글 3개씩을 보여주도록 하였습니다.

어떻게 만들었는지 간단히 정리해볼까 합니다.

1. 기존 코드
Mootools 1.11 버전을 이용, #menu-info와 #menu-tip이라는 div 레이어로 각 메뉴 링크의 title 속성을 애니메이션화하도록 되어 있었습니다.

2. Javascript 업글(?)
얼마 전부터 Google이 Ajax library들을 모아서 호스팅해주는 서비스를 시작했습니다. 트래픽을 최대한 아끼기 위해 그것을 이용했습니다.

Tip! google.load()를 이용할 때 <head> 태그 내에 자바스크립트 코드 자체가 직접 포함되어 있을 경우, 그 스크립트에서는 mootools에서 제공하는 domready 이벤트가 제대로 작동하지 않았습니다. 대신 google.setOnLoadCallback()을 이용하거나, 스크립트를 외부 파일로 빼내어 <script src="..."> 태그를 google.load()보다 아래쪽에 두면 문제 없이 실행됩니다.

3. Javascript 작성
뭐 긴말 할 것 없이 직접 소스를 보시는 게 빠르겠습니다. =3=3
앞쪽에 if (window.ie6) {...} 부분은 일단 무시하시고...-_-;;; (이 부분은 프로필 페이지에서 제 사진이 페이드인되는 효과를 구현하는 것인데, IE6에서 css 호환이 잘 안 되어 자바스크립트로 강제하는 것 때문에 쓸데없이 길어졌습니다.)

이전에는 #menu-info, #menu-tip을 HTML에 넣어둔 채로 시작했는데 이번부터는 아예 다 자바스크립트에서 동적으로 생성하여 사용하도록 하여 HTML을 좀더 깔끔하게 유지할 수 있었습니다. 대신 메뉴의 각 링크 element에 tag라는 비표준 속성을 추가하여 이 링크가 어디를 가리키는 것인지 자바스크립트 및 뒤에서 만들 ajax handler에서 구분하기 쉽도록 했습니다. (표준 속성인 id나 name을 이용해도 되지만 그러면 이름을 붙일 때 또 뭔가 'menu-'와 같은 prefix를 붙여야 할 것 같았기 때문에 그냥 깔끔하게 보이려고..-_- 이건 취향 따라 하시면 되겠습니다.)

Mootools가 제공하는 애니메이션 효과나 확장 메소드 등은 잘 문서화가 되어 있는 편이므로 직접 무툴즈 웹사이트를 참고하시길 권해드립니다.

가장 크게 삽질했던 부분은, 처음에는 모든 팁을 하나의 div element를 이리저리 옮겨가며 보여주었는데 ajax 로딩 시간이 마우스 움직임보다 느릴 경우 이전에 로딩을 시작한 내용이 다른 메뉴 항목에 마우스를 올렸을 때 나타난다든지 하는 타이밍 문제였습니다. 이걸 제대로 맞추려고 별의별 이상한 삽질을 다했는데 결론은 'element 개수 몇 개 되지도 않는데 메모리 아끼느니 메뉴별로 만들자'였고 5분만에 해결했습니다. -_-;

4. 서버측 스크립트 작성
이미 Django가 세팅되어 있는 상태에서, 다음과 같이 ajax 핸들러 주소만 추가해주었습니다. (루트에 있는 urls.py에서 /main의 urls.py를 include하고 아래는 그 두번째 urls.py의 내용)

from django.conf.urls.defaults import *
from mysite.main import views

urlpatterns = patterns('',
        (r'^ajax/news/(?P<menu>.+)$', views.ajax_news),
)

위와 같이 하면 menu라는 이름으로 캡쳐된 URL의 부분문자열이 ajax_news라는 뷰 핸들러 함수의 menu 인자로 넘겨집니다.

핸들러의 구조는 대략 다음과 같습니다.

def ajax_news(request, menu):
    # menu 이름으로 해당하는 최신 정보를 뽑아서 items 배열에 dictionary 형식(title, date)으로 저장함.
    # 실제로는 직접 MySQL로 DB를 읽거나 feedparser라는 패키지를 이용해 RSS를 긁었다.
    return render_page(request, 'ajax.main.news.html', {'menu': menu, 'items': items, 'no_date': no_date})

여기서 ajax.main.news.html은 완성된 HTML 문서를 담고 있는 게 아니라 Ajax로 전송되어질 부분(<ul> 블록)만 담고 있습니다. no_date는 Boolean 변수로, True가 지정되면 날짜를 출력하지 않습니다.

한 가지 삽질했던 것은 Feed burner의 RSS에는 date 정보가 없고-_- 본문 앞쪽에 그냥 텍스트로 붙여준다는 점인데, 아마 HanRSS 등에서는 그걸 직접 파싱하는 게 아닌가 싶군요.;; 그냥 저는 귀찮아서 no_date = True로 처리.;;

5. Cache
여기까지 만들고 나니 일단 돌아가기는 했습니다...만 메뉴 항목에 마우스를 올릴 때마다 서버측에서 http request를 날려서 rss를 가져오고 그걸 파싱하고 앉아있으니 엄청 느립니다. 그래서 얼마 전에 퍼키군님이 이름 통계 분석기를 만들면서 memcached를 이용해 큰 성능 향상을 보셨다는 글을 보고, 저도 적용하기로 결정했죠.

방법은 아주 간단합니다. Django 매뉴얼을 따라 memcached를 세팅하고(제 서버는 이미 세팅되어 있었음) settings.py에 CACHE_BACKEND 설정을 추가하고 view 핸들러에 @cache_page decorator를 붙여주면 끝입니다. 데코레이터 인자로 시간(초)을 주면 해당 시간이 지난 뒤 엑세스할 때 새로 내용을 업데이트하지요.

Firebug로 확인해본 결과 캐시가 없을 때는 최대 5초 이상 걸리던(;;) request 반응 시간이 10~50ms 정도로 단축되는 것을 볼 수 있었습니다. 툴팁 텍스트가 튀어나오는 애니메이션이 이루어지는 동안 로딩이 다 끝난다는 뜻이지요. 물론 중간에 캐시를 새로고침하는 경우에도 지루하지 않도록 ajax loading indicator를 넣기는 했습니다.

Django에서는 template의 일정 부분만을 캐싱한다든가 DB 레벨에서 캐싱한다든가 아니면 memcached에 직접 접근한다든가 하는 식으로 여러 가지 캐시 방법을 제공합니다. 현재 텍스트큐브 DB 백엔드를 완전히 새로 짜려고 머릿속으로 구상 중인데(조만간 TNF 분들께 문서 하나 공유 들어가겠습니다) 이런 특성들을 텍스트큐브 2.0에 도입하면 어떨까 생각 중입니다. 웹호스팅 업체에서 memcached를 지원하는지가 문제긴 하지만...-_-;;

뭐 어쨌든 이렇게 해서 또 하나의 삽질을 완성했던 것입니다.; =3=3