UNIX 철학을 작은 프로그램과 파이프의 집합으로만 보는 관점이 왜 오해인지, 그리고 그 핵심이 조합 가능성에 있다는 점을 살펴본다.
저는 최근 Oxide의 On The Metal 팟캐스트에서 Jonathan Blow가 인터뷰 중 "UNIX 철학"에 관해 한 몇 가지 이야기거리를 두고 친구와 이야기를 나눴습니다. 제공된 대화록의 일부를 여기에 옮겨 두겠습니다. (대화록에는 제가 Jonathan이 말한 것으로 들은 내용과 비교해 몇 가지 오류가 있어서 약간 수정했습니다)
Jonathan 사람들이 저 때문에 화를 낼 만한 돌을 하나 더 던져 보자면, 그런데 이렇게 축소되어야 할 이 모든 복잡성에 관해서는, 모든 것에는 때가 있다고 생각합니다. 그건–
Bryan 세상에, 다음은 뭐죠? 거기서 어디로 가시려는 거죠?
Jonathan 음, 예를 들어 Unix 철학은 어느 정도는 Windows에도 계승되었죠, 운영 체제는 다르지만, 그렇죠? 수많은 작은 프로그램이 있고 그것들을 두 가지 정도의 방식으로 조합한다는 Unix 철학은, 저는 틀렸다고 생각합니다. 오늘날에도 틀렸고 plan nine에도 역시 받아들여졌고 그래서–
Bryan 그건 마이크로서비스죠, 마이크로서비스는 Unix 철학의 한 표현이고, 그래서 Unix 철학에 대해서는 저는 복잡한 감정이 있습니다. Jess, 당신도 그럴 것 같아요. 이를테면, 저는 그걸 좋아해요, 파이프라인을 좋아하고, 제가 임시적인 무언가를 하고 싶을 때, 영구적으로 설계된 것이 아닌 일을 할 때 그게 좋습니다. 왜냐하면 그게 제게 가능하게 해 주니까– 그리고 당신이 아까 Rust와 비디오 게임 이야기를 하면서 왜 그게 빠르게 프로토타입을 만드는 능력 측면에서 어쩌면 맞지 않을 수 있는지를 말했는데, Unix 철학은 임시적 프로토타이핑에는 훌륭하죠.
UNIX란 여러 개의 아주 작은 프로그램이 파이프를 통해 서로 이야기하는 것을 뜻한다는 생각이 있습니다. 어떤 사람들은 더 나아가 이 통신이 반드시 일반 텍스트여야 한다고까지 암시합니다. 이런 분류는 UNIX가 제시한 아이디어에 대한 깊은 오해이며, 그럼에도 불구하고 제 또래의 많은 사람들이 공유하는 입장입니다. 저는 현대 시스템의 맥락에서 UNIX 철학이 언급될 때 사람들이 마이크로서비스를 가리키며 "저것 봐, 마이크로서비스는 UNIX고 마이크로서비스는 형편없어!"라고 말하는 데서 이런 모습을 봅니다.
제 생각에 이것은 설계와 구현을 혼동한 경우입니다. 나무를 보느라 숲을 놓친 셈입니다. 저는 이것이 사람들이 맥락 없이 문구를 인용하고 반복하면서 만트라처럼 받아들였기 때문에 벌어졌다고 생각합니다. 제가 정말 자주 듣는 것은 "한 가지 일을 하고 그것을 잘하는 프로그램을 작성하라"는 말인데, 이는 Peter H. Salus의 "A Quarter-Centurary of Unix"에 귀속됩니다. 또 저는 Bell System Technical Journal에 실린 Doug McIlroy의 글에서 나온 인용도 자주 봅니다. 특히 Doug가 Style이라는 제목의 절 아래에 제시한 글머리표 목록이 그렇습니다.
UNIX 시스템의 독특한 스타일을 설명하고 장려하기 위해 그 구축자들과 사용자들 사이에서 통용되게 된 여러 격언이 있다:
(i) 각 프로그램은 한 가지 일을 잘 하게 만들어라. 새로운 일을 하려면, 기능을 더 추가해 기존 프로그램을 복잡하게 만들기보다 새로 만드는 편이 낫다.
(ii) 모든 프로그램의 출력이 또 다른, 아직 알려지지 않은 프로그램의 입력이 될 것이라고 기대하라. 출력에 불필요한 정보를 뒤섞지 마라. 지나치게 엄격한 열 기반 형식이나 바이너리 입력 형식을 피하라. 대화형 입력을 고집하지 마라.
(iii) 소프트웨어는 운영 체제까지 포함해서, 초기에 시험해 볼 수 있도록 설계하고 만들라. 이상적으로는 몇 주 안에. 서투른 부분을 버리고 다시 만드는 일을 주저하지 마라.
(iv) 프로그래밍 작업을 가볍게 하기 위해 숙련되지 않은 도움보다 도구를 우선하라. 설령 그 도구를 만들기 위해 우회해야 하고 사용을 마친 뒤 그중 일부를 버리게 되리라 예상하더라도 말이다.
이 처음 두 인용문은 너무도 중요하게 여겨져서, "UNIX philosophy"에 대한 Wikipedia 페이지에 가 보면 "Origin" 절에서 가장 먼저 제시되는 두 인용문이 바로 이것들입니다. 겉으로 보면 이것들이 정확히 틀린 말은 아닙니다. 저는 여기서 어느 저자가 틀렸다고 말하려는 것이 아닙니다. UNIX가 구현된 방식 이 이러한 생각을 따르고 있다는 것은 관찰할 수 있습니다. 오류는 사람들이 이러한 구현 세부 사항을 철학 그 자체로 외삽할 때 생깁니다. 사실 이것들은 철학을 특히 운영 체제에 적용했을 때 생기는 부산물에 가깝습니다. Doug가 제시한 처음 두 스타일 규칙은 구체적으로 프로그램 을 함의하는데, 이것은 사실상 주된 일이 그것을 관리하는 운영 체제만이 신경 쓰는 조직 단위입니다. 세 번째 규칙에 오면 조금 더 추상적으로 되는데, 특히 여기서는 프로그램이 아니라 소프트웨어를 언급하기 때문이며, 저 역시 이것은 운영 체제 밖에서도 대체로 좋은 조언이라고 생각합니다. 네 번째 규칙으로 계속 가면, Doug는 다시 프로세스 가 아니라 도구 를 언급합니다. 따라서 이 역시 소프트웨어 전반에 대체로 적용 가능하다는 점에서 저도 동의합니다.
그런데 왜 마지막 두 스타일 규칙은 "UNIX 철학"을 논할 때 그다지 반복되지 않을까요? 왜 사람들은 철학을 운영 체제라는 개념에 그렇게 밀접하게 묶어 두는 지점에서 멈추는 걸까요? 마이크로서비스가 서로 대화하는 작은 프로그램들의 집합이기 때문에 UNIX라고 생각하는 것은 말이 되지 않습니다. 마이크로서비스는 분산 시스템 을 위한 아키텍처이고, 그런 작은 프로그램들이 서로 대화한다는 개념은 운영 체제 를 위한 규칙이기 때문입니다. 사람들이 겪는 마찰은 네모난 못을 둥근 구멍에 억지로 밀어 넣는 데서 옵니다.
조금 더 파고들어 좀 더 추상적인 무언가를 찾을 수 있는지 봅시다. 좋은 출발점은 Brian Kernighan과 Rob Pike의 "The UNIX Programming Environment" 서문입니다. 그중 다음은 발췌문입니다:
UNIX 시스템은 여러 혁신적인 프로그램과 기법을 도입했지만, 그 자체로 잘 작동하게 만드는 단일한 프로그램이나 아이디어는 없다. 대신 그것을 효과적으로 만드는 것은 프로그래밍에 대한 접근법, 즉 컴퓨터를 사용하는 철학이다. 이 철학을 하나의 문장으로 적을 수는 없지만, 그 핵심에는 시스템의 힘이 개별 프로그램 자체보다 프로그램들 사이의 관계에서 더 많이 나온다는 생각이 있다. 많은 UNIX 프로그램은 각각 따로 보면 꽤 사소한 일을 하지만, 다른 프로그램과 결합되면 일반적이고 유용한 도구가 된다.
이 책의 목표는 UNIX 프로그래밍 철학을 전달하는 것이다. 철학은 프로그램들 사이의 관계에 기반하고 있기 때문에, 우리는 대부분의 지면을 개별 도구에 대한 논의에 할애해야 한다. 하지만 그 전반을 관통하는 주제는 프로그램을 결합하는 것, 그리고 프로그램을 사용해 프로그램을 만드는 것이다. UNIX 시스템과 그 구성 요소를 잘 사용하려면, 프로그램을 어떻게 사용하는지만이 아니라, 그것들이 환경 속에서 어떻게 들어맞는지도 이해해야 한다.
그렇다면 여기서 "프로그램"이나 "프로세스" 같은 성가신 운영 체제 특화 용어를 추상화해 내면 무엇이 남을까요? 제가 보기엔 UNIX 철학은 조합 가능성에 관한 것입니다. 저는 프로그램의 크기와 목적보다 다른 것들과 함께 사용될 수 있는 능력이 더 중요하다고 주장하고 싶습니다. UNIX 같은 운영 체제를 사용할 때 기분 좋은 점은 원래 의도되지 않은 방식으로도 코드를 조합할 수 있다는 것입니다. 조각들이 LEGO 블록처럼 맞아떨어집니다.
마이크로서비스 이야기로 돌아가 봅시다. 제가 자주 보는 문제는(그리고 직접 겪기도 한 문제는) 그들 사이를 흐르는 정보가 복잡한 방식으로 흐른다는 점입니다. UNIX 파이프에서는 데이터가 하나에서 다음 것으로 선형적으로 흐르고, 전체 체인이 명령 안에 그대로 드러납니다. 반면 마이크로서비스 설계의 분산 시스템에서는 데이터 흐름이 종종 노드 사이에 여러 간선이 있는 복잡한 그래프처럼 보입니다. Rob과 Brian의 UNIX 설계 정의에 따르면, 마이크로서비스가 실패하는 이유는 조합성이 좋지 않기 때문이라는 점을 우리는 분명히 볼 수 있습니다. 그래프의 한 노드에 새로운 간선이 하나 추가될 때마다 복잡한 의존성과 오버헤드가 따라옵니다.
조합 가능성이 꼭 프로그램들 사이에만 있어야 하는 것은 아닙니다. 예를 들어 게임이나 최종 사용자 프로그램을 만들고 있다면, 조합 가능성은 전체 프로그램을 이루는 개별 라이브러리나 구성 요소들이 서로 잘 맞아떨어진다는 뜻일 수 있습니다. 하나의 단일 프로그램도 UNIX적일 수 있습니다. 그 내부의 다양한 개별 조각들이 잘 맞물린다면 말입니다.
마무리하면서 Ken Thompson과 Dennis Ritchie의 논문 "The UNIX Time-Sharing System"에서, 특히 "Perspective"라는 제목의 절에 있는 발췌문을 인용하고 싶습니다.
UNIX의 설계에 영향을 미친 세 가지 고려 사항은 돌이켜 보면 분명히 드러난다.
첫째, 우리는 프로그래머였기 때문에, 프로그램을 쉽게 작성하고, 시험하고, 실행할 수 있도록 시스템을 자연스럽게 설계했다. 프로그래밍 편의성을 향한 우리의 바람이 가장 중요하게 표현된 방식은, 원래 버전이 단 한 명의 사용자만 지원했음에도 불구하고 시스템이 대화형 사용을 위해 구성되었다는 점이다. 우리는 적절하게 설계된 대화형 시스템이 "배치" 시스템보다 훨씬 더 생산적이고 만족스럽게 사용할 수 있다고 믿는다. 게다가 그러한 시스템은 비대화형 사용으로도 비교적 쉽게 적응시킬 수 있지만, 그 반대는 성립하지 않는다.
둘째, 시스템과 그 소프트웨어에는 언제나 꽤 심한 크기 제약이 있었다. 합리적인 효율성과 표현력이라는, 부분적으로 상충하는 바람이 주어진 상황에서, 크기 제약은 절약뿐 아니라 어떤 설계상의 우아함도 장려했다. 이것은 어쩌면 "고통을 통한 구원" 철학을 얇게 위장한 버전일지도 모르지만, 우리의 경우에는 효과가 있었다.
셋째, 거의 처음부터 이 시스템은 스스로를 유지할 수 있었고, 실제로 그렇게 했다. 이 사실은 겉보기보다 더 중요하다. 어떤 시스템의 설계자들이 그 시스템을 직접 사용해야만 하면, 그들은 그 시스템의 기능적 결함과 표면적 결함을 빠르게 인식하게 되고, 너무 늦기 전에 그것들을 바로잡도록 강하게 동기부여된다. 모든 소스 프로그램은 항상 사용 가능했고 온라인에서 쉽게 수정할 수 있었기 때문에, 우리는 새로운 아이디어가 발명되거나, 발견되거나, 또는 다른 이들에 의해 제안될 때 시스템과 그 소프트웨어를 기꺼이 수정하고 다시 작성했다.