Skip to content

필사 모드: 타입 검사기를 통과한 두 서비스가 서로를 기다리며 멈출 때 — 코레오그래피라는 다른 접근

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

두 서비스가 서로를 기다리며 멈춥니다

메모리 안전한 언어로 서비스를 두 개 만들었습니다. 양쪽 다 컴파일이 깔끔하게 통과합니다. 널 참조도, 데이터 경쟁도, 해제 후 사용도 없습니다.

그런데 배포하고 나면 가끔 둘이 같이 멈춥니다. 서버는 클라이언트가 보내기로 한 두 번째 메시지를 기다리고 있고, 클라이언트는 서버가 먼저 응답할 것이라고 알고 기다리고 있습니다. 프로토콜이 어긋난 겁니다.

이 버그의 특징은 어느 쪽 코드에도 잘못이 없다는 점입니다. 각각을 따로 읽으면 둘 다 합리적입니다. 잘못은 둘 사이에 있고, 어느 컴파일러도 그 사이를 보지 않습니다.

문제의 뿌리는 코드가 둘로 나뉘어 있다는 사실입니다

타입 시스템이 지켜 주는 경계가 어디까지인지 생각해 보면 이유가 분명합니다. 타입 검사기는 하나의 컴파일 단위 안에서 값의 흐름을 봅니다. 그 단위 바깥으로 나간 순간, 즉 바이트가 소켓에 실린 순간부터는 아무것도 보장하지 못합니다. 반대편에서 그 바이트를 어떤 순서로 기대하는지는 검사기가 알 수 없는 정보입니다.

그래서 우리는 그 사이를 사람의 규율로 메웁니다. 문서에 시퀀스 다이어그램을 그리고, 스키마를 공유하고, 통합 테스트를 씁니다. 셋 다 유용하지만 셋 다 검사가 아니라 관행입니다. 한쪽 코드를 고치면서 다이어그램을 같이 고치지 않아도 컴파일은 통과합니다.

이 지점에 다른 답을 내는 계열이 있습니다. 사이를 검사하지 말고, 애초에 둘로 나누지 말자는 것입니다.

코레오그래피 — 시스템 전체를 한 프로그램으로 쓰기

코레오그래피 프로그래밍은 분산 시스템을 프로세스별로 따로 작성하는 대신 상호작용 전체를 하나의 전역 기술로 씁니다. 클라이언트가 이걸 보내고, 서버가 저걸 계산해서 되돌려 주고, 클라이언트가 그것을 받는다는 흐름이 하나의 프로그램 안에서 순서대로 읽힙니다.

이 이름은 Fabrizio Montesi의 2013년 박사 논문에서 붙었고, 이후로 여러 언어와 라이브러리가 이 아이디어를 구현해 왔습니다. 개념의 핵심은 관점 전환입니다. 지금 우리가 쓰는 코드는 한 노드의 시점에서 쓰입니다. 코레오그래피는 시스템 바깥에서 내려다보는 시점에서 씁니다. 시퀀스 다이어그램이 실행 가능해진 것이라고 생각하면 대체로 맞습니다.

엔드포인트 프로젝션이 하는 일

전역 기술을 그대로 실행할 수는 없습니다. 실제로 도는 것은 각 노드의 프로세스이기 때문입니다. 그래서 컴파일 단계에 절차가 하나 추가됩니다. 엔드포인트 프로젝션입니다.

프로젝션은 하나의 코레오그래피에서 노드별 코드를 기계적으로 뽑아냅니다. 클라이언트용 바이너리에는 클라이언트가 해야 할 보내기와 받기와 계산만 남고, 서버용 바이너리에도 마찬가지입니다. 두 결과물은 서로를 알지 못하지만, 같은 원본에서 나왔기 때문에 순서가 어긋날 수 없습니다.

여기서 중요한 것은 우리가 늘 손으로 해 오던 일이 컴파일러의 일이 되었다는 점입니다. 지금까지 클라이언트 코드와 서버 코드를 맞추는 작업은 사람이 다이어그램을 보고 양쪽에 옮겨 적는 일이었습니다. 옮겨 적는 두 번의 작업이 하나의 자동 변환으로 바뀝니다.

왜 데드락이 설계상 사라지는가

여기서 오해하기 쉬운 부분을 짚겠습니다. 데드락이 사라지는 것은 컴파일러가 똑똑해서가 아닙니다. 그런 프로그램을 쓸 수가 없어서입니다.

전역 기술에서 통신은 하나의 구문으로 표현됩니다. 누가 무엇을 누구에게 보낸다는 한 줄이 보내기와 받기를 동시에 낳습니다. 보내기만 있고 받기가 없는 상황, 또는 양쪽이 서로 받기부터 하는 상황을 소스 코드로 적을 방법이 없습니다. 그러니 프로젝션 결과에도 그런 짝이 나올 수 없습니다.

이 성질은 Carbone과 Montesi의 2013년 작업 이후 설계상 데드락 없음이라는 표현으로 알려져 있습니다. 다만 범위를 정확히 알아 둘 필요가 있습니다. 이 보장은 프로토콜의 어긋남에서 오는 데드락에 관한 것입니다. 노드가 죽거나, 네트워크가 끊기거나, 무한 루프에 빠지거나, 외부 자원에서 잠금이 걸리는 상황은 별개의 문제입니다.

Wyzer가 여기에 얹은 것

최근 공개된 Wyzer는 이 아이디어를 시스템 프로그래밍 쪽으로 가져오려는 시도입니다. README는 이 언어를 정적 타입에 컴파일되는 자원 지향 언어로 소개하고, 코레오그래피와 Perceus 메모리 모델을 결합했다고 밝힙니다.

문법에서 눈에 띄는 것은 타입에 노드가 붙는다는 점입니다.

role @Client;
role @Server;

fn fetch_data(query: str@Client) -> str@Client {
    // 클라이언트가 서버로 query 를 넘긴다
    let server_query: str@Server = query;

    // 서버가 처리한다
    let server_result: str@Server = db_lookup(server_query);

    // 서버가 결과를 클라이언트로 돌려준다
    let client_result: str@Client = server_result;

    return client_result;
}

여기서 네트워크 전송은 별도의 함수 호출이 아니라 다른 노드 타입으로의 대입입니다. 그리고 이 언어는 그 대입을 선형 이동으로 취급합니다. 즉 넘긴 값은 원래 자리에서 소멸합니다. README가 싣고 있는 오류 메시지가 그 결과입니다. 옮긴 뒤에 다시 쓰면 이동한 변수를 사용했다는 컴파일 오류가 납니다.

이 설계가 겨냥하는 지점이 분명합니다. 분산 시스템에서 흔한 버그 하나가 이미 보낸 데이터를 로컬에서도 계속 최신인 양 쓰는 것인데, 전송을 이동으로 만들면 그 실수가 타입 오류가 됩니다.

빌림 검사기 없이 제자리 갱신을 얻는 쪽

메모리 쪽 선택도 함께 볼 만합니다. Wyzer는 가비지 컬렉터도 빌림 검사기도 두지 않고 Perceus를 택했다고 밝힙니다.

Perceus는 2021년 PLDI에서 발표된 기법으로, 참조 카운트 증감 명령을 컴파일러가 정밀하게 삽입해 순환이 없는 프로그램에서 쓰레기가 남지 않게 합니다. 여기에 재사용 분석이 얹히면, 어떤 값의 참조가 하나뿐일 때 새로 할당하는 대신 기존 메모리를 그대로 고쳐 쓸 수 있습니다. 함수형 스타일로 쓴 코드가 제자리 갱신으로 컴파일되는 이 방식을 FBIP라고 부릅니다. Koka 언어에 구현되어 있습니다.

왜 이 조합이 자연스러운지도 짚어 둘 만합니다. 코레오그래피에서 값이 노드 사이를 이동한다는 것은 소유권이 옮겨진다는 뜻이고, 소유권을 세는 일은 참조 카운트가 이미 하고 있는 일입니다. 메모리와 네트워크에 같은 규칙을 쓴다는 README의 설계 원칙은 여기서 나옵니다. 다만 이것은 언어가 내세우는 설계 의도이고, 실제로 그 규칙이 두 영역 모두에서 잘 동작하는지는 구현이 나온 뒤에 판단할 문제입니다.

이 언어의 현재 상태를 있는 그대로 적으면

성숙도를 과장하지 않기 위해 확인한 것만 적습니다. 저장소는 Apache-2.0 라이선스이고 주 언어는 OCaml입니다. 확인한 시점에 릴리스가 하나도 올라와 있지 않습니다. 그리고 저자 본인이 공개 글에서 다섯 달의 연구와 몇 주의 개발을 거쳤으며 곧 0.1.0을 낼 예정이라고 밝혔습니다. 즉 첫 번째 버전이 아직 나오지 않은 상태입니다.

그러니 이 언어로 무언가를 만들 계획을 세울 단계가 아닙니다. 그럼에도 읽어 볼 값이 있다고 보는 이유는, 이 프로젝트가 왜 만들었는지를 문제로 서술하기 때문입니다. 저자는 Rust에 대한 불만에서 출발했다고 밝히면서, 타입 검사가 메모리는 지켜 주지만 분산 데드락과 서비스 간 프로토콜 불일치는 지켜 주지 못한다는 점을 이유로 듭니다. 이 진단은 언어의 완성도와 무관하게 맞습니다. 그리고 그 진단에 대한 답으로 십 년 넘게 쌓인 학술적 계보가 존재한다는 사실이, 이 프로젝트를 보고 얻을 수 있는 가장 실용적인 정보입니다.

정리와 출처

우리가 서비스 간 프로토콜을 문서와 테스트로 맞추고 있다면, 그것은 도구가 없어서가 아니라 우리가 쓰는 언어가 노드 하나만 보기 때문입니다. 코레오그래피는 그 시야를 바꾸는 접근이고, 시야를 바꾸면 일부 버그는 고칠 대상이 아니라 표현할 수 없는 것이 됩니다.

  • Choreographic Programming — Fabrizio Montesi 본인의 정의. 용어가 2013년 박사 논문에서 나왔다는 점, 엔드포인트 프로젝션의 역할, 설계상 데드락 없음이 Carbone과 Montesi의 2013년 작업으로 알려졌다는 점을 이 문서에서 확인했습니다.
  • Wyzer-Lang/wyzer 저장소 — 문법 예시, 설계 원칙, 오류 메시지 형태. 본문의 Wyzer 관련 서술과 코드는 이 저장소 README에서 가져온 것입니다.
  • 저장소 메타데이터로 확인한 사항: 라이선스 Apache-2.0, 언어 OCaml, 릴리스 없음. 0.1.0을 아직 내지 않았다는 서술은 저자가 공개한 소개 글의 내용입니다.
  • Perceus: Garbage Free Reference Counting with Reuse — Reinking, Xie, de Moura, Leijen, PLDI 2021. FBIP와 재사용 분석의 출처입니다.
  • 저는 Wyzer 컴파일러를 빌드하거나 실행해 보지 않았습니다. 본문의 코드는 README에 실린 예시이며, 동작을 직접 확인한 것이 아닙니다.

현재 단락 (1/40)

메모리 안전한 언어로 서비스를 두 개 만들었습니다. 양쪽 다 컴파일이 깔끔하게 통과합니다. 널 참조도, 데이터 경쟁도, 해제 후 사용도 없습니다.

작성 글자: 0원문 글자: 3,797작성 단락: 0/40