버퍼 캐쉬

디스크를 읽는 일은 (진짜) 메모리를 읽는 것보다 아주 느리다. [1] 더구나, 디스크의 동일한 영역을 짧은 시간 동안 반복해서 계속 읽는 일은 아주 빈번하다. 예를 들어, 누군가 e-mail 메시지를 읽고, 답장을 하기 위해 편집기로 불러들이고, 그걸 보내기 위해 메일프로그램에게 다시 읽게 하는 과정을 생각해 보자. 또한 ls 명령어 같은 것을 시스템의 모든 사용자들이 얼마나 자주 사용할지 생각해 보자. 따라서, 디스크로부터 한번 읽어들인 정보를 메모리에 상당시간 보관한다면, 첫번째로 읽을 때만 시간이 좀 걸릴 뿐 속도가 전반적으로 빨라질 것이다. 바로 이런 것을 가리켜 디스크 버퍼링(disk buffering)이라고 하며, 이런 목적으로 쓰이는 메모리를 버퍼 캐쉬(buffer cache)라고 부른다.

그러나 메모리는 아쉽게도 한정된, 아니, 아주 귀중한 자원이기 때문에, 버퍼 캐쉬는 보통 큰 크기를 가질 수 없다(즉, 우리에게 필요한 모든 데이터를 담아둘 수 있을 정도로 크지는 않다). 따라서, 캐쉬가 다 차게 되면 오랫동안 쓰이지 않은 데이터는 버려지며 그 빈 공간을 새로운 데이터가 메우게 된다.

이런 디스크 버퍼링은 쓰기에도 똑같이 적용된다. 보통, 데이터들은 쓰여지자 마자 또 곧바로 다시 읽어들여지므로(예를 들어, 소스 코드 파일은 일단 파일로 저장된 후, 컴파일러에 의해 다시 읽어들여진다), 이런 데이터들을 캐쉬에 넣어둔다면 확실히 효율적일 것이다. 또한, 쓰기 작업을 디스크에 즉시 하지 않고 캐쉬에 넣어두면, 프로그램들이 그만큼 출력을 빨리 끝낼 수 있기 때문에 전반적인 시스템 성능향상에도 도움이 된다.

대부분의 운영체제들이 버퍼 캐쉬를 갖고 있긴 하지만(좀 다른 이름으로 불릴 수도 있다), 모두가 위와 같은 원리로 동작하는 것은 아니다. 한가지 방법은 write-through라는 것인데, 이 방법은 쓰기를 할 때면 언제나 디스크에도 즉시 기록하는 것이다(물론 캐쉬에도 남겨둔다). 또 다른 방법은 write-back이라 불리는 것으로, 쓰기를 일단 캐쉬에 해 두었다가 나중에 한꺼번에 디스크에 기록하는 방식이다. 효율적이기는 write-back 방식이 뛰어나지만, 대신 약간의 에러가 발생할 소지가 있다. 즉, 시스템이 갑자기 멈춰버린다거나, 전원이 갑자기 나가버린다면, 또는 캐쉬 내용을 미처 써 넣기 전에 플로피를 빼 버린다면, 캐쉬에 담겨 있던 내용들은 고스란히 날아가 버리고 만다. 특히, 손실된 정보가 파일시스템 유지에 필요한 중요 데이터였다면, 자칫 전체 파일시스템을 망가뜨리고 마는 결과를 초래할 수도 있다.

이렇기 때문에, 컴퓨터를 끄기 전엔 반드시 적절한 셧다운 절차를 밟아야만 하는 것이고(Chapter 6 참조), 마운트한 플로피를 빼기 전엔 꼭 언마운트를 해야하는 것이다. 한편, 캐쉬를 디스크로 내보내기(flush) 위한 명령으로 sync가 있는데, 이 명령을 쓰면 아직 기록되지 않고 캐쉬에 남아있는 데이터들을 모두 디스크에 써넣게 되므로, 모든 내용이 안전하게 기록되었다는 점을 보장받을 수가 있다. 또한 전통적인 UNIX에는 update란 백그라운드 프로그램이 있어서, sync가 해주는 것과 같은 일을 30초에 한번씩 자동으로 해준다. 그러므로 사실 sync를 별로 사용할 필요는 없는 없는 셈이다. 특히, 리눅스에는 추가적인 데몬으로 bdflush란 것이 있는데, 이 것은 sync에 비해선 상당히 불충분하게 flush 작업을 하지만 대신 좀더 자주 실행하도록 되어 있다. 이런 방식이 고안된 이유는, sync가 디스크 입출력을 순간적으로 과도하게 일으키면서 시스템이 멈춰버리는 현상이 종종 있어 왔기 때문이다.

리눅스에서는, update에 의해 bdflush가 구동된다. 보통 때는 이 데몬들에 별로 신경 쓸 필요가 없지만, 만일 bdflush가 어떤 이유로 죽어버린다면 커널이 이 사실을 바로 알려줄 것이다. 이럴 때는 수동으로 실행시켜 주면 된다(/sbin/update).

그런데, 사실 캐쉬는 파일을 버퍼링하는 것은 아니고, 실제로는 디스크 입출력의 가장 작은 단위인 블록을 버퍼링한다(리눅스에서는 보통 1KB 크기이다). 그렇기 때문에, 디렉토리라든가, 수퍼 블록들, 다른 파일시스템의 유지 데이터, 심지어 파일시스템이 없는 디스크까지도 캐쉬될 수가 있는 것이다.

캐쉬의 효율성은 기본적으로 그 크기에 좌우된다. 캐쉬의 크기가 너무 작으면, 다른 데이터를 캐쉬하기 위해서 캐쉬된 데이터를 계속 내보내야 하므로, 사실상 작은 캐쉬는 별 쓸모가 없는 셈이다. 캐쉬가 어느 정도 쓸모있기 위한 최소한의 크기는, 얼마나 많은 데이터가 읽고 씌여지는지와, 같은 데이터가 얼마나 자주 액세스되는지에 달려있는데, 이것을 알아보기 위한 단 하나의 방법은 그저 실험해보는 것 뿐이다.

만일 캐쉬의 크기가 고정되어 있다면, 그 크기가 너무 큰 것도 곤란한 일일 것이다. 캐쉬가 너무 크면 여유 메모리는 그만큼 줄어들 것이고, 많은 스와핑을 일으켜서 시스템은 느려지게 된다. 리눅스는 자동적으로 모든 RAM의 빈공간을 버퍼 캐쉬로 사용하여 메모리의 효율성을 높이려 하는데, 프로그램들이 많은 메모리를 필요로 할 때는 자동적으로 캐쉬를 크기를 줄여 준다.

그래서, 리눅스에서는 캐쉬를 사용하는 데 대해서 아무것도 신경쓸 필요가 없다. 완벽하게 자동적이기 때문이다. 다만, 셧다운 할 때와 플로피를 빼낼 때의 절차는 꼭 지켜 주어야 한다. 이것만 빼면, 걱정할 것은 하나도 없다.

Notes

[1]

RAM 디스크의 경우는 제외이다. 이것은 RAM에 만들어진 가상의 디스크이므로, 당연히 램만큼이나 속도가 빠르다.