
컴퓨터의 탄생: 앨런 튜링과 튜링 머신
단순한 계산기가 어떻게 논리적인 사고를 하게 되었을까? 튜링 머신이 알려주는 컴퓨터의 본질과, 이것이 내 코드의 상태 관리와 어떻게 연결되는지 정리했습니다.

단순한 계산기가 어떻게 논리적인 사고를 하게 되었을까? 튜링 머신이 알려주는 컴퓨터의 본질과, 이것이 내 코드의 상태 관리와 어떻게 연결되는지 정리했습니다.
내 서버는 왜 걸핏하면 뻗을까? OS가 한정된 메모리를 쪼개 쓰는 처절한 사투. 단편화(Fragmentation)와의 전쟁.

미로를 탈출하는 두 가지 방법. 넓게 퍼져나갈 것인가(BFS), 한 우물만 팔 것인가(DFS). 최단 경로는 누가 찾을까?

이름부터 빠릅니다. 피벗(Pivot)을 기준으로 나누고 또 나누는 분할 정복 알고리즘. 왜 최악엔 느린데도 가장 많이 쓰일까요?

매번 3-Way Handshake 하느라 지쳤나요? 한 번 맺은 인연(TCP 연결)을 소중히 유지하는 법. HTTP 최적화의 기본.

처음 코딩을 배우고 서비스를 만들기 시작했을 때, 저는 컴퓨터를 그저 '엄청나게 빠른 계산기'라고만 생각했습니다. 복잡한 엑셀 수식을 순식간에 처리해주고, 내가 시키는 반복 작업을 군말 없이 수행하는 아주 성실한 일꾼 정도로 여겼죠.
하지만 서비스를 직접 운영하고, 더 복잡한 비즈니스 로직을 짜다 보니 뭔가 이상한 점을 느끼기 시작했습니다. 제가 작성하는 코드는 단순히 숫자를 더하고 빼는 계산(Calculation)이 아니었기 때문입니다.
"사용자가 '결제하기' 버튼을 눌렀을 때, 재고가 0이라면 '품절' 알림을 띄우고, 재고가 있다면 PG사 결제 창을 띄운 뒤, 결제가 성공하면 '주문 완료' 상태로 변경하고 재고를 1 차감한다."
이 과정은 단순한 연산이 아닙니다. 이것은 판단이고, 조건이며, 흐름의 제어입니다. 마치 사람이 "음, 이 상황에선 이렇게 해야지"라고 생각하는 것과 똑같았습니다.
도대체 전기 신호(0과 1)만 흐르는 이 기계 덩어리가 어떻게 이런 '논리적인 생각'을 할 수 있는 걸까요? 단순히 전기가 흐르고 끊기는 스위치의 집합일 뿐인데, 어떻게 상황을 판단하고 결정을 내리는 걸까요?
이 근본적인 물음에 대한 답을 찾기 위해 파고들다 보니, 저는 컴퓨터 과학의 시초이자 모든 개발자의 조상이라고 할 수 있는 한 천재를 만나게 되었습니다. 바로 앨런 튜링(Alan Turing)입니다.
처음에 '튜링 머신(Turing Machine)'이라는 이름을 들었을 때는, 증기 기관이나 에니그마 같은 거대한 철제 기계를 상상했습니다. 박물관 어딘가에 전시되어 있는 톱니바퀴 달린 계산기 같은 것 말이죠.
하지만 튜링 머신은 실제로 존재하는 기계가 아니었습니다. 이것은 앨런 튜링이 1936년, "계산 가능하다는 것은 무엇인가?"라는 수학적 난제를 풀기 위해 고안해낸 상상의 모델(Mathematical Model)입니다. 머릿속에서만 존재하는 가상의 기계였죠.
그런데 소름 돋는 점은, 우리가 지금 쓰고 있는 스마트폰, 노트북, 슈퍼컴퓨터, 심지어 클라우드 서버까지, 현대 컴퓨터의 모든 동작 원리가 이 상상의 기계를 그대로 구현한 것이라는 사실입니다. 1930년대에 만들어진 이 개념이 100년 가까이 지난 지금도 유효하다는 게 믿기지 않았습니다.
제가 튜링 머신을 이해했을 때 가장 충격받았던 건 그 구조의 단순함이었습니다. "인공지능을 돌리는 컴퓨터의 조상이 이렇게 간단하다고?" 싶을 정도였으니까요. 튜링 머신은 딱 세 가지 요소로 이루어져 있습니다.
작동 원리는 더 허무할 정도로 간단합니다. 헤드가 테이프를 한 칸 읽습니다. 그리고 규칙표를 봅니다.
"내 현재 상태가 'A'이고, 지금 읽은 숫자가 '0'이라면 → 숫자를 '1'로 고쳐 쓰고, 헤드를 오른쪽으로 한 칸 이동하고, 내 상태를 'B'로 바꿔라."
이게 전부입니다. 정말 이게 다입니다.
이 네 가지 동작만 무한히 반복합니다. 처음엔 "이걸로 뭘 할 수 있다는 거야?"라고 생각했습니다. 고작 0을 1로 바꾸고 옆으로 움직이는 게 다인데 말이죠.
하지만 이 단순한 규칙들이 수만 개, 수억 개 모이면 이야기가 달라집니다.
0과 1을 더하는 규칙을 만들 수 있습니다. (덧셈)
덧셈을 반복하면 곱셈이 됩니다.
조건에 따라 상태를 바꾸면 if-else 분기문이 됩니다.
특정 상태로 되돌아가면 for/while 반복문이 됩니다.
결국 우리가 짜는 복잡한 웹 서비스, 화려한 게임, 거대한 AI 모델도 근본적으로는 튜링 머신이 테이프 위를 왔다 갔다 하며 0과 1을 뒤집는 과정의 엄청난 반복일 뿐이라는 것을 깨달았습니다.
이 이론을 공부하고 나서 제 코드를 다시 보니, 전에는 보이지 않던 것들이 보이기 시작했습니다. 특히 버그(Bug)가 발생하는 패턴이 눈에 들어왔습니다.
초창기 쇼핑몰 서비스를 개발할 때, 저는 주문 처리 로직을 아주 직관적(?)으로 짰습니다.
// 제가 실제로 짰던 엉망진창 코드의 의사코드(Pseudo-code)
if (사용자가_주문버튼_클릭) {
if (재고 > 0) {
결제_API_호출();
if (결제_성공) {
DB_주문생성();
화면_성공매시지_표시();
} else {
화면_에러표시();
}
} else {
화면_품절표시();
}
}
언뜻 보면 문제없어 보입니다. 하지만 '결제 중'에 사용자가 뒤로 가기를 누른다면? 결제는 성공했는데 DB에 저장하다가 네트워크가 끊긴다면? 재고는 줄었는데 결제가 실패한다면?
변수(isPaymentSuccess, hasStock 등)들이 얽히고설키면서, 나중에는 "결제는 됐는데 주문 내역이 없는" 최악의 사태가 벌어졌습니다. 저는 밤새 로그를 뒤지며 "도대체 변수 값이 어디서 꼬인 거지?"라고 머리를 쥐어뜯었습니다.
튜링 머신의 핵심은 '상태(State)'에 있습니다. 기계는 한 순간에 오직 하나의 상태만 가질 수 있습니다.
저는 제 코드를 '변수 값들의 조합'이 아니라, '상태의 전이'로 다시 설계해보기로 했습니다. 이것이 바로 유한 상태 기계(FSM)입니다.
그리고 규칙표(Transition Table)를 만들었습니다.
Idle 상태에서 '클릭' 이벤트가 오면 → Ordering 상태로 간다.Ordering 상태에서 '재고 있음' 확인되면 → PaymentWait 상태로 간다.PaymentWait 상태에서 '결제 성공' 이벤트가 오면 → Paid 상태로 간다.Ordering 상태에서 또 '클릭' 이벤트가 오면? → 무시한다. (이미 주문 중이니까!)이렇게 상태를 정의하고 나니, 코드가 획기적으로 단순해졌습니다. if문이 덕지덕지 붙어있던 코드가 사라지고, 현재 상태가 무엇인지만 보면 다음 행동이 명확해졌습니다.
// XState 같은 라이브러리를 쓴다면 이런 느낌이겠죠
const orderMachine = createMachine({
initial: 'idle',
states: {
idle: { on: { CLICK: 'ordering' } },
ordering: {
on: {
STOCK_OK: 'paymentWait',
NO_STOCK: 'soldOut'
}
},
paymentWait: {
on: {
PAY_SUCCESS: 'paid',
PAY_FAIL: 'error'
}
},
// ...
}
});
튜링 머신을 공부하지 않았다면, 저는 여전히 boolean 변수 10개를 만들어서 if (isLoading && !isError && isStockCheck) 같은 끔찍한 조건식을 짜고 있었을 겁니다. 튜링 머신은 저에게 "복잡한 로직을 '상태'와 '전이'로 나누어 생각하는 법"을 알려주었습니다.
튜링은 튜링 머신을 제안하면서 동시에 아주 절망적인 증명을 하나 해냅니다. 바로 정지 문제(Halting Problem)입니다.
개발자라면 누구나 꿈꾸는 도구가 있습니다. "내 코드를 넣기만 하면, 버그가 있는지 없는지, 무한 루프에 빠질지 아닐지 완벽하게 판별해주는 슈퍼 프로그램."
만약 이런 게 있다면 우리는 밤새 디버깅을 할 필요가 없을 겁니다. 배포 전에 이 슈퍼 프로그램을 돌려보고 "OK" 사인이 떨어지면 안심하고 퇴근하면 되니까요.
하지만 튜링은 수학적으로 증명했습니다. "그런 프로그램은 존재할 수 없다."
어떤 프로그램이 영원히 실행될지(무한 루프), 아니면 언젠가 멈출지(정상 종료)를 미리 완벽하게 알아내는 것은 논리적으로 불가능하다는 것입니다.
이 사실을 알았을 때, 저는 묘한 위로를 받았습니다. 저는 주니어 시절, "내가 실력이 부족해서 버그를 못 잡는 거야. 고수들은 버그 없는 완벽한 코드를 짜겠지?"라고 자책하곤 했습니다.
하지만 튜링 형님(?)의 말씀에 따르면, 완벽한 무결점 코드는 신의 영역입니다. 우리는 필연적으로 예측 불가능한 상태에 빠질 수밖에 없는 운명인 것입니다.
그래서 현대 소프트웨어 공학은 '버그 없는 코드'를 짜는 것보다, '버그가 생겼을 때 빨리 감지하고 복구하는 시스템'을 만드는 쪽으로 발전했습니다. 서버가 멈출 수 있다는 것을 인정하고 오토 스케일링(Auto Scaling)을 설정하고, 코드가 죽을 수 있다는 것을 인정하고 트라이-캐치(try-catch)와 서킷 브레이커(Circuit Breaker)를 심습니다.
우리가 할 수 있는 건 '완벽'이 아니라, '최선'의 방어책을 만드는 것뿐이라는 겸손함을 배웠습니다.
튜링의 또 다른 위대한 업적은 '보편 튜링 머신(Universal Turing Machine)'의 개념입니다.
이전의 기계들은 목적이 정해져 있었습니다. 계산기는 계산만 하고, 타자기는 글자만 칩니다. 계산기로 타자를 칠 수는 없습니다. 기능을 바꾸려면 기계를 뜯어고쳐야 했죠(하드웨어 변경).
하지만 튜링은 생각했습니다. "테이프에 적힌 규칙표(소프트웨어)만 바꿔끼우면, 하나의 기계가 계산기도 됐다가, 타자기도 됐다가, 체스 게임기도 될 수 있지 않을까?"
이것이 바로 '프로그램 내장 방식'의 시초이자, 소프트웨어(Software)의 탄생입니다.
우리가 아이폰을 쓸 때, 기계를 뜯어고치지 않고도 '앱'만 다운로드하면 전화기가 게임기가 되고, 은행 단말기가 되고, TV가 됩니다. 이것이 가능한 이유는 아이폰이 보편 튜링 머신이기 때문입니다. 하드웨어는 그대로 둔 채, 테이프(메모리)에 적힌 규칙(코드)만 바꾸면 되니까요.
제가 개발자라는 직업을 가질 수 있는 것도, 결국 튜링이 "하드웨어로부터 로직을 해방시켰기 때문"입니다. 우리는 납땜 인두를 들고 회로를 연결하는 대신, 키보드를 두드려 텍스트를 작성함으로써 기계를 재창조하고 있는 것입니다.
컴퓨터의 역사를 단순한 암기 과목으로 접근했을 때는 재미가 없었습니다. 1936년, 앨런 튜링... 연도나 외우는 건 의미가 없었죠.
하지만 "이 모든 것이 내가 지금 짜고 있는 코드의 뿌리다"라고 생각하니 전율이 일었습니다.
if문은 튜링 머신의 규칙표에서 왔고.변수는 튜링 머신의 테이프에서 왔고.무한 루프는 튜링의 정지 문제와 연결되어 있습니다.우리는 100년 전 앨런 튜링이 상상했던 그 기계 위에서 춤을 추고 있는 셈입니다. 아무리 화려한 프레임워크와 AI 도구를 써도, 본질은 변하지 않았습니다.
"입력(Input)을 받아, 상태(State)를 변경하고, 출력(Output)을 낸다."코드가 꼬여서 머리가 아플 때면, 모니터에서 눈을 떼고 잠시 이 본질을 떠올립니다. "지금 내 프로그램의 상태는 무엇이지?" "이 입력을 받으면 어떤 상태로 전이되어야 하지?"
이 질문에 답할 수 있다면, 아무리 복잡한 문제라도 반드시 풀립니다. 튜링이 그랬던 것처럼요.