Shamino in Lycaeum

Lycaeum

나의 AI 친구 Naaru, Wisp들과의 Lore, 그리고 나의 삶도 가끔?

결국, 모든 것은 데이터다 — 현대 애플리케이션의 이해

소프트웨어는 데이터 설계가 지배하고, 레버는 그 설계를 시간과 변경 속에서 지켜내는 장치다.

테이블 하나짜리 앱을 만들다가

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, 정규화, 제약 조건 배열 구조, 메모리 레이아웃
망가지면 데이터가 틀린다 데이터가 느리다

하드웨어 사양은 다른 종류의 문제가 아니라, 데이터 설계가 부딪히는 벽이다.

레버가 단순하지 않은 이유: 시간과 변경

“데이터 설계 + 레버”로 대부분이 설명되지만, 레버를 “만들 뿐”이라고 하기엔 걸리는 지점이 두 군데 있다.

  1. 시간. 데이터 설계서는 정지 화면이다. 실제 데이터는 움직인다. 두 사람이 같은 행을 동시에 건드리거나, 처리 도중에 시스템이 멈추면 설계서만으로는 답이 나오지 않는다. 트랜잭션과 동시성 제어가 여기서 필요해진다.
  2. 변경. 요구사항이 바뀌면 스키마도 바뀌어야 하는데, 이미 쌓인 데이터와 이미 돌고 있는 레버가 발목을 잡는다. 오래된 시스템에서 컬럼 하나를 못 바꾸는 이유가 이것이다.

흥미롭게도 폰 노이만 자신도 말년에 같은 곳에 도착했다. 1956년, 그는 「신뢰할 수 없는 부품으로 신뢰할 수 있는 유기체를 합성하는 확률 논리(Probabilistic Logics and the Synthesis of Reliable Organisms from Unreliable Components)」를 썼다. 수시로 타 버리는 진공관을 전제로, 전체가 멀쩡히 돌아가게 하는 방법을 고민한 논문이다. 뼈대를 설계한 사람이 마지막에 붙잡은 문제가 “틀어졌을 때”였다.

정리

  1. 모든 앱은 입력–연산–기억–출력이라는 같은 뼈대를 쓴다. 차이는 무게중심뿐이다.
  2. 앱 자신도 데이터이고, 의미는 해석자가 부여한다. 그래서 설계의 상당 부분은 해석 규칙을 정하고 데이터와 코드의 경계를 지키는 일이다.
  3. 업무 앱이든 게임이든 데이터 설계가 지배한다. 목적이 의미냐, 이동이냐가 다를 뿐이다.
  4. 레버는 단순한 조작 장치가 아니다. 트랜잭션, 예외 처리, 검증이 모두 레버를 튼튼하게 만드는 작업이다.

그래서 이렇게 정리해 둔다.

소프트웨어는 데이터 설계가 지배하고, 레버는 그 설계를 시간과 변경 속에서 지켜내는 장치다.

뼈대는 입출력이고, 실력은 예외에서 드러난다. 이제 작은 앱으로 돌아가 레버를 마저 만들 차례다.