오픈 소스 오픈
추상화의 세계
우리는 추상화에 매우 익숙하다. 예를들면 우리는 HTTP를 직접 쓰지 않는다. Node.js로 서버를 구축한다면, 일반적으로 http 모듈을 직접 쓰지 않고, http 모듈 또한 OS에서 추상화된 소켓, 프로토콜, 그리고 네트워크까지 파고 팔 수 있다.
보통 내가 만드는 프로그램의 상위 레벨의 추상화만 어렴풋이 보인다. http 모듈 정도는 보겠지만, OS 소켓까지 너무 먼 이야기이다. 교과서나 교재로 보더라도 나와 거리가 너무 멀어서 와닿지 않을 것이다.
LLM 시대, 왜 기술 부채를 해결해야 할까
그럼에도 불구하고 왜 추상화된 영역을 파헤쳐 봐야할까? 오히려 기술을 자유롭게 사용, 선택하기 위함이다. 대용량을 운영하기 위해서 기술 부채 해결은 필수적이기도 하다.
일반적인 개발, 비즈니스 환경에서 우리는 가치에 집중한다. LLM으로 코드를 생성하는 시대에 오히려 더 중요하다고 생각한다. 콘텍스트를 머릿속의 추론능력으로 최초의 인풋을 만들어야 한다.
과거 면접관으로 참여하면서 LLM 면접을 몇 번 진행 해봤다. 아주 작은 요구사항부터 시작해서 조금씩 문제를 확장해 나가면서 사용하는 능력을 보았다. 그런데 정말로 공통적으로 보이는 희한한 점이 있었는데, 나도 자주 겪는 문제이다. LLM은 적은 입력으로, 많은 출력을 하는데, 그 출력 결과 중에 정말 중요한 포인트가 가끔씩 들어있다. 그런데 사람은 딱 내가 아는 수준만 받아들인다. 성장하기 위해서는, 내가 모르는 부분을 식별하고 그것에 초점을 맞추어야 하는데 역설적으로 아는 것만 해석하는 현상이었다.
평소의 나라면 안할 것 같은 일
생산량이 높기 때문에 가능한 정책이지만 가끔 팀 내 정책으로 연구할 시간이 부여된다.
아이패드 교보도서관 앱으로 우연히 리눅스 이북을 봤는데, 리눅스 책의 내용이 수 년 전에 보던 CS 책들과는 전혀 다르게 다가왔다. 맞다. 과거 OS 책 내용은 그저 글자였을 뿐, 혹은 외워야 할 대상이었을 뿐, 진정한 의미를 이해하지 못했다. 수많은 모르는 단어, 단어 하나 하나가 사실 중요한 주제인데 고구마 줄기처럼 익혀야 할 지식이 너무 많아서 지쳐서 포기하거나, 길을 잃고 대충 넘어갔었다.
지난 3년 동안 클라우드 네이티브 환경과 연동된, 전국 600 곳이 넘는 엣지 컴퓨팅 환경에서 1,000대가 넘는 엣지 이하 장비에 서비스를 올려 운영하다보니 리눅스의 여러가지 디자인의 의도가 너무도 와닿는 것이었다.
커널.. 커널은 왜 있을까에 대한 내용이 책 초반에 있는데 복잡한 대상 (입력, 출력, 저장 장치 등)에 대한 접근 제어를 하나의 역할과 책임으로 단일화한 것이다. 동일한 문제가, 모습만 다를 뿐 서버, 서비스 전반에도 동시성이나 키오스크의 장비제어 등에서도 동일하게 나타나는 문제였다.
서버, 즉 네트워크 프로그램를 꽤 많이 만들고 운영하고 있는데, 이렇게까지 깊게 생각해보진 않았다. 오케스트레이션 환경, 또는 EC2 하나를 띄우더라도 상위 물리적인 환경으로부터 할당받는 논리 CPU를 내 프로그램은 어떤 구조로 할당 받고 사용할까도 추적해봤다.
물리 CPU (소켓)
└─ 물리 코어
└─ 하드웨어 스레드 (SMT/Hyper-Threading)
└─ 논리 CPU (커널이 보는 실행 슬롯)
└─ 커널 스케줄러 (CFS 등)
└─ 프로세스 (주소 공간)
└─ 커널 스레드 (실행 단위, context 포함)
└─ 언어 런타임 (JVM, Go runtime 등)
└─ 사용자 스레드 (Java Thread, Goroutine 등)
그럼, 프로세스는 어떻게? 그냥 책을 보지 않고, 안하던 짓을 해봤다. 그냥 커널 소스를 보자. 도커로 우분투 이미지를 받았는데, linux-source 패키지를 설치해야 한다. 왜? 가상OS 환경은 호스트 머신 (내 맥스튜디오)의 커널을 그대로 공유하고, 경량화되었기 때문에 굳이 필요가 없다.
우분투 소스로 들어가보니, 익숙한 상태가 보인다. 16진수 키로 구성된 상태가 정의되어있다.
State Machine이었다. 습관처럼 입력하던 ps aux
의 Stat이었다.
/* Used in tsk->state: */
#define TASK_RUNNING 0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
...
시스템콜이 들어오면 프로세스를 만드는데, kernel_clone로 호스트의 메모리, 파일 디스크립터를 복제, 초기화해준다. 이렇게 생성된 프로세스를 스케줄러가 하드웨어 기반의 타이머 1ms마다 상태를 체크한다. 스테이트머신을 이용해 상태를 변경한다.
이렇게 계속 보았다........
결론
내가 리눅스 오픈소스 개발할 건 아니라서 심각하게 큰 의미는 없는데, 일단 시도 자체에 셀프 칭찬을 해야겠다. 제일 복잡한 OS를 봤으니, 다른 오픈소스는 더 편하게 다가오지 않을까한다. 그래도 잘 모르는 문법을 LLM 덕분에 쉽게 이해할 수 있었다.
아웃풋은 너무나 쉽게 접근할 수 있는데, 결국 "인풋" 문제였다. 어떤 것을 입력하느냐, 앞선 사건이 뒤에 있을 대부분을 결정 해버리는데, 시도해볼 생각조차 안하는 내 관념이 곧 내 한계였다. 마법이 아니었다. 말 그대로 "오픈" 소스인데, 오픈해 볼 생각도 하지 않았다. 뭔가 너무 복잡해보이고, 어려워보이는 무언가의 실체를 보면서 무의식적으로 두려워하던 한계를 깨는 데 의의를 둔다.