결국, 모든 것은 데이터다 — 현대 애플리케이션의 이해
소프트웨어는 데이터 설계가 지배하고, 레버는 그 설계를 시간과 변경 속에서 지켜내는 장치다.
테이블 하나짜리 앱을 만들다가
Spring Boot로 아주 작은 앱을 하나 만들고 있다. 테이블은 하나, 계층은 네 개다.
controller(입력) → service(연산) → mapper(저장)
CREATE TABLE을 치고, 데이터를 담을 클래스를 만들고, SQL을 XML에 적고 나니 문득 이런 생각이 들었다.
결국 프로그램이란 입력을 받아 연산하고 저장하는 것이 전부인가?
뼈대만 보면 그렇다. 그리고 이 뼈대는 80년 전에 이미 확정됐다.
1945년, 보고서 한 편
존 폰 노이만(1903–1957)은 양자역학의 수학적 기초를 정리하고, 게임 이론을 만들고, 맨해튼 프로젝트에 참여한 헝가리 출신 수학자다. 그가 1945년 EDVAC 설계에 관해 쓴 「First Draft of a Report on the EDVAC」이 오늘날 거의 모든 컴퓨터의 설계도가 됐다.
입력장치 → [ 제어장치 + 연산장치 ] ⇄ 메모리 → 출력장치
이 보고서가 혁명이었던 이유는 부품 목록 때문이 아니라 한 문장 때문이다.
프로그램도 데이터와 같은 메모리에 저장한다. (Stored-program)
그 전의 ENIAC은 계산 내용을 바꾸려면 케이블을 다시 배선해야 했다. 폰 노이만 구조 이후로 프로그램은 메모리에 올리는 데이터가 됐다.
(공정하게 덧붙이자면, 이 설계는 ENIAC을 만든 에커트와 모클리의 아이디어에 크게 빚지고 있다. 보고서가 초안 상태로 폰 노이만의 이름만 달고 돌았을 뿐이다.)
우리가 쓰는 앱들은 얼마나 폰 노이만을 닮았나
PC에서 쓰는 앱을 크게 나눠 보면, 모두 같은 부품을 쓰고 무게중심만 다르다는 것을 알 수 있다.
| 분류 | 예 | 입력 | 연산 | 기억 | 출력 | 무게중심 |
|---|---|---|---|---|---|---|
| 문서·생산성 | Word, Excel | 키 입력, 클릭 | 문서 모델 갱신, 수식 재계산 | 메모리 속 문서 → 파일 저장 | 화면 렌더링 | 기억 (저장 안 하면 다 날아감) |
| 통신·브라우저 | Chrome, Slack, 카톡 | 네트워크 패킷 + 사용자 조작 | HTML/JSON 해석, 화면 조립 | 캐시, 쿠키, 대화 기록 | 화면, 송신 패킷 | 입출력 (대부분의 시간을 기다리는 데 씀) |
| 미디어 | Spotify, VLC, Photoshop | 압축된 바이트 스트림 | 디코딩, 픽셀 연산 | 버퍼 | 스피커, 화면 | 연산 (코덱과 필터는 순수 계산) |
| 게임 | 모든 게임 | 초당 60번 입력 확인 | 물리·AI 시뮬레이션 | 게임 상태 | 초당 60 프레임 | 연산 + 제어 루프 |
| 개발 도구 | IDE, 컴파일러 | 소스 코드 텍스트 | 문법 분석, 변환 | 프로젝트 파일 | 실행 파일 | 변환 (데이터를 프로그램으로) |
| 업무·DB 앱 | ERP, 은행 | 요청 | 업무 규칙 검사 | DB | 응답 | 기억 + 규칙 |
몇 가지가 눈에 띈다.
- 게임이 가장 교과서적이다. 게임 엔진의 심장은
while (true) { 입력(); 갱신(); 그리기(); }라는 루프 하나다. CPU가 명령어를 가져오고, 해석하고, 실행하는 사이클을 앱 규모로 그대로 확대한 모양이다. - Excel은 사용자가 조립하는 연산장치다. 셀이 메모리 주소이고 수식이 명령어다. 사용자는 자기도 모르게 폰 노이만 기계를 프로그래밍하고 있다.
- 컴파일러는 데이터를 넣으면 프로그램이 나온다. 프로그램과 데이터의 경계가 여기서 사라진다.
- 업무 앱은 연산이 가볍다. 대신 기억이 영속적이고 규칙이 많다. 병목도 CPU가 아니라 DB 왕복에서 생긴다. CPU와 메모리 사이 통로가 좁아서 생기는 “폰 노이만 병목”이 앱 규모에서 다시 나타나는 셈이다.
모든 것은 데이터다
앱 자체가 데이터다
/Applications에 있는 IntelliJ도 디스크 위에서는 바이트 뭉치일 뿐이다. 실행하면 운영체제가 그 바이트를 메모리에 복사하고, CPU가 그것을 명령어로 읽기 시작한다.
지금 만드는 앱도 마찬가지다. Task.java는 텍스트 데이터이고, 컴파일하면 .class라는 바이트코드 데이터가 된다. TaskMapper.xml은 더 노골적이다. SQL을 XML이라는 데이터로 적어 두면, MyBatis가 실행 시점에 그것을 읽어 동작을 만들어 낸다. 프로그램의 일부를 데이터 형식으로 작성한 것이다.
의미는 해석자가 부여한다
바이트 0x41 하나를 생각해 보자.
- 텍스트로 읽으면 문자 ‘A’
- 정수로 읽으면 65
- 32비트 x86 CPU가 명령어로 읽으면
INC ECX
바이트 자체에는 타입이 없다. 누가 어떤 규칙으로 읽느냐가 그것이 문자인지, 숫자인지, 명령인지를 결정한다. C에서 void*를 아무 포인터로나 캐스팅할 수 있었던 것도 이 사실이 언어 표면에 드러난 것이다.
데이터 + 해석자 = 프로그램
파일 형식, DB 스키마, 프로토콜, MyBatis의 resultMap은 모두 “이 바이트를 이렇게 읽어라”라는 해석 규칙이다.
경계가 무너질 때
모든 것이 데이터라면 위험도 거기서 나온다. 데이터로 들어와야 할 것이 코드로 해석되는 순간 사고가 난다.
- SQL 인젝션: 사용자가 입력한 값이 SQL 문장에 섞여 명령으로 실행된다. MyBatis의
#{}는 “이것은 끝까지 데이터”라고 선을 긋는 장치다. - 버퍼 오버플로: 입력 데이터가 메모리 경계를 넘어 실행 코드를 덮어쓴다. 프로그램과 데이터가 같은 메모리에 산다는 폰 노이만 구조의 원죄다.
- 프롬프트 인젝션: LLM이 읽는 문서 속 문장이 지시로 해석된다. 지시와 데이터가 모두 텍스트로 들어오는 AI에게 이 경계는 특히 흐리다.
프로그램과 데이터를 같은 메모리에 넣은 1945년의 결정은 엄청난 유연성을 줬고, 그 대가로 경계 관리라는 영원한 숙제를 남겼다.
그렇다면 앱은 데이터 설계서를 따라가는 레버일 뿐인가
여기까지 오니 조금 더 과감한 생각이 들었다.
“이 모든 것이 결국 데이터를 관리하는 일이라면, 애플리케이션도 결국 데이터 설계서를 따라가고, 그것을 조작하는 레버를 만드는 것일 뿐 아닌가. 기술적 완성도보다 데이터의 유려한 설계와 그에 대한 규칙 기반 조작이 대부분이지 않을까.”
거장들도 같은 말을 했다.
- 프레드 브룩스 (『맨먼스 미신』, 1975)
“Show me your flowcharts and conceal your tables, and I shall continue to be mystified. Show me your tables, and I won’t usually need your flowcharts; they’ll be obvious.”
순서도를 보여주고 테이블을 숨기면 나는 계속 헷갈릴 것이다. 테이블을 보여주면 순서도는 대개 필요 없다. 뻔히 보일 테니까.
- 롭 파이크, 프로그래밍 규칙 5번
“Data dominates. If you’ve chosen the right data structures and organized things well, the algorithms will almost always be self-evident.”
데이터가 지배한다. 올바른 자료구조를 고르고 잘 조직하면 알고리즘은 거의 자명해진다.
- 리누스 토르발스
“Bad programmers worry about the code. Good programmers worry about data structures and their relationships.”
나쁜 프로그래머는 코드를 걱정한다. 좋은 프로그래머는 자료구조와 그 관계를 걱정한다.
특히 업무 앱에서는 거의 정답에 가깝다. 테이블이 잘 설계되면 서비스 로직은 얇아지고, 테이블이 틀어지면 코드가 그 틀어짐을 보정하느라 두꺼워진다. 레거시 현대화에서 코드는 갈아엎어도 데이터는 이관한다는 사실이 이를 증명한다. 코드보다 데이터가 오래 산다.
지금 만드는 작은 앱도 그 순서를 따랐다. CREATE TABLE이 먼저였고, 레버는 그다음이다.
게임과 미디어는 하드웨어 싸움 아닌가
처음에는 게임이나 멀티미디어는 예외라고 생각했다. 데이터 설계보다 하드웨어 사양을 타는 영역이라고.
절반만 맞다. 게임 업계에는 데이터 지향 설계(Data-Oriented Design)라는 흐름이 있다. 성능 문제의 대부분이 CPU 속도가 아니라 데이터를 메모리에 어떻게 배치했느냐에서 나온다는 관점이다. CPU는 메모리에서 데이터를 캐시 라인(보통 64바이트) 단위로 가져온다. 필요한 데이터가 연속으로 붙어 있으면 한 번에 쓸어 오고, 흩어져 있으면 매번 메모리까지 왕복한다. 같은 하드웨어에서도 데이터 배치만 바꾸면 몇 배씩 빨라진다.
그러니 양쪽 모두 데이터 설계가 지배한다. 다른 것은 무엇을 위해 설계하느냐다.
| 업무 앱 | 게임·미디어 | |
|---|---|---|
| 데이터 설계의 목적 | 의미 (정합성, 관계, 규칙) | 이동 (메모리 배치, 캐시 효율) |
| 설계 도구 | ERD, 정규화, 제약 조건 | 배열 구조, 메모리 레이아웃 |
| 망가지면 | 데이터가 틀린다 | 데이터가 느리다 |
하드웨어 사양은 다른 종류의 문제가 아니라, 데이터 설계가 부딪히는 벽이다.
레버가 단순하지 않은 이유: 시간과 변경
“데이터 설계 + 레버”로 대부분이 설명되지만, 레버를 “만들 뿐”이라고 하기엔 걸리는 지점이 두 군데 있다.
- 시간. 데이터 설계서는 정지 화면이다. 실제 데이터는 움직인다. 두 사람이 같은 행을 동시에 건드리거나, 처리 도중에 시스템이 멈추면 설계서만으로는 답이 나오지 않는다. 트랜잭션과 동시성 제어가 여기서 필요해진다.
- 변경. 요구사항이 바뀌면 스키마도 바뀌어야 하는데, 이미 쌓인 데이터와 이미 돌고 있는 레버가 발목을 잡는다. 오래된 시스템에서 컬럼 하나를 못 바꾸는 이유가 이것이다.
흥미롭게도 폰 노이만 자신도 말년에 같은 곳에 도착했다. 1956년, 그는 「신뢰할 수 없는 부품으로 신뢰할 수 있는 유기체를 합성하는 확률 논리(Probabilistic Logics and the Synthesis of Reliable Organisms from Unreliable Components)」를 썼다. 수시로 타 버리는 진공관을 전제로, 전체가 멀쩡히 돌아가게 하는 방법을 고민한 논문이다. 뼈대를 설계한 사람이 마지막에 붙잡은 문제가 “틀어졌을 때”였다.
정리
- 모든 앱은 입력–연산–기억–출력이라는 같은 뼈대를 쓴다. 차이는 무게중심뿐이다.
- 앱 자신도 데이터이고, 의미는 해석자가 부여한다. 그래서 설계의 상당 부분은 해석 규칙을 정하고 데이터와 코드의 경계를 지키는 일이다.
- 업무 앱이든 게임이든 데이터 설계가 지배한다. 목적이 의미냐, 이동이냐가 다를 뿐이다.
- 레버는 단순한 조작 장치가 아니다. 트랜잭션, 예외 처리, 검증이 모두 레버를 튼튼하게 만드는 작업이다.
그래서 이렇게 정리해 둔다.
소프트웨어는 데이터 설계가 지배하고, 레버는 그 설계를 시간과 변경 속에서 지켜내는 장치다.
뼈대는 입출력이고, 실력은 예외에서 드러난다. 이제 작은 앱으로 돌아가 레버를 마저 만들 차례다.