이 글은 인스타그램(Instagram)의 엔지니어링 블로그에 작성된 글을 번역한 것이다. 원문은 여기.

최근 유사한 문제로 고민하다가 발견한, 많은 도움을 받을 수 있었던 글.

또 다른 번역글도 있으니 참고하시길.





Sharding & IDs at Instagram


인스타그램에는 매 초당 25개의 사진과 90개의 Like 정보가 등록된다. 우리는 이런 중요한 정보들이 메모리에 적합하게 적재되고 사용자들이 빠르게 사용할 수 있도록 만들기 위해 데이터를 샤딩(Sharding)하고 있다. 다시 말하면 데이터들을 여러 개의 작은 버킷으로 나누고 각각의 버킷은 데이터의 일부를 저장하는 형태이다.

우리의 애플리케이션 서버는 DJango로 구현되어 있으며 백엔드 데이터베이스 서버로 PostgreSQL을 사용하고 있다. 데이터를 샤딩하기로 결정한 이후 우리가 가진 첫 번째 고민은 주 데이터 저장소로서 PostgreSQL을 그대로 활용할 것인지 아니면 다른 것으로 변경할 것인지 여부를 결정하는 것이었다. 몇 가지 NoSQL 솔루션을 검토해 봤지만 현재 우리에게 가장 적합한 솔루션은 여러 대의 PostgreSQL 서버에 데이터를 샤딩하는 것이라는 결론에 도달했다.

그러나 데이터를 이들 서버에 나누어 기록하기에 앞서 우리는 데이터베이스 내에 분산될 각각의 데이터(예를 들면 우리의 시스템으로 등록되는 각 사진들)를 위한 유일한 식별자를 할당해야 하는 이슈를 해결해야만 했다. 동시에 여러 데이터베이스에 데이터가 추가되는 환경에서는 단일 데이터베이스에서 동작하는 해결책 - 예를 들면 데이터베이스의 자동 증가 기본 키 기능을 사용하는 등 - 은 올바른 해결책이 될 수 없었다. 이 블로그의 내용은 이와 같은 이슈를 우리가 어떻게 해결했는지를 설명한다.

시작하기에 앞서 우리는 시스템의 기본적인 요구 사항을 다음과 같이 정리했다.

  1. 생성된 ID는 반드시 시간 순으로 정렬이 가능해야 한다 (예를 들어 모든 사진 ID는 사진에 대한 추가 정보를 조회하지 않고도 정렬이 가능해야 한다).
  2. ID는 64비트여야 한다(인덱스의 크기를 줄이고 시스템에 적용된 Redis와 같은 더 나은 저장소를 지원할 수 있다).
  3. 우리 시스템은 새로운 시스템 컴포넌트의 도입을 최소화해야 한다. 겨우 몇 명의 엔지니어로 인스타그램의 확장성을 확보하기 위해서는 우리가 신뢰하는 간결하며 이해하기 솔루션으로 구성되어야 한다.

기존의 솔루션

이미 ID 생성 문제에 대한 해결책은 여러 가지가 존재한다. 이들 중 우리가 고려해 본 방법은 다음과 같다.

웹 애플리케이션에서 ID를 생성하는 방법

이 방법은 ID 생성을 데이터베이스가 아닌 애플리케이션에 완전히 위임하는 방식이다. 예를 들어 몽고DB의 ObjectId는 12바이트 길이의 인코딩된 타임스탬프를 첫 번째 컴포넌트로 사용한다. 또 다른 방법은 UUID를 사용하는 방법이다.

장점:
  1. 각각의 애플리케이션 스레드가 ID를 독자적으로 생성하기 때문에 ID 생성의 실패와 병목 현상을 최소화할 수 있다.
  2. ID의 첫 번째 컴포넌트로 타임스탬프를 사용하면 ID를 시간 순으로 정렬할 수 있다.
단점:
  1. 유일성을 보장하기 위해서는 통상 더 많은 저장 공간(96비트 혹은 그 이상)이 필요하다.
  2. 일부 UUID 타입은 완전히 랜덤하게 만들어져 본질적으로 정렬이 불가능하다.

독립된 서버에서 ID를 생성하는 방법

아파치 주키퍼(ZooKeeper)를 사용하는 트위터의 Snowflake와 같은 Thrift 서비스를 이용하여 64비트의 유일한 ID를 생성한다.

장점:
  1.  Snowflake의 ID는 64비트이며 UUID의 절반에 지나지 않는다.
  2. 정렬을 위해 시간을 ID의 첫 번째 컴포넌트로 사용할 수 있다.
  3. 일부 노드가 다운되더라도 사용 가능한 분산 시스템이다.
단점:
  1. 복잡도가 증가하며 시스템 아키텍처 내의 구성 요소가 증가하게 된다 (ZooKeeper, Snowflake 서버 등)

DB 티켓 서버를 활용하는 방법

이 방법은 데이터베이스의 자동 증가 기능을 이용하여 유일성을 확보하는 방법이다. 플리커(Flickr)가 이 방식을 사용하지만 두 개의 티켓 DB(하나는 홀수, 하나는 짝수를 담당)를 통해 SPOF(Single Point of Failure, 시스템 컴포넌트 중 하나가 다운됨으로서 전체 시스템이 마비되는 현상)을 해결하고 있다.

장점:
  1.  DB는 이미 친숙하며 확장에 대한 예측이 가능하다.
단점:
  1. 결과적으로 쓰기 병목이 발생할 수 있다 (플리커에서 이런 현상을 경험했지만 대규모 서비스에서는 큰 이슈가 아닐 수 있다).
  2. 관리해야 할 머신(혹은 EC2 인스턴스)이 증가한다
  3. 단일 DB를 사용하면 SPOF의 근원이 될 수 있다. 여러 DB를 사용하면 시간으로 정렬 가능한 ID의 생성을 보장할 수 없다.
이상의 방법들을 검토해 본 결과 트위터의 Snowflake가 우리의 요구사항에 가장 적합했지만 ID 서비스를 위해 시스템의 복잡도가 증가하는 것이 마음에 걸렸다. 결국 우리는 PostgreSQL을 통해 이와 유사한 컨셉의 방법을 직접 구현하기로 결정했다.

우리의 해결책

우리의 샤딩 시스템은 수천 개의 '논리적' 샤드로 구성되며 각각의 논리적 샤드는 보다 적은 수로 구성된 물리적 샤드에 매핑된다. 이렇게 함으로써 몇 대의 데이터베이스 서버만으로 샤드를 구성할 수 있었으며 향후 데이터를 새로운 버켓으로 이동할 필요없이 단순히 논리적 샤드를  다른 데이터베이스 서버로 이동하는 것만으로 샤드를 확장할 수 있게 되었다. 또한 PostreSQL의 스키마 기능을 이용하여 손쉽게 스크립팅과 관리가 가능하도록 구성하였다.

스키마(개별 테이블에 대한 SQL 스키마와 혼동하지 않기를 바란다)는 PostgreSQL이 제공하는 논리적 그룹화 기능이다. 각각의 PostreSQL DB는 여러 개의 스키마를 가질 수 있으며 각각의 스키마는 하나 이상의 테이블을 가질 수 있다. 테이블 이름은 DB가 아닌 스키마 별로 유일해야 하며 기본적으로 PostreSQL은 이 모든 것을 'public'이라는 이름의 스키마를 통해 관리한다.

앞서 이야기한 각각의 '논리적' 샤드란 우리의 시스템 내에서는 Postgres 스키마이며 샤드된 각 테이블(예를 들면 사진 데이터들)은 각 스키마에 위치하게 된다.

ID의 생성은 Postgres가 제공하는 자동 증가 기능과 내장 프로그래밍 언어인 PL/PSGSQL을 이용하여 각 샤드 내의 각 테이블에서 담당한다.

각 ID는 다음과 같이 구성된다.
  • 밀리 초 단위의 시간을 위한 41비트(41년을 사용할 수 있는 ID를 생성할 수 있다).
  • 논리적 샤드 ID를 표현하기 위한 13비트
  • 자동 증가 값을 1024로 나눈 나머지 값을 표현하기 위한 10비트. 이는 샤드 당 및 초당 1024개의 ID를 생성할 수 있음을 의미한다.
예를 들어 설명해 보자. 현재 시간이 2011년 9월 9일 오후 5시 정각이며 시스템이 2011년 1월 1일부터 가동되기 시작했다고 가정해보자. 그렇다면 현재는 시스템 가동 이후 1387263000 밀리 초가 지난 시간이며 따라서 ID의 처음 41비트는 다음과 같은 값이 채워진다.

id = 1387263000 << (64 - 41)


다음으로 추가할 데이터의 샤드 ID를 결정해야 한다. 데이터를 사용자 ID를 기준으로 샤딩하며 2000개의 논리적 샤드가 존재하고 사용자 ID가 31341이라고 가정하면 샤드 ID는 31341 % 2000 = 1341이 된다. 따라서 ID의 다음 13비트는 이 값으로 채워진다.
id |= 1341 << (64 - 41 - 13)

마지막으로 자동 증가 값(이 값은 각 스키마 내의 각 테이블마다 유일하다)을 얻어 나머지 비트를 채워야 한다. 이미 테이블에  5,000개의 ID가 생성되어 있다고 가정하면 다음 값은 5001이며 이 값을 1024로 나누어(그러면 10비트로 채우기에 적당하다) 나머지 비트를 다음과 같이 채우면 된다.
id |= (5001 % 1024)

이제 ID가 완성되었으므로 이 값을 RETURNING 키워드를 통해 애플리케이션 서버로 리턴하면 된다.

아래의 코드는 완성된 PL/PGSQL 코드이다(예제 스키마는 insta5이다).
CREATE OR REPLACE FUNCTION insta5.next_id(OUT result bigint) AS $$
DECLARE
    our_epoch bigint := 1314220021721;
    seq_id bigint;
    now_millis bigint;
    shard_id int := 5;
BEGIN
    SELECT nextval('insta5.table_id_seq') %% 1024 INTO seq_id;

    SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
    result := (now_millis - our_epoch) << 23;
    result := result | (shard_id << 10);
    result := result | (seq_id);
END;
$$ LANGUAGE PLPGSQL;

그리고 테이블은 다음과 같이 생성한다.
CREATE TABLE insta5.our_table (
    "id" bigint NOT NULL DEFAULT insta5.next_id(),
    ...테이블의 나머지 스키마...
)

이로서 끝이다. 기본 키는 애플리케이션 내에서 완전히 유일하다(게다가 샤드 ID를 포함하고 있어 매핑이 더욱 쉬워졌다). 이 방법을 실제 서비스에 적용해 본 결과는 매우 만족스러웠다. 이같은 확장성에 대한 해결책을 마련하는데 흥미가 있다면 우리 회사에 지원해 보기 바란다.

Mike Krieger, co-founder


















Posted by 웹지니 트랙백 1 : 댓글 14
안녕하세요 웹지니입니다.

세상엔 참 착한 사람이 많은거 같아요. 며칠 전까지 우분투 리눅스에 Mono 한 번 설치해 보겠다고 갖은 삽질을 마다 않던 저에게 단비같은 사이트가 하나 나타났네요. Badgerports.org라고 하는 이 사이트는 Jo Shields라는 사람이 운영하는 사이트입니다. 이 사람은 현재 데비안/우분투 리눅스를 위한 Mono 패키지 메인 관리자라고 하네요.

그림 1: bagderports.org 홈페이지

이 사이트는 단순히 사이트에 대한 소개 및 사용법 설명이 전부입니다. Jo가 제공하는 패키지 서버를 우분투 리눅스에 등록하면 Jo의 패키지 서버를 통해 최신 버전의 Mono 프레임워크와 MonoDevelop IDE를 apt-get 명령으로 손쉽게 설치할 수 있어요. 이 글을 작성하는 현재 제공되는 버전은 아래와 같습니다.

  • Mono 2.6.3
  • MonoDevelop 2.4 + 전체 패키지

자, 그럼 우분투 서버에 Jo의 패키지 서버를 등록해 볼까요?

badgerports 패키지 서버 등록하기

우선 우분투 리눅스의 상단 위치한 시스템 메뉴에서 [System > Software Sources] 메뉴를 선택합니다. 그러면 아래 그림과 같은 대화 상자가 나타납니다. 여기서 [Other Software] 탭을 클릭하고 [Add] 버튼을 클릭한 후 그림과 같이 소스 서버의 주소를 입력해 줍니다.


입력을 완료했으면 [Add Source] 버튼을 클릭하고 [Authentication] 탭을 다시 클릭한 후 [Import Key File] 버튼을 클릭합니다. 그러면 파일 찾기 대화 상자가 나타날텐데요. 여기를 클릭해서 키 파일을 다운로드한 후 해당 파일을 찾아 선택하면 됩니다. 키 파일을 지정했으면 [Close] 버튼을 클릭해서 대화 상자를 닫고 [Reload] 버튼을 클릭하여 새로 추가한 정보를 업데이트해 줍니다. 여기까지 하면 일단 패키지 서버의 추가는 완료!

Mono와 MonoDevelop 설치하기

패키지 서버를 등록했으므로 이제 apt-get 명령을 이용하면 Mono와 MonoDevelop을 손쉽게 설치할 수 있습니다. Mono와 MonoDevelop을 설치하는 명령은 아래와 같습니다.
sudo apt-get install mono
sudo apt-get install monodevelop
크흑... 두 줄의 명령만으로 Mono와 MonoDevelop을 손쉽게 설치할 수 있게 되었습니다. 아래는 인증샷.


그림에서 보듯이 Mono 2.6.3과 MonoDevelop 2.4가 설치된 것을 확인할 수 있어요.
착한 Jo한테 메일이라도 한 통 보내야 할까봅니다. 최저 생활비 수급자인 관계로 Donate는 생략~ ㅋ.

그럼 모두 즐거운 하루 되세요~
Posted by 웹지니 트랙백 0 : 댓글 3
안녕하세요? 웹지니입니다.

드디어 여름인가봅니다. 날씨가 무척이나 덥네요.
요즘도 전 우리 회사 제품들을 리눅스 운영체제에서 동작할 수 있도록 하기 위해 Mono/리눅스와 씨름을 하고 있습니다. 리눅스 초짜에겐 다소 버거운(ㅠㅠ) 작업이네요.

오늘은 우분투 리눅스 10.04 LTS 버전에서 Mono와 MonoDevelop의 설치 방법에 대해 기록해 둘까 해요.
역시나 잊어버리면 안되기에...ㅋ

Mono 설치하기

우선은 Mono부터 설치해 보겠습니다. 며칠간 삽질하면서 보니 Mono를 설치할 수 있는 방법은 꽤나 다양하더군요. 대략 정리해보면 아래와 같습니다.

1. Mono 프로젝트 홈페이지에서 공식 릴리즈된 Mono 설치 파일을 다운로드하여 설치합니다. 이 경우 현재 시점에서는 2.6.4 버전이 설치됩니다.
2. Mono 프로젝트 홈페이지에서 Subversion 기반의 소스 저장소로부터 trunk혹은 tag 버전을 직접 다운로드 및 빌드하여 설치합니다. 이 경우 tag 버전은 2.6.4가, trunk 버전은 2.7이 설치됩니다.
3. Mono 프로젝트 홈페이지에서 매일 배포하는 Daily Build를 다운로드하여 빌드 및 설치합니다. 이 경우는 버전을 명확하게 알 수 없으며 해당 날짜가 버전 대신 사용됩니다. (MonoDevelop이 현재 실행 중인 Mono 버전을 알 수 없다고 징징거리더라고요 -ㅅ-;;;)

오늘 소개할 방법은 이 3번 방법입니다. 우선 구글링을 하다보니 어느 착한(?) 분이 친절하게도 설치 과정을 스크립트로 작성해 둔 것이 있었어요. 원문은 여기서 보실 수 있고 설치 스크립트는 아래와 같습니다. 

#!/bin/bash

TOPDIR=$(pwd)
BUILDDIR=$TOPDIR/build
DLDDIR=$TOPDIR/downloads

export PATH=/usr/local/bin:$PATH

echo "installing prerequisites"
sudo apt-get install -y build-essential libc6-dev g++ gcc libglib2.0-dev pkg-config subversion apache2 bison gettext

mkdir -p $BUILDDIR

echo
echo "downloading mono packages"
echo

cd $BUILDDIR

svn co svn://anonsvn.mono-project.com/source/trunk/xsp
svn co svn://anonsvn.mono-project.com/source/trunk/mod_mono

wget http://mono.ximian.com/daily/mono-latest.tar.bz2
wget http://mono.ximian.com/daily/monocharge-latest.tar.gz

cd $BUILDDIR
bunzip2 -df mono-latest.tar.bz2
tar -xvf mono-latest.tar
tar -xzvf monocharge-latest.tar.gz

echo
echo "building and installing mono packages"
echo
cd $BUILDDIR
cd mono-*
./configure --prefix=/usr/local --with-glib=system
make
sudo make install

cd $BUILDDIR
cd monocharge*
sudo env prefix=/path/to/prefix ./recharge.sh

cd $BUILDDIR
cd xsp
./autogen.sh --prefix=/usr/local
make
sudo make install

cd $BUILDDIR
cd mod_mono
./autogen.sh --prefix=/usr/local
make
sudo make install
cd $BUILDDIR

echo
echo "done"
위의 스크립트를 실행하면 최신 Daily Build Binary Package를 이용하여 Mono 프레임워크를 설치할 수 있습니다. 위의 스크립트를 *.sh 파일로 저장하고 실행하면 걍 날로 먹는 Mono 설치는 이것으로 끝 -ㅅ-;;;

MonoDevelop의 설치

어느 틈엔가 MonoDevelop이 2.4 버전으로 업그레이드가 되었어요. Mono 프로젝트의 소스 저장소와 공식 릴리즈된 MonoDevelop의 Tarball 파일을 다운로드하여 MonoDevelop 2.4를 설치하는 스크립트는 다음과 같습니다.

#!/bin/bash

TOPDIR=$(pwd)
BUILDDIR=$TOPDIR/build

echo "installing prerequisites"

sudo apt-get install -y libpango1.0-dev libatk1.0-dev libgtk2.0-dev
sudo apt-get install -y libglib2.0-dev libglade2-dev
sudo apt-get install -y libpanel-applet2-dev libgtkhtml3.14-dev libgtksourceview-dev
sudo apt-get install -y libgnomeprint2.2-dev libgnomeprintui2.2-dev
sudo apt-get install -y autoconf libtool automake
sudo apt-get install -y libgnome-desktop-dev
sudo apt-get install -y libglade2.0-cil-dev
sudo apt-get install -y subversion apache2-threaded-dev

echo
echo "downloading monodevelop packages"
echo

cd $BUILDDIR
svn co svn://anonsvn.mono-project.com/source/trunk/mono-addins
svn co svn://anonsvn.mono-project.com/source/trunk/gnome-sharp
svn co svn://anonsvn.mono-project.com/source/trunk/gnome-desktop-sharp
sudo wget http://ftp.novell.com/pub/mono/sources/monodevelop/monodevelop-2.4.tar.bz2

echo
echo "installing monodevelop 2.4"
echo

cd gnome-sharp
./bootstrap-2.24 --prefix=/usr/local
make
sudo make install
cd $BUILDDIR

cd gnome-desktop-sharp
./autogen.sh --prefix=/usr/local
make
sudo make install
cd $BUILDDIR

cd mono-addins
./autogen.sh --prefix=/usr/local
make
sudo make install
cd $BUILDDIR

tar xvjf monodevelop-2.4.tar.bz2
cd monodevelop-2.4
./configure --prefix=/usr/local
make
sudo make install

echo "done"
여기까지 실행하면 MonoDevelop 2.4의 설치도 완료! 아래는 인증샷입니다.


지금까지 작성한 스크립트로 이제는 삽질 그만 할래요 ㅠㅠ. 참, 이 삽질을 왜 했느냐구요?

삽질 비화 대공개!!

저는 현재 우리 회사 제품을 리눅스 운영체제 상에서도 동작할 수 있도록 수정하고 있습니다. 기존에는 .NET 3.5기반에서 Windows 운영체제와 SQL Server 데이터베이스만을 지원했거든요. 리눅스를 지원하게 되면서 자연스럽게 오라클이나 MySql 등 다른 데이터베이스 서버의 지원도 필요하게 되었어요. 이 때 가장 문제가 되었던 것은 DBMS별로 조금씩 다른 SQL 쿼리였습니다.

이쯤에서는 당연히 동일한 코드로 여러 DB를 지원할 수 있는 ORM 도구의 사용을 고려하게 됩니다. 그러나 Linq to SQL의 경우는 단순한 1:1 매핑만을 지원하고 Entity Framework의 경우에는 아직 Mono에서 지원이 되지 않고 있었죠. 그래서 결국 선택하게 된 것이 NHibernate였습니다.

NHibernate는 훌륭한 기능들을 제공했지만 두 가지가 아쉬웠는데 그 중 하나는 XML 파일 기반의 객체 관계 매핑이었고 두 번째는 다소 생소한 형태의 Criteria API였습니다. Criteria API란 NHibernate를 이용하여 SQL 쿼리를 만들어내기 위한 API입니다.

XML 파일 기반의 객체 관계 매핑 문제는 Fluent Interface 패턴을 이용하여 C# 코드로 객체 관계 매핑을 생성할 수 있는 Fluent NHibernate로 간단히 해결할 수 있었습니다. 또한 Criteria API는 Linq to NHibernate라는 녀석을 이용해서 해결할 수 있었죠. 여기에 가 보시면 NHibernate Linq 2.1.2 버전을 다운로드하실 수 있습니다.

문제는 바로 이 Linq to NHibernate였는데요. 이 녀석은 System.Data.Services 어셈블리의 3.5 버전을 참조로 빌드되었습니다. 안타깝게도 Mono 2.6.4에서는 이 어셈블리의 2.0 버전만 제공되는데요. 알고보니 이것은 Mono 팀의 실수라고 하네요. 즉, 3.5 버전이 제공되어야 함에도 불구하고 실수로 2.0 버전이(히뱀...) 배포된 것인 듯 합니다.

결국 Linq to NHibernate를 사용하는 우리 제품의 코드가 Mono 플랫폼에서 빌드조차 되지 않는 황당한 상황이 되어 버린거죠. 해서 가장 최신 버전의 Mono 플랫폼을 설치해 보게 되었고 그 결과 System.Data.Services 어셈블리의 3.5버전과 4.0 버전이 제공되는 것을 확인할 수 있었습니다.

이로 인해 Mono와 MonoDevelop 설치, 제품 코드 빌드 등에 거의 일주일을 날려먹었네요. 초짜는 힘들어요. ㅠㅠ
혹시 Mono로 개발하실 일이 있으시다면 저처럼은 삽질하지 마시길...

이만 물러갑니다!


Posted by 웹지니 트랙백 0 : 댓글 2

안녕하세요? 웹지니입니다.

2010년 새해가 시작된지도 벌써 1주일이나 지났네요. 다들 건강하시죠?
전 요즘 감기가 걸려서 개고생을 하고 있습니다. 사실 아프려면 크게 아파야 좀 쉬기도 하고 그럴텐데 지금처럼 콧물만 졸졸 흐르면 괜히 지저분하기만 하고 일하기만 더 힘들고 그러네요.

2010년 새해에도 웹지니는 여전히 ASP.NET을 이용한 크로스 플랫폼 웹 애플리케이션을 개발하고 있습니다.
물론 Primary Target은 Windows 플랫폼이지만 리눅스/유닉스 계열과 Mac OS 등을 탑재한 시스템에도 저희 팀에서 구현한 애플리케이션이 동작해야 한다는 (그것도 별도의 코드 수정이나 재컴파일 없이 -ㅅ-;;) 다소 난해한 요구 사항과 맞닥뜨린 상황입니다.
해서 Windows 이외의 플랫폼에서는 마이크로소프트가 지원하고 노벨(Novell)이 프로젝트를 주도하는 모노(Mono)를 사용하게 된 것이지요.

사실 10년 간 Windows 플랫폼만 사용해 온 웹지니에게 리눅스 플랫폼은 매우 생소한 녀석이었기에 이런 저런 고생을 좀 했습니다.
특히 Windows 플랫폼에서는 그냥 “다음” 버튼만 누르면 간단히 해결되던 소프트웨어의 설치가 가장 난제였었지요.
해서 이번 포스트는 크게 두 가지 목적으로 작성되었습니다.

1. Ubuntu 리눅스에서 소스 컴파일을 통해 모노 프레임워크를 설치하는 방법을 잊어버리지 않기 위해서
2. 혹시 다른 .NET/모노 개발자에게 도움이 될까봐

그럼 시작해 볼까요?

1. Installing MonoDevelop 2.0 on Ubuntu 9.10

일단 이 포스트는 Ubuntu 리눅스 9.10 버전을 기준으로 하며 여러분이 이미 가상 머신 혹은 실제 물리적 머신에 Ubuntu 9.10을 설치한 상태라고 가정합니다. 사실 Ubuntu 9.10에는 Mono 2.4.2.3 버전이 이미 설치되어 있으며 모노 개발을 위한 IDE인 MonoDevelop 2.0이 미리 패키징되어 있어 손쉽게 설치가 가능합니다.

Mono 2.4.2.3과 MonoDevelop 2.0만으로도 얼마든지 리눅스 플랫폼에서 .NET 개발이 가능합니다. 게다가 설치도 참 쉽죠~잉. 리눅스 터미널에서 다음과 같은 커맨드만 실행하면 됩니다.

  1: sudo apt-get install monodevelop monodevelop-database

그런 후 리눅스 사용자 계정의 비밀 번호를 입력해 주면 간단히 MonoDevelop 2.0을 설치할 수 있습니다. 그러나 최신 버전은 Mono 2.6.1과 MonoDevelop 2.2이며 늘 최신 버전에 목말라 하는 웹지니는 미리 패키징 된 개발 도구들 대신 최신 소스를 다운로드하여 직접 컴파일 후 설치하는 과정에 도전해 보기로 한 것이지요.

2. Building and Installing Mono 2.6.1 on Ubuntu 9.10

Mono 2.6.1을 설치는 과정은 제 개인적으로는 절대 쉽지 않았습니다. 우선 Ubuntu에 필요한 라이브러리들이 모두 설치되어 있어야 하기에 어떤 것들을 먼저 설치해야 하는지 알아내는 것도 쉽지 않았지요. 그러면 이제 필요한 라이브러리를 먼저 설치해볼까요? 터미널에서 다음과 같은 커맨드를 실행합니다.

  1: sudo apt-get remove mono-common
  2: 
  3: sudo apt-get install build-essential pkg-config bison gettext libglib2.0-dev libcairo2-dev libungif4-dev libjpeg62-dev libtiff4-dev 
  4: 
  5: wget http://ftp.novell.com/pub/mono/sources/libgdiplus/libgdiplus-2.6.tar.bz2
  6: tar xvjf libgdiplus-2.6.tar.bz2
  7: cd libgdiplus-2.6
  8: ./configure && make && sudo make install

자 먼저 1번 라인의 커맨드를 이용하여 이전 버전의 mono-common 라이브러리를 삭제한 후 3번 라인과 같이 필요한 라이브러리들을 모두 설치합니다. 그런 후 마지막으로 libgdiplus 라이브러리를 설치해야 하는데 이 라이브러리는 Mono 2.6.1에 맞춘 최신 버전이 제공되므로 5번 라인과 같이 FTP를 통해 먼저 다운로드 한 후 6번 라인의 커맨드로 압축을 해제하고 8번 라인과 같이 설치해 줍니다.

여기까지 설치했으면 이제 드디어 Mono 2.6.1을 설치할 차례입니다. 다음의 커맨드를 차례로 실행해 보겠습니다.

  1: wget http://ftp.novell.com/pub/mono/sources/mono/mono-2.6.1.tar.bz2
  2: tar xvjf mono-2.6.1.tar.bz2
  3: cd mono2.6.1
  4: ./configure && make && sudo make install

차례대로 설명을 드리자면 1번 라인의 커맨드로 Mono 2.6.1의 소스 코드를 다운로드 한 후 2번 라인의 커맨드로 압축을 해제하고 4번 라인의 커맨드로 설치를 합니다. 설치 과정은 생각보다 오래 걸립니다 (흡연자라면 담배 한 대 때리고 오심이... 쿨럭). 알 수 없는 내용들이 터미널에 가득 출력이 되고 가끔 .NET 컴파일 경고가 보이기도 하지만 일단 불안해 하지 말고 넘어갑니다. 설치 과정이 모두 완료되면 다음과 같이 시스템에 설치된 모노의 버전을 확인할 수 있습니다.

  1: mono --version
  2: 
  3: Mono JIT compiler version 2.6.1 (tarball Thu Jan 7 16:44:43 KST 2010)
  4: Copyright (C) 2002-2008 Novell, Inc and Contributors. www.mono-project.com
  5: 	TLS:		__thread
  6: 	GC:		Included Boehm (with typed GC and Parallel Mark)
  7: 	SIGSEGV:	altstack
  8: 	Architecture:	x86
  9: 	Disabled:	none
 10: 

1번 라인과 같이 mono 실행 파일에 --version 옵션을 주면 3라인 이후의 정보가 출력됩니다. 3번 라인의 버전이 2.6.1로 표시되면 올바르게 설치가 완료된 것입니다.

3. Building and Installing MonoDevelop 2.2 on Ubuntu 9.10

Mono 2.6.1을 설치했으면 이제 모노 개발을 위한 IDE인 MonoDevelop의 최신 버전인 2.2 버전을 설치해 보겠습니다. 안타깝게도 MonoDevelop 2.2는 Windows 플랫폼을 비롯하여 Mac OS X와 노벨의 OpenSUSE 리눅스에서의 설치를 위한 패키지를 지원하지만 Ubuntu 리눅스를 위한 패키지는 지원하지 않습니다. 해서 번거롭지만 소스 컴파일을 통해 설치해야 하는 것이지요. MonoDevelop을 설치하기 이전에 먼저 GTK# 및 Gnome#이라는 라이브러리를 설치해야 합니다. 터미널에서 다음과 같이 커맨드를 실행합니다.

  1: wget http://ftp.novell.com/pub/mono/sources/gtk-sharp212/gtk-sharp-2.12.9.tar.bz2
  2: wget http://ftp.novell.com/pub/mono/sources/gnome-sharp2/gnome-sharp-2.24.1.tar.bz2
  3: wget http://ftp.novell.com/pub/mono/sources/gnome-desktop-sharp2/gnome-desktop-sharp-2.24.0.tar.bz2
  4: 
  5: tar xvjf gtk-sharp-2.12.9.tar.bz2
  6: tar xvjf gnome-sharp-2.24.1.tar.bz2
  7: tar xvjf gnome-desktop-sharp-2.24.0.tar.bz2
  8: 
  9: cd gtk-sharp-2.12.9
 10: ./configure && make && sudo make install
 11: 
 12: cd ../gnome-sharp-2.24.1
 13: ./configure && make && sudo make install
 14: 
 15: cd ../gnome-desktop-sharp-2.24.0
 16: ./configure && make && sudo make install

먼저 위와 같이 세 개의 GTK#및 Gnome# 라이브러리를 다운로드 한 후 각자 압축을 해제하고 각각의 라이브러리가 압축 해제된 폴더에서 설치를 실행합니다. 설치가 완료되면 이제 다음의 커맨드를 이용하여 MonoDevelop 2.2의 소스 코드를 다운로드 하고 설치합니다.

  1: wget http://ftp.novell.com/pub/mono/sources/monodevelop/monodevelop-2.2.tar.bz2
  2: 
  3: tar xvjf monodevelop-2.2.tar.bz2
  4: 
  5: cd monodevelop-2.2
  6: ./configure && make && sudo make install

위의 커맨드를 실행하고 설치가 완료될 때까지 기다립니다. 설치가 완료되면 아래 그림과 같이 Ubuntu 데스크톱의 [Applications] 메뉴에 [Programming > MonoDevelop] 메뉴가 생성됩니다.

MonoDevelop 메뉴를 클릭하면 아래 그림과 같이 MonoDevelop 2.2가 실행됩니다. 새 프로젝트 대화 상자에서 볼 수 있듯이 ASP.NET MVC 1.0 프로젝트과 Silverlight의 리눅스 버전인 Moonlight 애플리케이션 개발을 위한 템플릿도 지원되네요. 훗~

이상으로 Mono 2.6.1과 MonoDevelop 2.2를 설치해 보았습니다. 사실 Mono 2.6.1은 아직 .NET 프레임워크 3.5의 모든 기능을 구현하고 있지는 않습니다만 .NET 2.0의 클래스 라이브러리는 거의 대부분 구현하고 있어 실제 프로젝트를 진행하기에 큰 무리는 없어 보입니다.

앞으로 .NET/Mono 프레임워크로 크로스 플랫폼 웹 애플리케이션을 구현하면서 알게 되는 유용한 정보들도 함께 공유하도록 할게요.
허접한 설치 경험담은 여기서 끝~

Posted by 웹지니 트랙백 0 : 댓글 0

안녕하세요? 웹지니입니다.

오늘은 .NET on OpenSource 시리즈의 두 번째 포스트로 Memcached라는 이름의 분산 메모리 캐싱 시스템을 Ubuntu 리눅스에 설치하고 이를 .NET Application에서 활용하는 방법에 대해 소개해 볼까 합니다. 사실 이번 시리즈를 시작하게 된 계기가 있는데요. 다른 .NET 개발자 분들은 어떨지 모르겠지만 저 개인적으로는 많은 부분을 Microsoft에 의존하고 있었던 것 같아요. 그 사실을 Memcached라는 녀석을 알게 되면서 깨닫게 되었습니다.

현재 Microsoft는 Velocity라는 이름으로 Memcached와 유사한 분산 캐싱 시스템을 구현 중에 있어요. Velocity의 가장 버전은 이 글을 쓰는 시점에서는 CTP3이며 조만간 CTP4가 출시될 예정입니다. 사실 CTP 버전이면 실무에 도입하기에는 다소 무리가 따르죠. 반면 Memcached는 이미 LiveJournal.com을 비롯하여 이미 많은 곳에서 사용되고 있으며 충분히 검증된 시스템입니다.

이런 경우 당연히 후자인 Memcached를 선택하는 것이 타당하겠지요. 문제는 .NET 환경에서 사용이 가능한지 여부입니다. 다행히 Memcached 그 자체는 객체를 메모리 캐시에 넣고 빼는 매우 기본적인 기능에만 충실합니다. 대신 Client API를 위한 프로토콜을 정의하고 Memcached 서버와 클라이언트 사이의 통신은 Client API가 책임지도록 하고 있어요. 따라서 잘 만들어진 .NET용 Client API가 있다면 .NET 환경에서도 얼마든지 Memcached 서버를 활용할 수 있는 셈이지요.

또한 Win32버전의 Memcached 서버도 존재합니다. 그러나 안타깝게도 원래 Memcached를 개발하고 배포하는 Danga Interactive가 제공하는 것은 아니며 Linux용 Memcached가 현재 1.2.8 버전인 것에 비해 Win32용은 1.2.1 버전이 제공되고 있습니다. 해서 저는 최종적으로 Linux 환경에서 Memcached 1.2.8을 설치하고 .NET 환경을 위한 Client API를 사용하여 .NET Application에 분산 캐시를 도입하기로 마음 먹었답니다.

1. Installing libevent on Ubuntu

여러분이 이미 Ubuntu 리눅스가 설치된 VM이나 혹은 물리적 머신을 가지고 있다는 가정하에 Ubuntu 리눅스에 Memcached 서버를 설치해 보도록 하겠습니다. Memcached 서버를 설치하려면 그 전에 libevent라는 라이브러리를 먼저 설치해야 합니다. 우선 libevent 라이브러리의 홈페이지를 방문해 볼까요?

img1

그림에서 보듯이 좌측의 Download 메뉴를 보면 libevent-1.4.11이 현재 가장 최신의 안정 버전임을 알 수 있습니다. 그러면 리눅스 환경에서 이 파일을 다운로드하여 설치를 해야겠지요. 링크를 마우스 오른쪽 버튼으로 클릭해 보면 해당 링크가 가리키는 주소를 알 수 있습니다. 이 주소는 리눅스에서 파일을 다운로드할 때 필요합니다. 리눅스의 터미널을 열고 다음과 같이 명령을 날려봅니다.

  1: $ wget http://www.monkey.org/~provos/libevent-1.4.1-stable.tar.gz\
  2: $ gunzip libevent-1.4.11-stable.tar.gz
  3: $ tar xvf libevent-1.4.11-stable.tar
  4: $ cd libevent-1.4.11-stable/
  5: $ ./configure
  6: $ make
  7: $ sudo make install

1번 라인의 커맨드는 libevent 홈페이지로부터 libevent-1.4.11-stable.tar.gz 파일을 다운로드하는 명령입니다. 다운로드가 완료되면 2번 라인과 같이 gunzip 명령으로 gz 파일의 압축을 해제한 후 다시 3번 라인과 같이 tar 명령을 이용하여 libevent-1.4.11-stable.tar 파일의 압축을 해제합니다.

압축이 해제되면 4번 라인과 같이 압축이 해제된 디렉터리로 이동한 후 5번, 6번, 7번 라인의 명령을 차례대로 실행합니다. 이 세 가지 명령은 리눅스에서 뭔가를 빌드해서 설치하기 위한 기본 단계입니다. 별 다른 오류 없이 설치가 완료되었다면 whereis 명령으로 libevent 라이브러리가 제대로 설치되었는지 확인할 수 있습니다.

  1: $ whereis libevent
  2: libevent: /usr/local/lib/libevent.a /usr/local/lib/libevent.so /usr/local/lib/libevent.la

저의 경우에는 /usr/local/lib 디렉터리에 libevent가 설치된 것을 확인할 수 있었습니다. 그러면 이제 Memcached를 설치해 보겠습니다.

2. Installing Memcached on Ubuntu

Memcached는 이미 리눅스 배포판에 포함되어 있습니다. 이 포스트에서 사용하고 있는 Ubuntu 9.0.4의 경우에도 이미 포함되어 있으며 아래의 커맨드를 통해 확인할 수 있습니다.

  1: apt-cache search memcached 혹은
  2: apt-cache pkgnames | grep memcached

둘 중 하나의 커맨드를 실행해 보면 Memcached와 관련된 패키지들이 주르륵 나타날 것입니다. 그러나 가장 최신 버전의 모듈이 아닌 관계로 웹을 통해 Memcached 1.2.8 버전을 다운로드해서 설치해 보겠습니다. 리눅스의 터미널에서 다음의 커맨드를 차례대로 실행합니다.

  1: $ wget http://memcached.googlecode.com/files/memcached-1.2.8.tar.gz
  2: $ gunzip memcached-1.2.8.tar.gz
  3: $ tar xvf memcached-1.2.8.tar
  4: $ cd memcached-1.2.8/
  5: $ sudo mkdir -p /opt/memcached/
  6: $ ./configure --prefix=/opt/memcached/
  7: $ make
  8: $ sudo make install
  9: $ whereis memcached
 10: /opt/memcached/bin/memcached

우선 1번 라인과 같이 Memcached 1.2.8 버전을 다운로드한 후 2번, 3번 라인과 같이 파일의 압축을 해제합니다. 그런 후 5번 라인과 같이 /opt/memcached/ 라는 이름의 디렉터리를 생성하는데 이는 제가 Memcached를 설치하려고 하는 디렉터리입니다. 이 디렉터리는 6번 라인과 같이 설치 환경을 설정할 때 --prefix 매개 변수를 이용할 수 있습니다. 그런 후 7번, 8번 라인과 같이 make, make install을 차례로 실행하여 Memcached를 설치한 후 9번 라인과 같이 설치를 확인하면 10번 라인처럼 /opt/memcached/bin/디렉터리에 Memcached가 설치됩니다.

3. Configuring and Running Memcached Service

이제 Memcached의 설치를 마쳤으므로 간단한 설정을 거쳐 서비스를 실행해 보겠습니다. 우선 Memcached가 설치된 /opt/memcached/bin/ 디렉터리로 이동하여 Memcached를 실행해 봅니다.

  1: $ cd /opt/memcached/bin/
  2: $ memcached

그러면 아마도 Memcached가 설치되지 않았다는 메시지를 보게 될 것입니다. 아니, 방금 설치했는데 이게 무슨 소리??? 그래서 Memcached가 제대로 설치됐는지를 다음과 같이 확인해 봅니다.

  1: $ ldd /opt/memcached/bin/memcached
  2:   linux-gate.so.1 => (oxb7fa0000)
  3:   libevent-1.4.so.2 => not found
  4:   libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7e2e000)
  5:   /lib/ld-linux.so.2 (0xb7fa1000)

ldd 명령을 통해 Memcached가 필요로 하는 Library Depenency를 살펴보니 3번 라인과 같이 libevent 라이브러리를 찾을 수 없다는 문장이 보이네요. 아하 이제보니 앞서 설치한 libevent 라이브러리를 Memcached가 알아보지 못했나봅니다. 해서 /etc/ld.so.conf 파일을 편집하여 libevent 라이브러리가 설치된 폴더를 지정해 주어야 합니다. vi 에디터를 이용해서 ld.so.conf 파일에 다음과 같이 문장을 추가합니다.

  1: include /usr/local/lib/libevent/

그런 후 다음과 같이 ldconfig 명령을 실행하여 방금 수정한 설정 내용이 적용되도록 합니다.

  1: $ sudo ldconfig /etc/ld.so.conf
  2: $ ldd /opt/memcached/bin/memcached
  3:   linux-gate.so.1=> (0xb8034000)
  4:   libevent-1.4.so.2 => /user/local/lib/libevent-1.4.so.2 (0xb800d000)
  5:   libc.so.6 => /lib/tls/i686/libc.so.6 (0x7eaa000)
  6:   libnsl.so.1 => /lib/tls/i686/cmov/libnsl.so.1 (0xb7e90000)
  7:   librt.so.1 => /lib/tls/i686/cmov/librt.so.1 (0xb7e87000)
  8:   libresolv.so.2 => /lib/tls/i686/cmov/libresolv.so.2 (0xb7e71000)
  9:   /lib/ld-linux.so.2 (0xb8035000)
 10:   libpthread.so.0 => /lib/tls/i686/cmov/libpthread.so.0 (0xb7e58000)

1번 라인과 같이 ldconfig 명령을 실행한 후 2번의 ldd 명령을 다시 날려보면 이번에는 모든 라이브러리들이 제대로 로드되어 있음을 확인할 수 있습니다. 이제 다음과 같이 Memcached를 실행해 봅니다.

  1: $ sudo /opt/memcached/bin/memcached -d -m 2048 -p 11211

위의 명령은 /opt/memcached/bin/ 디렉터리의 memcached 파일을 실행합니다. 이  때 –m 옵션에 의해 메모리는 2GB를 사용하며 포트 번호는 –p 11211에 의해 11211번 포트를 사용하게 됩니다. 참고로 이 포트 번호는 Memcached의 기본 포트 번호입니다. 이 커맨드 역시 /etc/rc.local 파일에 기록해 두면 리눅스가 재시작할 때 자동으로 Memcached 서비스를 시작하도록 구성할 수 있습니다.

  1: /opt/memcached/bin/memcached -d -m 2048 -p 11211 2 > &1 > /dev/null

4. Using Memcached on Microsoft .NET

이제 Memcached 서비스를 설치하고 서비스를 시작하는데 성공했다면 .NET 환경에서 Memcached 서비스를 활용하는 방법에 대해 살펴보겠습니다. 먼저 Memcached 서비스를 사용하기 위한 .NET용 Client API를 구해야 합니다. 이미 CodePlex를 통해 몇 가지 API가 제공되고 있으며 그 중 제가 보기에 enyim.com Memcached Client 프로젝트가 가장 괜찮아 보였습니다.

img2

오른쪽의 Download Now 링크를 클릭하여 enyim.com_memcached_1.2.0.2.zip 파일을 다운로드 한 후 압축을 풀어보면 Enyim.Caching.dll 파일을 발견할 수 있습니다. 그러면 이 어셈블리를 이용하여 Memcached 서비스를 이용하는 간단한 ASP.NET 웹 애플리케이션을 구현해 보겠습니다.

4.1 Create new classic ASP.NET Web Application project

Visual Studio 2008을 실행하고 새로운 ASP.NET Web Application 프로젝트를 선택한 후 아래 그림과 같이 새 프로젝트를 생성합니다.

img3

프로젝트가 생성되면 솔루션 탐색기를 통해 앞서 다운로드 한 Enyim.Caching.dll 파일을 프로젝트로 참조합니다.

img4

그런 후 Web.config 파일을 열고 앞서 다운로드한 파일의 압축이 해제된 폴더에 저장된 Sample.config 파일을 참고하여 Memcached 서비스에 대한 설정을 추가해야 합니다. 이 코드는 다음과 같습니다.

  1: <configuration>
  2:   <configSections>
  3:     <sectionGroup name="enyim.com">
  4:       <section name="memcached" type="Enyim.Caching.Configuration.MemcachedClientSection, Enyim.Caching" />
  5:     </sectionGroup>
  6:   </configSections>
  7:   <enyim.com>
  8:     <memcached enabled="true">
  9:       <!-- keyTransformer="" -->
 10:       <servers>
 11:         <add address="192.168.160.11" port="11211" />
 12:       </servers>
 13:       <socketPool minPoolSize="10" maxPoolSize="100" connectionTimeout="00:10:00" deadTimeout="00:02:00" />
 14:     </memcached>
 15:   </enyim.com>
 16: </configuration>
 17: 

여기서 가장 중요한 설정은 바로 8번 라인부터 14번 라인까지의 설정입니다. 8번 라인에서는 Memcached를 사용하도록 enabled 특성에 true를 지정하였으며 11번 라인에서는 Memcached가 서비스되는 서버의 IP와 포트 번호를 지정합니다. <servers> 요소에는 여러 개의 <add> 요소를 지정할 수 있으므로 여러 개의 Memcached 서버를 지정하여 일종의 클러스터링 캐시를 구성할 수도 있습니다.

이제 Memcached 캐시를 이용하는 간단한 예제를 구현해 보겠습니다. 우선 다음과 같이 Account라는 이름의 클래스를 구현합니다.

  1: public class Account
  2: {
  3:   public string UserName { get; set; }
  4:   public string DisplayName { get; set; }
  5:   public int Age { get; set; }
  6: 
  7:   public override string ToString()
  8:   {
  9:     return String.Format(
 10:       "{0}, {1}",
 11:       this.DisplayName,
 12:       this.Age
 13:     );
 14:   }
 15: }

Account 클래스를 추가했으면 Default.aspx.cs 파일에 다음과 같이 코드를 추가합니다.

  1: protected void Page_Load(object sender, EventArgs e)
  2:     {
  3:       MemcachedClient client = new MemcachedClient();
  4:       List<Account> accounts = client.Get<List<Account>>("myAccounts");
  5:       
  6:       if (accounts == null)
  7:       {
  8:         accounts = CreateSampleData();
  9:         client.Store(StoreMode.Set, "myAccounts", accounts);
 10:         Response.Write("Cached");
 11:         Response.Write("<br>");
 12:       }
 13: 
 14:       foreach (Account account in accounts)
 15:       {
 16:         Response.Write(account.ToString());
 17:         Response.Write("<br>");
 18:       }
 19:     }
 20: 
 21:     private List<Account> CreateSampleData()
 22:     {
 23:       List<Account> accounts = new List<Account>();
 24:       accounts.Add(new Account()
 25:       {
 26:         UserName = "webgenie",
 27:         DisplayName = "웹지니",
 28:         Age = 100
 29:       });
 30: 
 31:       accounts.Add(new Account()
 32:       {
 33:         UserName = "zmeun",
 34:         DisplayName = "천호민",
 35:         Age = 80
 36:       });
 37: 
 38:       accounts.Add(new Account()
 39:       {
 40:         UserName = "sleepy",
 41:         DisplayName = "꽃미남",
 42:         Age = 70
 43:       });
 44: 
 45:       return accounts;
 46:     }

우선 3번 라인의 코드를 보면 MemcachedClient 클래스의 인스턴스를 생성합니다. 이 클래스가 바로 Memcached 서버에 액세스하기 위한 Entry Point 역할을 담당하는 클래스입니다. 4번 라인과 같이 MemcachedClient.Get 메서드를 이용하면 Memcached 서버의 캐시로부터 지정된 키로 저장된 객체를 얻어올 수 있습니다. 만일 객체를 찾을 수 없다면 null이 리턴되므로 이 경우에는 6번 라인의 if 구문을 이용해 새로운 객체를 생성하고 이를 MemcachedClient.Store 메서드를 호출하여 Memcached 서버의 캐시에 객체를 저장합니다.

이제 이 예제 애플리케이션을 실행해 보면 아래 그림과 같이 10번 라인에서 객체를 캐시에 추가할 때 출력한 Cached!라는 문자열이 출력된 채로 데이터가 보여지는 것을 볼 수 있습니다.

img5

F5 키를 눌러 페이지를 새로 고치면 아래 그림과 같이 캐시로부터 데이터를 가져와 보여주게 됩니다.

img6

5. Why need Memcached?

자, 이제 Memcached 서버가 올바르게 동작하는 것을 확인했습니다. 그런데 이 녀석을 어디에 어떻게 써먹을 수 있을까요? ASP.NET도 이미 훌륭한 캐싱 기능을 제공하고 있는데 말이지요.

아시다시피 ASP.NET의 캐싱 기능은 웹 서버의 메모리를 활용합니다. 따라서 너무 많이 사용할 경우 결국은 웹 서버의 메모리 부하로 이어질 수 있다는 단점이 있지요. 그러나 물리적 서버 수에 여유가 있다면 이처럼 Memcached 서버를 구축함으로써 많은 데이터를 캐싱할 수 있어 전반적인 서비스의 성능 향상을 꾀할 수 있습니다.

특히 Memcached Providers 프로젝트 사이트를 보면 이 포스트에서 사용했던 Enyim Memcached Client API를 바탕으로 ASP.NET의 Cache API와 Session 객체를 대체한 Provider 객체가 구현되어 제공되고 있습니다. 따라서 현재 ASP.NET의 Cache나 Session을 사용하고 있다면 이를 Memcached 서버로 옮김으로써 웹 서버의 부하를 줄일 수 있겠지요?

뿐만 아니라 여러 개의 Memcached 서버를 구축하고 Web.config 파일에 서버들의 IP를 추가해주면 캐싱하는 객체들이 여러 Memcached 서버에 분산되어 저장될 뿐 아니라 Memcached 서버에 복제 기능이 추가된 repcached라는 녀석을 이용하면 분산 캐시 클러스터를 구성할 수도 있습니다.

물론 이들 기능들은 Velocity에서도 구현이 되고 있습니다만 분산 캐시가 필요한데 Velocity를 마냥 기다릴 수만 없다면 Memcached가 현재로서는 가장 탁월한 선택이 아닐까 생각됩니다.

요즘들어 느끼는 거지만 세상 참 좋아졌어요 ^^

즐거운 하루 되세요~

[UPDATED: 2009-06-26 13:39]

조금 전 저는 이 포스트와 동일한 방법으로 구성한 Memcached 서버에 간단한 테스트를 진행해 보았습니다. 제가 실행했던 테스트는 미국에 위치한 우리 회사의 DB로부터 100개의 레코드를 가져와 이를 List<T> 타입의 Entity 객체로 변환한 후 GridView 컨트롤에 바인딩 했을 때와 변환된 Entity 객체를 Memcached 캐시에 추가한 후 캐시로부터 가져왔을 때의 성능 비교였습니다. 먼저 그 결과를 보여드리자면 다음과 같습니다.

Try from Database (밀리초) from Cache (밀리초)
1 3862 8
2 3123 7
3 2987 7
4 3136 8
5 3124 7

위의 표에서 알 수 있듯이 성능의 차이가 상당함을 볼 수 있습니다. 물론 DB는 미국에 있고 Memcached 서버는 현재 회사 네트워크 내에 있기 때문에 더 큰 성능 상의 차이가 발생했겠습니다. 해서 회사 내의 네트워크에 존재하는 다른 DB에 대해 동일한 테스트를 수행해 보았습니다. 그 결과는 아래 표와 같습니다.

Try from Database (밀리초) from Cache (밀리초)
1 2176 8
2 2172 7
3 2174 7
4 2182 8
5 2171 7

로컬 DB와의 테스트에서도 상당한 성능 차이가 있군요. 물론 이런 이유로 캐싱을 사용하는 것이겠지만 이 정도라면 Memcached 웹사이트에 적혀있던 "Very Fast"라는 말이 무색하지 않네요. 도움이 되셨기를 바랍니다. ^^

Posted by 웹지니 트랙백 1 : 댓글 0

안녕하세요? 웹지니입니다.

최근 저는 회사 업무 차원에서 Microsoft .NET 기반의 솔루션을 개발함에 있어 오픈 소스 및 Linux 시스템과의 협업에 대해 관심있게 공부를 하고 있어요. 처음 시작은 업무를 위한 것이었는데 지금은 개인적으로도 많은 흥미를 느끼고 있어 이제는 슬슬 저만의 Linux 시스템을 구성해 보고 싶은 생각도 듭니다만 아직은 만일의 사태에 대처할 능력이 안되어 망설여지고 있는 시점입니다.

사실 Visual Basic 4.0부터 시작해서 .NET에 이르기까지 Microsoft 기술만으로 먹고 살아온 저에게 있어 Linux나 오픈 소스 세상이란 그저 소위 말하는 긱(Geek)이나 해커(Hacker, Cracker가 아닌 컴퓨터에 대한 초고수 전문가 집단을 의미하는 말입니다 ^^)들만이 살 수 있는 일종의 별천지 같은 세상으로 밖에 느껴지지 않았었는데요. 이번 기회를 계기로 조금씩 Linux와 여러 오픈 소스 프로젝트들을 접해보면서 나름대로 느낀 바가 많았습니다.

해서 지금까지 공부했던 내용들을 정리할 겸 또 여러 분들과 공유도 할 겸 시리즈로 포스트를 기획하게 되었어요. 그 첫 번째 주제로는 유명한 Subversion을 이용한 소스 제어 환경의 구축입니다. 사실 이번 포스트는 .NET과 오픈 소스의 결합이라는 측면에서 볼 때 그 의미가 크지는 않습니다. 그러나 가격이 상대적으로 비싼 Team Foundation Server의 구축은 형편 상 쉽지 않고 그렇다고 Visual Source Safe를 사용하기는 또 좀 거시기한 소규모 기업이나 개인 개발자에게 Subversion은 매우 매력적인 도구가 아닐 수 없기에 Linux 상에서 Subversion 서버를 설치하고 Visual Studio를 통해 소스 관리를 수행하는 방법에 대해 소개할까 합니다.

사실 뭐 살짝 고루한 느낌의 제목들이 아닐 수 없습니다. 구글 신에게 물어보면 금새 수많은 유사한 내용의 포스트들을 쓰나미처럼 쏟아낼테니까요. 이쯤에서 다시 한 번 강조하지만 전 남들 다 아는 이야기도 포스트로 씁니다 –ㅅ-;;;

1. Installing Subversion on Ubuntu 9.0.4

자, 그러면 일단 가장 인기있는 Linux 배포판인 Ubuntu 리눅스를 기반으로 Subversion 설치 방법에 대해 알아보겠습니다. 저는 현재 Ubuntu 9.0.4 Desktop Edition을 사용하고 있으며 VMWare를 이용하여 가상 머신에 설치해 둔 상태입니다. Ubuntu 리눅스를 올바르게 설치하셨다면 터미널을 통해 다음의 커맨드를 실행하여 손쉽게 Subversion을 설치할 수 있습니다.

$ sudo apt-get install subversion

그러면 Ubuntu 리눅스는 현재 사용자 계정을 수퍼유저로 만들기 위해 비밀 번호를 확인하려 합니다. 올바른 비밀 번호를 입력하면 커맨드가 Ubuntu 리눅스가 자신이 가지고 있는 패키지 목록에서 subversion 패키지를 찾아 설치할 준비를 시작한 후 설치할 것인지를 묻습니다.

img1

여기서 [Y]를 입력하면 Ubuntu 리눅스가 필요한 파일들을 다운로드하여 압축을 해제한 후 설치를 마치게 됩니다. 설치 절차가 끝나고 다시 프롬프트가 나타나면 Subversion이 올바르게 설치되었는지 확인하기 위해 아래의 커맨드를 한 번 날려봅니다.

$ whereis subversion

whereis 명령은 지정된 패키지가 설치된 위치를 보여주는 커맨드입니다. 아래와 같은 결과를 볼 수 있다면 성공적으로 설치를 완료한 상태입니다.

subversion: /etc/subversion

2. Creating and configuring Subversion repository

Subversion의 설치를 마쳤으면 이제 새로운 Repository를 생성하고 사용자를 등록해 주어야 합니다. 물론 사용자별로 권한도 설정해 주어야 하겠지요. Windows 운영체제와 마찬가지로 Linux 환경이라 하더라도 사용자들은 별도의 그룹으로 만들어 관리하는 것이 향후에 관리 부담을 줄일 수 있는 방법일 것입니다.

2.1 Creating new account and user group for SVN

우선 다음과 같이 Subversion 사용자를 위한 새로운 사용자 그룹을 생성합니다.

$ sudo groupadd svnusers

이렇게 하면 svnusers라는 이름의 사용자 그룹이 생성됩니다. 이제 이 그룹에 추가할 사용자 계정을 생성해야겠지요? 사용자 계정을 생성하는 명령은 다음과 같습니다.

$ sudo useradd –G svnusers svnuser1 

이 커맨드는 svnuser1이라는 사용자 계정을 생성하고 svnusers 그룹에 추가하는 명령입니다. 이제 사용자 계정에 대한 비밀 번호를 지정하기 위해 다음의 명령을 실행합니다.

$ sudo passwd svnuser1

그러면 아래 그림과 같이 새로운 UNIX 비밀 번호를 입력하라는 프롬프트가 나타나게 됩니다. 비밀 번호를 두 번 입력하면 사용자 계정의 생성까지 완료하게 됩니다.

img2

2.2 Creating new Subversion repository

사용자 계정과 그룹을 생성했으므로 이제 Subversion의 Source Repository를 생성해 보겠습니다. 우선 Source Repository를 생성할 경로를 결정해야 합니다. 저의 경우에는 /opt/svn/project1 이라는 경로를 Subversion의 Source Repository로 사용하려고 합니다. 그러기 위해서는 다음과 같이 우선 이 디렉터리를 모두 생성해 주어야 합니다.

$ sudo mkdir –p /opt/svn/project1   <-- /opt/svn/project1 디렉터리를 만듭니다. 이 때 부모 디렉터리가 존재하지 않으면 함께 생성합니다.

위의 명령을 실행하면 /opt/svn/project1 디렉터리가 생성됩니다. 이 project1이라는 이름의 디렉터리는 실제 프로젝트 명으로 바꾸어서 이름을 지정해도 무방하겠지요? 예를 들면 /opt/svn/myFirstMvcProject 처럼요.

여기까지 실행했다면 이제 Subversion으로 하여금 방금 생성한 디렉터리를 Source Repository로 인식하도록 해야 합니다. 이 경우 다음의 커맨드를 사용합니다.

$ sudo svnadmin create /opt/svn/project1/
$ ls –alt ./project1

svnadmin 명령을 통해 /opt/svn/project1/ 디렉터리에 Source Repository를 설치합니다. 그런 후 ls 명령으로 해당 디렉터리를 살펴보면 몇 개의 디렉터리들이 생성되어 있는 것을 볼 수 있습니다. 이 중 conf 폴더에는 해당 Source Repository에 대한 설정 파일들이 생성되어 있습니다.

2.3 Access control

그러면 conf 폴더에 어떤 파일들이 생성되었는지 살펴볼까요? 기본적으로 Subversion Repository 폴더에는 다음의 세 가지 파일이 생성됩니다.

  • authz: Repository에 접근이 가능한 사용자 목록을 지정하는 설정 파일입니다.
  • passwd: authz 파일에 나열된 사용자들의 비밀 번호를 지정하는 설정 파일입니다.
  • svnserve.conf: 해당 Repository에 대한 사용자 DB와 비밀 번호 DB, 액세스 권한 등을 설정하는 파일입니다.

자, 우선 vi 에디터를 통해 svnserve.conf 파일을 열어봅니다. 그러면 아래와 같은 코드가 보일 것입니다.

#anon-access = read                    <- 익명 사용자에게는 읽기 권한만 부여합니다.
#auth-access = write                   <- 인증된 사용자에게는 읽기 및 쓰기 권한을 부여합니다.

#password-db = passwd               <- 사용자 인증에 필요한 비밀 번호를 저장한 파일로 passwd 파일을 지정합니다.
#authz-db = authz                       <- 인증된 사용자의 권한 목록을 저장한 파일로 authz 파일을 지정합니다.

#realm = My First Repository       <- Repository의 표시 이름을 지정합니다.

위의 코드에서 보듯이 각각의 항목은 모두 주석으로 처리되어 있습니다. 이 값들은 모두 기본 값으로 사용되므로 특별히 손 댈 필요는 없고 주석 처리된 그대로 두어도 무방합니다만 아래와 같이 변경해 보겠습니다. 주석을 제거할 때 줄의 맨 앞에 공백이 없도록 하셔야 합니다.

 

anon-access = read
auth-access = write

password-db = passwd 
authz-db = authz

realm = Project1 Repository

그런 후 password-db 항목에 지정된 passwd 파일과 authz-db 항목에 지정된 authz 파일을 수정하여 접근 가능한 사용자와 각 사용자의 권한을 편집해야 합니다.

우선 passwd 파일을 vi 편집기를 통해 열어보면 아래와 같은 코드가 보일 것입니다.

[users]
# harry = harryssecret
# sally = sallyssecret

미리 준비된 두 사용자는 예제로서 사용법을 보여주기 위한 것입니다. 이 두 라인을 삭제하고 앞서 생성했던 svnuser1 사용자에 대한 비밀 번호를 다음과 같이 지정합니다. 미리 말씀드리지만 이 비밀 번호는 Linux 시스템에 로그인 하기 위한 계정의 비밀 번호가 아니라 Subversion 시스템에 인증을 얻기 위해 사용하는 비밀 번호입니다.

[users]
svnuser1 = svnuser!

이와 같이 svnuser1 사용자의 인증 비밀 번호로 svnuser!를 지정했습니다. 이제 파일을 저장한 후 vi 에디터를 빠져나와 다시 authz 파일을 열어봅니다. 이 파일에는 다음과 같은 코드가 보일 것입니다.

[/foo/bar]
# harry = rw
# &joe = r
# * =

이 역시 예제 코드이며 각각 harry라는 사용자에게는 읽기 권한을, joe라는 사용자 역시 읽기 권한을 부여하며 그 외에 모든 사용자는 권한을 일체 제공하지 않는다는 뜻입니다. 이 파일을 편집하여 다음과 같이 project1 Repository에 대해 svnuser1 사용자에게 읽기 및 쓰기 권한을 부여합니다.

[/]
* =
svnuser1 = rw 

위의 코드는 project1 프로젝트 디렉터리의 루트에 대한 권한 설정을 수행하는 코드입니다. 우선 첫 번째 라인은 일단 모든 사용자에게 아무런 권한을 주지 않겠다는 뜻입니다. 그 이후로는 사용자마다 권한을 지정할 수 있으며 r은 읽기, w는 쓰기 권한을 의미합니다. * = 구문을 이용하여 전체 사용자에게 권한을 할당하지 않는 코드는 항상 맨 위에 있어야 한다는 것에 주의하세요!

2.4 Running Subversion Server

이제 Subversion 서비스를 실행하여 올바르게 동작하는지 확인해 보겠습니다. 다음의 명령을 실행하여 svnserve 도구를 이용하여 Subversion 서비스를 시작합니다.

$ sudo svnserve –d –r /opt/svn/

그런데 이 명령을 이용하면 svnserve가 동작하기는 하지만 Linux 시스템을 재시작하면 다시 수동으로 실행해 주어야 한다는 단점이 있습니다. 이 경우 /etc/rc.local 파일에 svnserve 도구가 실행되도록 스크립트를 작성하면 Linux 시스템을 재시작해도 자동으로 svnserve 도구를 실행할 수 있게 됩니다. 우선 whereis 명령을 통해 svnserve 도구가 설치된 폴더를 알아냅니다. 저의 경우에는 /usr/bin/svnserve에 설치되어 있군요. 따라서 vi 에디터를 통해 /etc/rc.local 파일을 열고 다음과 같이 스크립트를 추가합니다.

# rc.local

/usr/bin/svnserve –d –r /opt/svn/ > /dev/null

exit 0

위의 굵게 표시된 코드를 주석과 exit 0 구문 사이에 넣어주면 Linux 시스템을 재시작할 때 svnserve 도구도 함께 실행됩니다.

2.5 Using Subversion on Windows

이제 Tortoise SVN과 같은 도구를 이용하여 Windows 운영체제 상에서 해당 Repository에 접근해 보겠습니다. Tortoise SVN을 설치한 후 Windows 탐색기에서 마우스 오른쪽 버튼을 클릭하고 [Tortoise SVN > Repo Browser...]  메뉴를 차례대로 선택합니다.

img3

그러면 아래 그림과 같이 Repository Browser가 나타나며 Repository URL의 입력을 요구합니다.

img4

조금 전 생성한 Subversion Repository의 주소를 입력하고 [OK] 버튼을 클릭하면 아래 그림과 같이 사용자를 인증 정보 입력을 요구하는 화면이 나타납니다.

img5

이미 생성해 둔 사용자 계정과 Subversion 비밀 번호를 입력하면 아래 그림과 같이 Repository Browser가 모습을 나타냅니다.

img6

대부분의 경우 Subversion Repository에는 trunk, branches, tags 등의 디렉터리로 구분하여 소스 코드를 관리합니다. 따라서 이 세 가지 폴더를 생성하기 위해 아래 그림과 같이 Repository Browser에서 마우스 오른쪽 버튼을 클릭하고 [Create folder...] 메뉴를 선택합니다.

img7

그러면 생성할 디렉터리 이름을 입력하는 대화 상자가 나타납니다. [trunk]라고 입력하고 [OK] 버튼을 클릭한 후 로그를 남기는 대화 상자에서 역시 [OK] 버튼을 클릭하면 아래 그림과 같이 새로운 폴더가 추가되면서 Revision 값이 1로 증가되는 것을 볼 수 있습니다.

img8

지금까지의 과정을 통해 Linux 시스템에 Subversion을 설치하고 활용할 수 있게 되었습니다. 그러면 Windows 환경에서 사용할 수 있는 Subversion 클라이언트들에 대해 잠깐 소개해 볼까요?

3. SVN Clients for Windows and Visual Studio

Subversion은 이전에 한창 인기를 끌던 CVS를 대체하기 위해 개발된 소스 제어 솔루션으로 CVS의 여러 단점들을 훌륭히 극복하여 많은 사용자 층을 확보하고 있습니다. 덕분에 Linux는 물론 Windows에서도 동작하는 다양한 Subversion 클라이언트 도구들을 손쉽게 찾아볼 수 있습니다.

3.1 Tortoise SVN

이 도구는 Windows 탐색기에 통합되어 Windows 탐색기를 통해 다양한 Subversion 관련 작업을 수행할 수 있는 도구입니다. 아마도 소스 제어 솔루션으로 Subversion을 사용하는 개발자라면 거의 대부분 이미 설치하여 사용 중일 것입니다. 이 도구는 아래 URL을 통해 다운로드하고 설치할 수 있습니다.

Tortoise SVN 홈페이지: http://tortoisesvn.tigris.org/

3.2 Ankh SVN

Ankh SVN은 Visual Studio 2005 및 Visual Studio 2008에 통합되어 동작하는 Subversion 클라이언트 애드온입니다. Collab.net에서 개발하여 무료로 제공되고 있으며 얼마 전 2.1 버전이 새롭게 출시되었습니다. Visual Studio 2003 혹은 그 이전 버전 사용자는 Ankh SVN 1.0.4 버전을 사용할 수 있습니다. 2.1버전은 기존의 2.0 버전에 비해 안정성이 매우 향상된 느낌이에요. 다운로드 링크는 다음과 같습니다.

Ankh SVN 홈페이지: http://ankhsvn.open.collab.net/

3.3 Visual SVN

Visual SVN은 Visual Studio 2003부터 Visual Studio 2008까지 지원하는 Subversion 클라이언트 애드온으로 안타깝게도 유료로 판매되는 제품입니다. 또한 특이한 점은 Tortoise SVN이 반드시 함께 설치가 되어야 한다는 점입니다. 홈페이지에서는 평가판을 다운로드 하여 사용해 볼 수 있습니다.

VisualSVN 홈페이지: http://www.visualsvn.com/

이상으로 Subversion의 설치부터 필요한 환경 설정 및 권한 설정, 실제 사용에 이르기까지 필요한 내용들에 대해 간략하게 살펴보았습니다. 조금 더 상세한 내용을 전달해 드릴 수 있었으면 좋았겠지만 저도 아직은 많이 모자랍니다.

뭐...곧 더 좋아지겠지요? ^^
즐거운 하루 되세요!

Posted by 웹지니 트랙백 0 : 댓글 0