Database Access Layer

일반적으로 여러 DBMS 벤더 별로 플랫폼 또는 언어에 따라 접근할 수 있는 서로 다른 API들을 제공한다. 이들을 한꺼풀 덮어씌워 공통적인 방법으로 접근하도록 해주는 API를 Database Access Layer 또는 Database Abstraction Layer라고 부른다.

JDBC, ODBC, ADO.NET, Perl의 DBI, PHP의 PDO, Pear::MDB2 (Pear::DB, Pear::MDB) 등은 모두 Database Access Layer에 해당하는 것들이다. 각자 특정 플랫폼 또는 프로그래밍 언어에서 표준적인 위치를 가지고 있다. 물론 이외에도 이들과 경쟁하는 API들이 있으며, 이 외의 플랫폼 또는 언어들도 이러한 API를 가지고 있다.

Jeremy Zawodny는 LtU에서도 이슈가 된 모양Database Abstraction Layers Must Die!라는 글에서,

  • 성능을 떨어뜨리고 복잡도를 증가시킨다.
  • 데이터베이스를 쉽게 바꿀 수 있다는 이점은 별로 중요하지 않으며, 실제로는 쉽게 바꿀 수 없다.
  • 데이터베이스 기능을 완전하게 활용할 수 없고, 튜닝도 제대로 할 수 없다.

는 점에서 Database Abstraction Layer를 사용하기 보다는, 데이터베이스에 접근하는 부분을 ‘라이브러리’를 사용해서 모듈화하고, 만일에 하나라도 데이터베이스를 변경할 일이 생긴다면, 이를 수정하면 된다고 설명하고 있다. 그의 주장은 일견 옳다.

이 외에도

하지만, 여러 사람이 지적한대로, 그가 얘기하는 ‘라이브러리’가 바로 Database Abstraction Layer다. 그리고,

  • 성능은 떨어질 수 있지만, scalability도 떨어지는 것은 아니다.
  • 여러 데이터베이스를 지원해야하는 소프트웨어도 존재한다.
  • 하나의 소프트웨어에서 데이터베이스를 바꾸지 않더라도, 프로그래머는 여러 소프트웨어에서 서로 다른 데이터베이스를 접근해야한다.
  • Database Abstraction Layer 라기보다는 Database Access Layer다. 즉, 데이터베이스의 공통적인 기능에는 공통적인 방법으로 접근할 수 있도록 하지만, 특정한 데이터베이스가 지원하는 기능을 접근하도록 만들 수도 있다.

는 면에서 Database Access Layer는 유용하다.

JDBC를 예로 들어보면,

  • 많은 Java 프로그래머들은 특정 데이터베이스 API를 익힐 필요없이 JDBC만을 알아도 데이터베이스에 접근하는 기본적인 프로그램을 짤 수 있다.
  • 특정한 데이터베이스가 지원하는 대부분의 기능은 SQL이라는 불투명한 데이터를 전달하는 API를 통하거나, 설정을 통해 동작방식을 제어하는 방식으로 접근할 수 있다.

‘왜 PHP 개발자들은 Pear::MDB2 또는 PDO를 아직 사용하지 않는가’라고 물어보면 분명 위와 같은 이유로 반대하는 사람들이 많을 것이라고 생각한다. 이 글이 그에 대한 대답이다. 그리고 한가지 더, JDBC, ODBC, ADO.NET, Perl DBI 등의 성공은 결정적으로 Database Access Layer의 유용함을 반영하고 있다. JDBC를 한번이라도 사용해본 사람들이 PHP의 mysql이나 mysqli 모듈로 돌아갈 것이라고 절대 상상할 수 없다.

Database Access Layer 더 읽기"

Dryad

Microsoft의 MapReduce라고 부를 수 있는 DryadGoogle Tech Talks 비디오를 보았다.

MapReduce와의 가장 큰 차이점은 acyclic graph 모델을 사용한다는 것이다. 강연에서는 계속 optimization에 관해 얘기하는데, 내가 가장 궁금한 점은 이러한 프로그래밍 모델이 사용자에게 어떻게 보일 것인가 하는 것이다. 분명 acyclic graph 모델이 유용하게 쓰일 수 있는 경우는 있을테지만, 너무 복잡한 프로그래밍 모델은 현실적으로 많은 프로그래머가 접근하지 못하게 만든다. 게다가 acyclic graph 모델이라는 아이디어 자체는 그리 새로운 것은 아니다. MapReduce 페이퍼에서도 인용하고 있는 Condor가 훨씬 generic한 분산 환경이다.

요즘 팀에서 주로 하는 일이 대용량 데이터 처리다보니 이런 문제들에 대해 많이 고민하게 되는데, 현재는 단순한 파이프라인 (정확히는 Pipeline & Filter) 모델을 분산된 멀티프로세서 환경에 최적화하면서, 동시에 사용자가 쉽게 프로그래밍할 수 있는가를 고민하고 있다. 물론, 후자가 더욱 어려운 일이다.

Dryad 더 읽기"

Delegation

기술적인 논의가 항상 최상의 결론을 내리라는 법은 없으며, 차선의 결론 조차도 낼 수 없는 경우도 있다. 모호하고 복잡한 문제가 아닌, 단순한 기술의 사용 여부와 같은 단순한 문제에서도 서로의 논거가 팽팽히 대립하여 논의가 끝이 나지 않는 경우가 있다. 얼굴을 붉히는 이런 상황에서 흔히 사용되는 해결법은 결정권자-책임자의 결정이다. 서로의 논거가 충분히 합당함에도 불구하고, 서로 논거를 인정하지 않는다는 것은 모종의 불확실성 즉, 리스크가 존재한다는 것이고, 이러한 리스크에 대한 책임을 결정권자가 가져가는 이러한 해결법은 일반적으로 옳다.

하지만, 이러한 논의가 결정권자와 비결정권자 사이의 것이였다면 얘기가 조금 다르다. 끝나지 않는 논의의 해결을 위한 결정권자의 결정이 충분히 합리적이었다고 하더라도, 얼굴을 붉힌 상황에서의 결정권의 행사는 공정하지 못하다는 인상을 주게 마련이다. 그렇다면 결정권자는 어떻게 이 상황을 해결해야할까?

우선, 팽팽하게 대립하는 두 의견은 흔히, 어느 것을 택하더라도 리스크가 크지 않은 경우가 많은 듯하다. 이런 경우에 굳이 결정권자의 강제적인 결정을 통해, 상호 불신의 소용돌이로 빠져들 필요는 없을 듯하다. 리스크로 인한 1 man-day 손실보다 상호 불신으로 인한 1 man-month의 손실이 클 수 있다는 얘기다.

리스크의 간극이 커서, 결정권자의 의견을 선택하는 것이 옳다면, 그것을 충분히 납득시켜야 한다고 생각한다. 얼굴을 붉힌 상태에서 강제적으로 결정하는 것은 너무나 폭력적이다. 만약 정말로 결정권자가 옳고, 상대가 충분한 설명에도 납득하지 못한다면, 그것은 상대방의 문제라고 생각된다.

일단, 상호 불신의 싹이 트기 시작하면, 모든 상황이 나빠진다. 애초에 단순한 논의가 대립하는 것 자체가 어느 정도 상호 불신에 근거하고 있다. 논의 주제에 대한 권위자가 존재하지 않는 상황에서는 더욱 더 심하다. 서로의 경험과 기술적인 숙련도에 대해 파악이 제대로 되지 않은 경우도 많다. 하나의 논의에서 나쁜 감정이 배가된 불신이 생기기 시작하면, 이후의 모든 논의에 있어서 서로의 경험과 논거를 믿을 수 없으므로 – 또는 믿으려하지 않으므로, 서로의 의견을 절대 인정할 수 없다. 심해지면, 경험과 논거를 따지기 전에, 서로의 의도와 감정을 부정적으로 해석하기 시작한다. 가장 심한 것은, 한 배에 탄 다른 사람들까지 한꺼번에 상호 불신의 소용돌이로 끌어들이는 것이다.

반대로, 상호 신뢰란 일의 효율과 성과를 위한 마법과도 같은 것이다. 상호 신뢰에서 나오는 가장 바람직한 행위는 바로 위임이다. 위임이란 결정권자가 자신의 결정권을 조금 나누어주는 것이다. 위임이 어려운 이유는 Chaos와 Control 사이의 밸런스를 맞추는 일이기 때문이다. 결정권자는 Chaos에 이르는 것을 두려워하며, 지식노동자는 Control을 싫어한다. 조직의 특성에 따라 Chaos와 Control 사이의 어느 지점을 선택하느냐가 정해지겠지만, 전통적인 조직들보다 지식노동을 필요로 하는 현대의 조직에서는 좀 더 낮은 Control, 즉 위임이 흔히 강조되곤 한다. 어떤 작은 문제에 있어서는 완벽한 Control이 좀 더 효율적일 수도 있다. 하지만 크게 바라보면, 완벽한 Control을 통해 얻는 조그만 이익은 Control로 인한 손해에 비해 아무것도 아닐 수도 있다. ‘결정권자의 강제적인 결정’얘기도 바로 이러한 맥락이다.

Delegation 더 읽기"

Exception Handling

Rob Walling의 Exception Handling에 관한 글에서 Exception Handling의 두가지 기본적인 규칙이 마음에 들어 인용해본다.

  • Rule #1: If you can’t add helpful information to an error message, don’t catch the exception.
  • Rule #2: If you catch an exception, log it.

실제로 사람들은 너무나 바빠서 또는 게을러서 다음과 같은 짓을 한다.

} catch (YourOwnException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

이클립스의 기본 Exception Handling 코드 템플릿이다. 사람들은 그저 이클립스에 생성해주는 코드를 너무 신뢰하고 있든가, TODO 라인도 지워주기 어려울 만큼 바쁘거나 귀찮은거다.

만약 프로젝트를 시작하고 있다면 이클립스의 Exception Handling 코드 템플릿을 다음과 같이 정해주면 좋을 것이다.

} catch (YourOwnException e) {
logger.error("failed to do SOMETHING", e);
throw new RuntimeException(e);
}

Exception Handling 더 읽기"

On Swapping and Swappiness

swapping의 목적은, 자주 사용되는 디스크 블럭의 디스크 캐쉬와 자주 사용되는 애플리케이션 메모리를, 자주 사용되지 않는 애플리케이션 메모리로부터 확보해, 메모리의 사용률을 높히고, 결과적으로 전체적인 시스템의 성능을 높히는데 있다.

디스크 캐쉬가 필요로 하는 메모리의 크기는 사용하는 디스크 블럭의 크기와 같다. 디스크를 전혀 읽지 않는다면, 페이지 캐쉬는 필요없고, 디스크의 대부분을 사용한다면, 최대한 많은 메모리를 디스크 캐쉬로 이용하고 싶을 것이다.

결국, (애플리케이션이 할당한 메모리의 크기) + (사용하는 디스크 블럭의 크기)를 한정된 메모리 크기 내에서 가장 효율적으로 사용하기 위한 방법이 swapping이다. 이를 뒤집어 말하면, 모든 데이터들을 담을만큼 메모리가 크다면, swapping을 할 필요는 없다는 얘기가 된다.

커널은 페이지 별로 사용 빈도를 알고 있기 때문에, 애플리케이션이 할당한 메모리의 페이지들과 디스크 캐쉬의 페이지들 가운데 어느 것이 덜 사용되는 지를 판단할 수 있으며, 기본적으로 애플리케이션이 할당한 메모리 페이지의 빈도가 낮다면 swapping을 할 것이고, 디스크 캐쉬 페이지의 사용 빈도가 낮다면 이를 해제하고 애플리케이션 메모리로 활용할 것이다.

쓸데없이 메모리만 할당해놓고 전체 디스크를 긁어대는 애플리케이션이나, 메모리 상의 대용량 데이터를 이용한 계산을 하면서 장시간동안 로그를 기록하는 애플리케이션을 상상해보면, 그리 어렵지 않게 잘 동작할 것이라고 추측할 수 있다.

하지만, 문제는 실제로 사용하는 메모리도 많으면서 사용하는 디스크 블럭도 많은 경우 – 대용량 데이터 처리를 수행하는 디스크 바운드 애플리케이션이다. 이 때, 애플리케이션이 할당한 메모리를 swap하게 되면, 다시 필요할 때, 디스크에서 읽어오기 위해 애플리케이션이 느려진다. (Thrashing) 대신 디스크 캐쉬를 줄이면, 역시 디스크의 반응속도도 느려진다. 게다가 swapping은 디스크 I/O도 느리게 만든다. 당연하게도 이러한 애플리케이션이 느려지지 않기를 원한다면, 충분한 메모리를 확보하는 수 밖에는 없다.

Swappiness에 관한 글에서 언급했듯이 리눅스 2.6 커널은 80% 이상의 메모리가 사용 중일 때, swapping을 하기 시작한다. 이러한 동작은 (특히 데스크탑에서의) responsiveness를 보장하기 위해서 이루어지는 것으로 보인다. 부수적으로 페이지 캐쉬의 크기를 보장하는 효과도 있으리라고 보인다. 1GB의 메모리를 가진 머신에서, 20% 즉, 200MB의 페이지 캐쉬를 확보하기 위한 노력이라고 보면 당연해 보이지만, 32GB의 메모리를 가진 머신에서, 위와 같은 애플리케이션을 동작시킨다면, 6.4GB의 페이지 캐쉬를 확보하기 위해 swapping을 하는 것은 비정상적일 수도 있다. 6.4GB의 페이지 캐쉬가 적절한 정도인가는 사용되는 디스크 블럭의 크기와 사용 빈도에 따라 달라질 수 있는 판단이고, swapping이 애플리케이션의 성능에 도움이 되리라는 보장은 없는 것이다. 80%가 디폴트 동작인 것은 중요만 의미가 있는 것은 아닐 듯하고, 메모리의 크기가 커질 수록 더욱 그 의미가 이상해진다.

swapping이 애플리케이션에 해를 끼치는 경우는 극단적으로 애플리케이션이 자체적으로 디스크 캐싱을 하는 경우다. MySQL이 대표적인 경우인데, 자체 캐쉬를 줄이고, 리눅스의 디스크 캐쉬를 활용할 수도 있지만, 자체 캐쉬를 사용할 경우 좀 더 효율적인 메모리 관리를 기대할 수 있다. 그렇다면, MySQL이 자체 캐쉬를 사용할 경우에, 페이지 캐쉬의 크기를 20% 정도로 유지하는 것이 의미가 있을까? 32GB 머신에서 6.4GB를 페이지 캐쉬로 확보해둔 상태에서 swap을 해야할까? (물론 MySQL이 페이지 캐쉬를 사용하지 않도록 설정해도 마찬가지다.)

이러한 경우에 Swappiness를 튜닝할 필요성이 발생하는 것 같다. 실제로 많은 MySQL 튜닝 가이드라인은 Swappiness를 0으로 만들 것을 권장하고 있다. 개인적인 생각으로는 Swappiness를 30-50 정도만 하더라도 애플리케이션이 메모리를 모두 사용하더라도 swap_tendency가 100을 넘지 않아 swap을 시작하지 않으므로 괜찮으리라 생각되지만, distress 등의 변수를 생각하면 실제 상황에서의 실험이 필요할 듯 하다. VM에 관련된 커널 동작은 커널 버전, 심지어는 2.6 내에서도 크게 달라지는 것으로 보인다. /proc/sys/vm/*의 파라미터들도 2.6 내에서 생겼다가 사라지기도 하고, 각각의 동작을 정확하게 정의하기도 어려우며, 그 파라미터들이 어울려서 어떤 동작이 나올 것인가를 예측하기란 더욱 어렵다.

 

결론: 대용량 데이터 처리를 수행하는 디스크 바운드 애플리케이션을 대용량 메모리 상에서 수행할 경우 Swappiness 튜닝에 신경 쓰도록 하자.

On Swapping and Swappiness 더 읽기"

Swappiness in Linux 2.6

Linux 2.6의 swap 경향은 mm/vmscan.c에 의하면 다음과 같이 정의된다.

swap_tendency = mapped_ratio / 2 + distress + sc->swappiness;

이 때, mapped_ratio는 다음과 정의된다.

/*
* The point of this algorithm is to decide when to start
* reclaiming mapped memory instead of just pagecache.  Work out
* how much memory
* is mapped.
*/
mapped_ratio = ((global_page_state(NR_FILE_MAPPED) +
global_page_state(NR_ANON_PAGES)) * 100) /
vm_total_pages;

즉, mapped_ratio는 전체 메모리 크기에 대한 애플리케이션이 사용하고 있는 메모리 크기의 비율이다.

distress는 다음과 같이 정의된다.

/*
* `distress' is a measure of how much trouble we're having
* reclaiming pages.  0 -> no problems.  100 -> great trouble.
*/
distress = 100 >> min(zone->prev_priority, priority);

include/linux/mmzone.h에 따르면, priority는 priority of VM scanning이며, (아마도 free page를 확보하기 위해) 한번에 zone별 page 리스트를 스캐닝 하는 페이지의 수를 얻는데 (queue_length >> priority) 사용되며, 디폴트 값은 12로 한번에 2^12 = 4096개의 페이지를 스캔하는 것을 의미한다. (아마 free page를 확보하지 못해,) priority가 올라갈 수록 (값이 낮아질수록) 많은 페이지를 스캔하게 된다. 2.6 VM을 제대로 공부하지 않아서 이 부분은 확신할 수는 없다.

어쨌든, priority가 디폴트 값인 12일 때, distress값은 100/2^12 = 0이다.

swappiness는 sysctl이나 /proc/sys/vm/swappiness를 통해 사용자가 설정할 수 있는 파라미터이며, 0-100 사이의 값을 갖는다. swappiness의 디폴트 값은 60이다.

/*
* From 0 .. 100.  Higher means more swappy.
*/
int vm_swappiness = 60;

이런 값들로부터 계산된 swap_tendency가 100이 넘으면, mapped memory를 inactive list로 옮기기 시작한다. 다시 말하면, swap을 시작하게 된다.

예를 들어, 2GB의 메모리를 가지고 있는데, 1.8GB의 메모리를 애플리케이션이 사용하고 있고, distress가 0인 상태라면, swap_tendency는 1.8GB * 100 / 2GB / 2 + 0 + 60 = 105가 되어, swap 되기 시작한다.

결국, swap_tendency를 산출하는 식에 따르면, swappiness가 디폴트 값 60인 상태에서는 80% 이상의 메모리를 사용하기 시작하면, swap하기 시작한다. 다음과 같은 방법으로 swappiness 값을 조정함으로써, 이러한 swap behavior를 조정할 수 있다.

sysctl -w vm.swappiness=30

또는,

echo 30 >/proc/sys/vm/swappiness

References

Swappiness in Linux 2.6 더 읽기"

Web Search Engine Startups

우리나라에서도 스타트업들의 웹 검색엔진들이 간혹 나오고 있지만, 검색엔진으로서의 기본적인 품질을 만족하는 것은 매우 드문 것 같다. 블로거스피어를 통해서 잠시 회자된다 하더라도 품질이 뒷받침 되지 않은 유명세는 의미없는 것이다.

검색엔진을 만났을 때 맨처음 해보는 쿼리는 두가지다. 바로 매우 일반적인 표제어에 해당하는 쿼리 – 나의 경우 주로 ‘C++’ – 와 최신성을 반영하는 쿼리 – 오늘 같으면 ‘원더걸스 수능’ 이다. 그리고, 이 간단한 쿼리 두 가지도 무난하게 처리하지 못하는 경우가 대부분이다.

검색 엔진이 해야할 일들은 너무나도 많고, 비용도 많이 들며, 경험도 많이 필요해서, 스타트업이 웹 검색 엔진을 제대로 하기란 정말 어려운 일이라고 생각한다. 이해할 수 있다. 하지만, 그 이상은 아니다. 스타트업의 용기를 칭찬하고 북돋아주는 소수의 블로거들을 제외한 나머지 사용자들은, 웹 검색엔진 스타트업의 사정을 어느 정도 이해하는 소프트웨어 엔지니어들보다는, 훨씬 냉정하다.

Web Search Engine Startups 더 읽기"

Proactive Attitude to Solve Problems

얼마전 회사 워크샵, 아니 플레이샵을 마치고 집으로 또는 회사로 돌아갈 때의 일이었다.

난 회사에 들를 일이 있어서, 회사로 향하는 차에 타기로 했다. 그 때, 팀장님이 내게 프로젝터를 건내며 말했다.

"회사 들를거지? 이거 책임지고 회사에 갖다 놔."

나는 엑스박스도 들고 온터라 이래 저래 짐이 많았다.

"전 짐이 많아서…"

잠시 정적이 흘렀다.

워크샵 운영을 책임진 분이 분주하게 돌아다니다 프로젝터를 받아들어 차에 실으며 말했다.

"회사에 가시는 분들이 이 프로젝터 좀 회사에 가져다 놔 주세요."

아차 싶었다.

내가 짐이 많은 것은 사실이지만, 프로젝터를 두고 가거나 집으로 가져갈 수는 없으니, 어차피 회사로 가져다 둘 짐은 회사로 향하는 차에 실어야 하는 것이고, 또 회사로 향하는 차에 탄 사람이 책임을 져야하는 것이었다.

일을 하다보면 위와 같은 실수를 하는 사람이 비단 나만 있는 것은 아니다.

자신의 역할에 해당하는 문제가 아니라고, 또는 자신이 해결하기에 곤란하다고, 그 문제의 해결을 방관하거나 거절하는 경우가 있다. 하지만, 그 문제는 누군가는 해결해야하는 문제인 경우가 있다. 문제가 주어졌을 때, 그 문제가 자신만의 책임이 아니거나, 자신만의 힘으로 문제 해결이 어려운 상황에 부닥치더라도, 그 문제의 해결 방법을 모색해보는 태도가 프로페셔널에게는 필요하다. 물론, 이 말은 자신이 해결할 수 없는데도 해결할 수 있다고 거짓말을 하라는 얘기가 아니다. 이러한 태도가 있느냐 없느냐가 좀 더 성숙된 프로페셔널이냐 아니냐를 구분하는 잣대 중 하나라고 생각된다. (또한 그러한 태도가 없는 사람들이 답답하기도 하다.)

아마도 그 때 난 다음과 같이 말했더라면 좋았을 것이다.

"아, 제가 짐이 많긴 한데, 같이 가시는 분들이랑 함께 갖다놓도록 하죠."

Proactive Attitude to Solve Problems 더 읽기"

An Understanding

초등학교나 중학교에 다니던 시절은 차라리 행복했다. 그가 아무것도 하지 않아도 그를 좋아하고 곁을 지켜주는 사람들이 있었다.

전조는 아마도 고등학교 시절부터 였는지도 모르겠다. 가벼운 말다툼 후 둘은 서로 사과하지 않았고, 졸업 후에 만나서도, 가벼운 인사치레만 나눌 수 있을 뿐이었다. 그래도, 그는 그 친구를 이해할 수 있었다.

그는 그의 미래에 대해 생각하기도 전에 대학교에 다니게 되었다. 대학교 시절에는 고등학교 친구들도 많았는데, 가장 친한 고등학교 친구 한명에게 실소가 나오는 이유로 주먹을 맞고나서, 그는 그 친구에게 핀잔인 듯한 소리를 했고, 그 친구는 사과했지만, 이후로 그도 그 친구도 원래처럼 대하지 못했다. 그래도, 그는 그 친구를 이해할 수 있었다.

그래도 대학시절 동안 그에게는 어울려 술마시고 함께 어깨동무를 하고 노래부를 대학 친구들이 있었다. 하지만, 대학을 졸업하고나자, 그 중 가장 친한 친구는 졸업 후에 점점 바빠지더니, 가벼운 부탁조차도 들어주지 않는 사이가 되었다. 그에게는 그저 가벼운 부탁이란 건 없었는데도 말이다. 그래도, 그는 그 친구를 이해할 수 있었다.

그에게는 특별한 친구 한명이 있었다. 그 친구는 그에게 지적인 자극을 주었으며, 음악의 기쁨을 함께 해주었다. 하지만, 그런 즐거움은 순간일 뿐이었고, 그 친구는 떠나갔다. 그 친구는 필요할 때마다 그를 찾았지만, 역시 자신이 필요할 때마다 떠나갔다. 그래도, 그는 그 친구를 이해할 수 있었다.

그는 직장을 가지게 되었다. 대학 친구들 중 몇몇은 그와 같은 일을 하게 되었고, 전문적인 의견을 종종 나누는 친구들도 생겼다. 하지만 그 친구들은 전문적인 의견을 물어보기 위해서 그와 이야기했고, 그가 뭔가 이야기 하고 싶다고 하면 바쁘다고 했다. 그래도, 그는 그 친구를 이해할 수 있었다.

그는 일을 하다가 좋은 동료들을 만났다. 동료들은 마치 친구처럼 그에게 관심을 보여주었고, 그도 그들에게 친구와 같이 마음의 문을 열어주었다. 하지만 그런 동료들도 다른 직장으로 옮기게 되어, 더이상 동료가 아니게 되자, 더이상 그에게 관심을 보이지 않게 되었다. 그래도, 그는 그 친구를 이해할 수 있었다.

그는 그가 말을 걸 수 있는 사람들이 별로 남지않게되자, 네트웍으로 사람들과 이야기하기 시작했다. 네트웍에는 많은 사람들이 있었고, 그들 중 몇몇은 그와 관심사도 생각도 비슷해서 친구처럼 지낼 수 있었다. 하지만, 그와 그들 사이에는 뭔가 친해지기 어려운 벽 같은 것이 있었다. 그들과 조금 친해졌다 싶어도, 자고 일어나면 그 벽은 다시 자라나있었다. 그러다보면 그들은 저절로 그로부터 멀어져갔다. 그래도, 그는 그 친구를 이해할 수 있었다.

그의 주위에 친구라고 할 만한 사람들이 거의 없어지자, 그는 외로웠다. 그에게는 삶의 목적이 있었고 삶에의 열망도 있었지만, 외로운 삶 자체는 너무 고통스러웠다. 그래서, 그는 자살하기로 마음먹었다. 하지만, 삶에의 열망이 매듭을 묶는 그의 손을 배반하여 그 자살은 실패로 돌아갔다. 그는 정신과 의사를 마주하게 되었다. 의사는 정말 그를 이해한 것 같았고, 그가 살겠다는 의지만 있다면, 그를 돕겠다고 약속했다. 그는 성실하게 의사와 정기적으로 면담을 했고 약도 빠뜨리지 않고 먹었다. 의사 친구가 웃으며 더이상 병원에 오지않아도 된다고 얘기했다. 그 날 이후로 의사는 그에게 아무것도 요구하지 않았고, 그가 요구를 해도 아무것도 받아주지 않을 것 같았다. 그래도, 그는 그 친구를 이해할 수 있었다.

그는 일과 친구가 되기로 했다. 그 친구는 말은 없었지만 솔직했고 늘 그의 곁에 있었다. 그는 직장에서 인정을 받았고, 직장 내의 누구나 그를 칭찬했다. 이윽고 그는 은퇴할 때가 되었고, 일이란 친구는 더이상 그가 필요하지 않다고 털어놓았다. 그래도, 그는 그 친구를 이해할 수 있었다.

그는 병에 걸렸다. 그는 잠시 생각했다. 이 병이 내 새로운 친구가 되어줄 수 있을까. 그는 알 수 있었다. 이 친구도 언젠가 소리없이 나를 떠나가겠지. 병은 그의 생각을 아는지 모르는지 그를 점점 장악하여 쇠약하게 만들고 있었다. 그래도, 그는 그 친구를 이해할 수 있었다.

병과 싸우는 일은 고통스러웠지만, 의외로 끝은 빨리 찾아왔다. 그와 친구가 되고 싶지는 않다는 투로 의사가 그에게는 이제 몇달의 시간밖에 없다는 것을 알려주었다. 삶도 나를 떠나가는구나. 그는 심한 배신감을 느꼈다. 그래도, 그는 그 친구를 이해할 수 있었다.

그는 그가 죽어가고 있다는 것을 알 수 있었다. 마지막 호흡이 잦아오는 동안 그는 기뻐했다. 죽음이 그의 새로운 친구이며, 그 친구는 그를 절대 배신하지 않을 것임을 깨달았다. 그는 후회했다. 그가 죽음과 친구가 될 수 있었을 때, 왜 삶을 한번 더 믿었던가를. 그가 눈을 감을 때, 그 친구는 속삭였다. 그가 삶을 살지 않았더라면, 나라는 친구가 얼마나 좋은 친구인지를 깨닫지 못했을거라고. 그제서야, 그는 그 친구를 이해할 수 있었다.

An Understanding 더 읽기"