많은 이들이 AI가 코드를 대신 짜주는 시대에 개발자의 종말을 이야기합니다. 하지만 9년 차 이상의 시니어라면 직감하고 있습니다. 엔진이 강력해질수록 핸들의 유격은 더 정교해야 한다는 것을요. 고대 신화 속 자신의 꼬리를 삼키며 무한히 순환하는 우로보로스(Ouroboros)는, 이제 우리에게 단순한 상징을 넘어 'AI 오케스트레이션의 최종 진화 모델'을 제시합니다.

1. 뱀의 머리: "명세(Specification)는 가장 고차원적인 코딩이다"

우로보로스 프로젝트의 철학은 명확합니다. "프롬프팅을 멈추고, 스펙을 써라." 자바 시니어라면 익숙한 인터페이스 기반 설계(Interface-driven Design)와 맞닿아 있습니다. 우리가 Interface를 정의할 때 구현체에 관심이 없듯, AI 에이전트에게 필요한 것은 "어떻게(How)"가 아니라, 완벽하게 모호성이 제거된 "무엇을(What)"에 대한 명세입니다.

  • 모호성 점수(Ambiguity Score)의 관리: 훌륭한 아키텍트는 AI에게 명령하기 전, 스스로 질문합니다. "이 명세에 해석의 여지가 남아 있는가?"
  • Seed YAML의 힘: 우로보로스에서 사용하는 Seed 명세처럼, 도메인 주도 설계(DDD)의 유비쿼터스 언어를 활용해 '비즈니스 요구사항'을 '기계적 명세'로 치환하는 능력이 AI 시대 시니어의 첫 번째 덕목입니다.

2. 뱀의 몸통: "오케스트레이션은 검증의 연속이다"

명세가 정확하다면, 그다음은 수많은 에이전트를 조율(Orchestration)하는 단계입니다. 단순히 코드를 생성하는 것이 아니라, 생성된 코드가 명세를 배반하지 않는지 끝없이 순환하며 확인하는 과정이죠.

우로보로스가 제안하는 3단계 검증 루프는 자바의 견고한 인프라와 결합할 때 폭발적인 ROI를 냅니다.

  • Mechanical Gate: 자바의 강력한 Type SystemLinter가 문법적 무결성을 1차 검증합니다.
  • Semantic Gate: Property-based Testing(Fast Check)을 통해 로직의 엣지 케이스를 무차별적으로 공격하여 논리적 결함을 찾아냅니다.
  • Consensus Gate: 여러 LLM 모델이 서로의 코드를 리뷰하며 '최선의 합의점'을 도출합니다.

이 과정에서 아키텍트는 단순 코더가 아닌, '검증 파이프라인의 설계자'가 됩니다.

3. 뱀의 꼬리: "자기 참조적 진화(Self-Correction)"

우로보로스의 뱀이 제 꼬리를 먹으며 몸집을 키우듯, 좋은 AI 아키텍처는 [에러 피드백 -> 명세 수정 -> 코드 재생성]의 루프가 인간의 개입 없이 돌아가야 합니다.

에러가 발생했을 때 사람이 코드를 직접 고치는 순간, 그 프로젝트의 AI ROI는 멈춥니다. 대신 "왜 명세가 이 에러를 막지 못했는가?"를 고민하고 명세를 수정해야 합니다. 꼬리(에러/결과물)를 머리(명세)로 다시 보내는 이 순환 루프가 완성될 때, 비로소 시스템은 스스로 진화하는 '살아있는 유기체'가 됩니다.


맺으며: "명세는 영원하고, 코드는 찰나다"

자바 25가 나오고 버추얼 스레드가 세상을 바꿔도, '도메인의 본질을 꿰뚫는 명세'의 가치는 변하지 않습니다. AI라는 거대한 파도를 타는 유능한 서퍼는 파도를 통제하려 하지 않습니다. 대신 자신이 가고자 하는 목적지(Specification)를 명확히 하고, 보드(Infra)를 견고하게 다질 뿐입니다.

검증 인프라에 투자하고 명세를 예술의 경지로 끌어올리는 프로젝트만이, 이 우로보로스의 순환 루프 안에서 무한히 성장할 수 있습니다.

"당신은 오늘, AI에게 코드를 짜라고 명령했습니까? 아니면 완벽한 명세를 선물했습니까?"

 

 

LIST

1. 10년 전이면 고수의 글, 지금은 딸각

10년 전 기술 블로그를 떠올려 본다. 커널 패닉을 며칠 밤새 추적한 흔적, GC 로그를 한 줄씩 뜯어 본 기록, 분산 트랜잭션의 엣지 케이스를 직접 재현해 본 이야기. 그런 글에는 손때가 묻어 있었다. 글쓴이가 그 깊이까지 닿았다는 증명이 글 자체였다. 그런데 지금은 같은 수준의 문장이 Claude 한 번 "딸각"이면 그럴듯하게 쏟아진다. 정보의 양은 폭증했는데 진짜 신호를 가려내기는 더 어려워졌다. 자신감 넘치는 어조와 잘 정돈된 목차, 매끄러운 코드 블록—그 안이 비어 있어도 외형은 완벽하다. 빈수레일수록 더 요란하게 굴러간다. 과거에는 깊이가 형식을 만들었다면, 지금은 형식이 깊이를 흉내 낸다. 글의 모양만 보고 사람을 판단하던 기준이 더 이상 작동하지 않는다.

https://www.youtube.com/shorts/qtxUtMhDqYI

 

2. 고수의 재정의 — 한 명의 천재에서 여러 전문가의 오케스트레이터로

그렇다면 고수란 무엇인가. 과거의 고수는 단수(單數)였다. 한 분야를 깊이 파고들어 그 영역에서는 누구보다 멀리 본 사람. 그러나 미래의 고수는 복수(複數)를 다룬다. 과거의 고수 여럿을 agent로 부리는 사람이다. 이는 단순히 프롬프트를 잘 쓰는 능력과 다르다. 각 영역에서 어느 수준의 전문성이 필요한지 판별하고, AI가 내놓은 결과물이 진짜인지 그럴듯한 모조품인지 검증하고, 도메인 판단력으로 여러 산출물을 통합해 의사결정하는 능력이다. Claude Code 한두 개 띄워 본 사람과 멀티 에이전트를 조직처럼 운영해 본 사람의 격차는, 10년 전 IDE를 막 쓰는 신입과 디버거를 손발처럼 다루던 시니어의 격차와 닮았다. 도구가 평준화될수록 도구를 부리는 판단력이 유일한 차별화 요소로 남는다. 고수의 자리는 사라진 것이 아니라 한 단계 위로 이동했다.

3. 중간 관리자 개발자의 위치 애매성

이 지각 변동에서 가장 위태로운 자리가 4~7년차 중간 관리자급 개발자다. 위로는 시니어 아키텍트의 시야와 통찰에 아직 미치지 못하고, 아래로는 AI가 주니어급 코딩을 빠른 속도로 흡수하고 있다. 정확히 말하면 "어중간한 깊이의 작업"이 가장 먼저 자동화되는 영역이다. CRUD 한 세트, 평이한 리팩터링, 무난한 API 설계, 매뉴얼 수준의 트러블슈팅. 이 일들로 연봉의 정당성을 만들어 왔다면, 이제 그 정당성이 흔들린다. 회사 입장에서 어중간한 미들 레벨 다섯 명보다 시니어 한 명 + AI 도구 잘 쓰는 주니어 두 명이 더 효율적인 시대가 오고 있다. 미들 개발자의 진짜 위기는 일자리가 사라지는 것이 아니라, 자기 포지션이 무엇인지 스스로도 설명하지 못하는 상태다. "두루두루 합니다"는 더 이상 강점이 아니라 약점에 가깝다.

4. 두 갈래 길 — 깊이 있는 개발자 혹은 전천후 오케스트레이터

이 애매한 지대에서 빠져나가는 길은 두 가지다. 하나는 깊이로 가는 길. 분산 시스템, 보안, 컴파일러, 도메인 모델링—AI가 표면을 흉내 낼 수는 있어도 진짜 엣지 케이스에서 책임을 질 수는 없는 영역이 있다. 그곳에서 한 점을 깊게 뚫고 들어간 사람은 AI 시대에도 대체되지 않는다. 다른 하나는 전천후 오케스트레이터로 가는 길. 모든 코드를 직접 짜지는 못해도 전체 아키텍처를 머릿속에 그릴 수 있고, 도메인 판단력과 시스템 사고로 여러 agent의 산출물을 통합해 책임지는 사람. 본인이 모든 깊이를 갖지 않아도 어느 깊이가 필요한지 판단할 수 있다면 그 자체가 고수다. 두 길의 공통점은 결국 판단력이다. 깊이는 한 점에 집중된 판단력이고, 전천후는 넓은 영역에 분산된 통합 판단력이다. 가장 위험한 선택은 어느 쪽도 아닌 중간에 머무르는 것. 빈수레의 요란함을 흉내 내는 데 시간을 쓰지 말고, 어느 방향이든 무게가 실리는 수레를 만들어야 한다. 10년 후의 고수는 지금 그 무게를 쌓고 있는 사람이다.

LIST

1일차: 클라우드 및 Docker 개념 학습

클라우드 컴퓨팅이 필요한 이유

  1. 비용절감
    • 하드웨어 및 소프트웨어를 구입하고 데이터 센터 설치 및 운영 비용을 줄일 수 있습니다.
    • 서버 랙, 전원 및 냉각에 사용되는 전기료, 인프라 관리를 위한 IT 전문가 인건비 등도 절약됩니다.
  2. 속도향상
    • 주문형 셀프서비스로 제공되기 때문에 다양한 컴퓨팅 리소스를 몇번의 마우스 클릭으로 설정할 수 있습니다.
  3. 확장성
    • 필요할 때 적절하게 스토리지, 네트워크 대역폭 등 IT 자원을 확장할 수 있습니다.
  4. 생산성
    • 일반적인 온사이트 데이터센터에서는 하드웨어 설치, 소프트웨어 패치 및 시간이 오래걸리는 IT 운영 작업이 필요하지만, 클라우드 컴퓨팅을 사용하면 이 모든게 불필요해지므로 IT 팀은 인프라 외에 비즈니스에 집중할 수 있습니다.

Docker Compose vs Kubernetes

 

 

 

2일차: Docker 이미지 생성 실습

Docker 이미지 주요 명령어

# 현재 시스템에서 사용 가능한 모든 Docker 이미지 목록 확인
$ docker images

# 특정 이미지 다운로드
$ docker pull <이미지 이름>

# 이미지 정보 확인
$ docker inspect <이미지 이름>

# 이미지 삭제
$ docker rmi <이미지 이름>

 

 불필요한 파일 줄이기 (.dockerignore 활용)

  • .**dockerignore** 파일은 Docker 빌드 과정에서 제외할 파일과 디렉터리를 지정하는 역할을 합니다.
  • Docker는 docker build를 실행할 때, 현재 디렉터리(.)의 모든 파일을 Docker 컨텍스트에 복사합니다.
  • 이때 .dockerignore 파일을 사용하면 불필요한 파일을 제외하여 빌드 속도를 향상시키고, 보안성을 강화할 수 있습니다.
  • .dockerignore 파일이 필요한 이유
    1. 빌드 속도 향상 → 불필요한 파일을 제외하여 Docker 컨텍스트 크기를 줄임
    2. 보안 강화 → 환경 변수 파일(.env), 인증 정보(.git, node_modules) 등이 이미지에 포함되지 않도록 방지
    3. 이미지 크기 최소화 → 최적화된 Docker 이미지를 생성하여 배포 용량을 줄임

 

 

레이어 캐싱 (Layer Caching)

도커의 가장 중요한 특징 중 하나는 레이어 캐싱입니다. 이미지를 빌드할 때, Dockerfile의 각 명령어를 순서대로 실행하며 레이어를 만듭니다. 이때 명령어의 내용이 이전 빌드와 동일하다면, 새로운 레이어를 만들지 않고 기존에 만들어 두었던 레이어를 재사용합니다. 이를 '캐시(Cache)를 사용한다'고 말합니다.

  • 빌드 속도 향상: 변경되지 않은 부분은 다시 실행할 필요가 없으므로 이미지 빌드 시간이 크게 단축됩니다.
  • 효율적인 저장 공간 사용: 여러 이미지가 동일한 레이어를 공유하는 경우, 해당 레이어는 디스크에 한 번만 저장됩니다. 예를 들어, ubuntu:22.04를 기반으로 하는 10개의 다른 이미지가 있더라도, 우분투 베이스 레이어는 단 한 번만 저장됩니다.

Dockerfile을 작성할 때, 자주 변경되지 않는 내용은 앞부분에, 자주 변경되는 내용은 뒷부분에 배치하는 것이 레이어 캐싱을 효율적으로 활용하는 좋은 방법입니다. 소스 코드는 자주 바뀌므로 COPY . . 명령어는 보통 뒷부분에 위치시킵니다.

 

 

Docker 이미지 Versioning 과 Semantic Versioning

Docker 이미지 versioning

  • Docker 이미지는 태그(Tag) 를 사용하여 버전 관리를 수행합니다.
  • latest 태그를 사용할 수도 있지만, 명확한 버전 관리를 위해 시멘틱 버저닝(Semantic Versioning) 을 사용하는 것이 좋습니다.

시멘틱 버저닝(Semantic Versioning)이란

  • 시멘틱 버저닝(SemVer)은 주버전(Major), 부버전(Minor), 패치버전(Patch) 으로 구성됩니다.
    • 형식: MAJOR.MINOR.PATCH
    • MAJOR (주 버전): 호환성이 깨지는 큰 변경이 있을 때 올립니다.
      • 예: 1.5.2 → 2.0.0
      • 기존의 사용법이나 기능이 완전히 바뀌어, 이 버전을 사용하려면 내 코드도 수정해야 할 수 있다는 강력한 신호입니다. 마치 자동차 엔진이 바뀌는 것과 같습니다.
    • MINOR (부 버전): 기존 버전과 호환되면서 새로운 기능이 추가될 때 올립니다.
      • 예: 1.9.2 → 1.10.0
      • 기존 기능은 그대로 잘 작동하며, 새로운 옵션이나 기능이 추가된 경우입니다. 자동차에 새로운 내비게이션 시스템이 추가된 것과 같습니다. 기존 운전 방식에는 영향이 없습니다.
    • PATCH (패치 버전): 기존 버전과 호환되는 작은 버그 수정이 있을 때 올립니다.
      • 예: 1.5.2 → 1.5.3
      • 기능 변화 없이, 기존 기능의 오류를 바로잡은 경우입니다. 자동차의 사소한 부품 결함을 수리한 것과 같습니다.

버전 형식 설명

1.0.0 초기 릴리즈 버전
1.1.0 기능(feature) 추가 (하위 호환 유지)
1.1.1 버그 수정 (패치 버전)
2.0.0 기존 기능을 변경하거나 호환되지 않는 수정

Docker에서 Semantic Versioning 적용 예제

  • Dockerfile을 사용하여 시멘틱 버저닝을 적용할 때, 태그를 명확하게 지정합니다.
  • 시멘틱 버저닝을 활용하면 배포 관리가 쉬워지고, 특정 버전으로 롤백이 용이해집니다.
# 1.0.0 버전으로 빌드
docker build --build-arg PROFILE=dev -t spring-docker:1.0.0 .

# 1.0.1 버전으로 빌드 (패치 업데이트 포함)
docker build --build-arg PROFILE=dev -t spring-docker:1.0.1 .

 

3일차: Docker 네트워크 및 데이터 관리

 

  • Docker 네트워크 개념 및 종류 이해
  • 컨테이너 간 네트워크 설정 및 통신 실습
  • Docker 볼륨을 활용한 데이터 관리 실습
  • 보안 및 성능 최적화 기법 적용

1.3 컨테이너 간 기본 네트워크 통신 확인

 

# 현재 Docker 네트워크 확인
docker network ls

# 사용자 지정 브리지 네트워크 생성
# sleep infinity : 컨테이너 내부에서 종료되지 않는 프로세스를 실행하여 유지
docker run -d --name svc1 --network bridge busybox sleep infinity
docker run -d --name svc2 --network bridge busybox sleep infinity

docker exec -it svc1 ping svc2

-------------------------------------------------
ping: bad address 'svc2'

 

  • 기본 bridge 네트워크에서 컨테이너 간 직접 통신이 안 되는 이유
    1. 기본 bridge 네트워크에서는 컨테이너 이름 기반 DNS 해석이 지원되지 않음
    2. IP 주소를 직접 사용해야 하지만, IP는 컨테이너 재시작 시 변경될 수 있어 관리가 어려움
    3. 사용자 정의 네트워크를 만들면 컨테이너 이름을 통해 자동으로 통신 가능
  • 해결책
    • 기본 bridge 네트워크를 사용할 경우, 컨테이너 간 통신 시 IP 주소를 사용해야 합니다.
    • 컨테이너 이름으로 통신하려면 사용자 정의 네트워크(docker network create <네트워크명>)를 사용해야 합니다.

 

4일차: Docker 를 활용한 Log 관리

 

 

1. 표준 로그 스트림 및 출력 관리

1.1 표준 에러 스트림과 표준 출력 스트림

  1. 컨테이너 실행 및 로그 생성
    • sh -c 명령어를 통해 stdout과 stderr로 각각 로그를 출력합니다.
    • echo "stderr log" >&2는 표준 에러로 메시지를 보냅니다.
  2. docker run --name log-test -d alpine sh -c ' for i in $(seq 1 10); do echo "$i: stdout log"; echo "$i: stderr log" >&2; done'
  3. 로그 확인하기
    • 실행 중인 컨테이너의 모든 로그 출력
    docker logs log-test
    
    • 최근 5개의 로그 출력
    docker logs --tail 5 log-test
    
  1. 실시간 로그 스트림 확인
    • f 옵션을 사용하면 컨테이너의 실시간 로그 스트림을 확인할 수 있습니다.
  2. docker logs -f log-test
  1. 로그 출력 시 타임스탬프 포함
    • 각 로그에 타임스탬프를 추가하여 출력합니다.
    docker logs --timestamps log-test
    
  2. 실습 종료 및 컨테이너 정리

docker stop log-test && docker rm log-test

2. 컨테이너 로그 수집 및 관리

2.1 syslog 드라이버를 이용해 로그 전송 설정하기

Docker 컨테이너의 로그를 외부 syslog 서버로 전송 설정합니다.

docker run --log-driver=syslog --log-opt syslog-address=udp://localhost:514 -d ubuntu echo "Logging to syslog"

2.2 로그 로테이션 설정

Docker 컨테이너의 로그 드라이버는 기본적으로 json-file을 사용하며, 이 드라이버는 로그 로테이션 옵션이 있습니다. max-size와 max-file 옵션을 사용하여 로그 파일의 최대 크기와 보관할 로그 파일의 수를 제어할 수 있습니다.

# Docker 컨테이너를 로그 로테이션 설정과 함께 실행
docker run -d \\
  --name=log-rotation-test \\
  --log-opt max-size=10m \\
  --log-opt max-file=3 \\
  ubuntu bash -c 'while true; do echo "$(date) - Writing logs to fill up space..."; sleep 1; done'
  
# 로그 확인
docker logs -f log-rotation-test

# 종료 
docker stop log-rotation-test
docker rm log-rotation-test

2.3 로그 모니터링 실습

2.3.1 로그 모니터링 스크립트 생성

#!/bin/bash

# 첫 번째 인자로 컨테이너 이름을 받음
CONTAINER_NAME=$1
# 로그 라인 수 임계값 설정
THRESHOLD=1000

# 무한 루프로 계속 모니터링
while true; do
    # 현재 컨테이너의 로그 라인 수를 계산
    LOG_SIZE=$(docker logs $CONTAINER_NAME 2>&1 | wc -l)
    
    # 현재 시간 저장
    CURRENT_TIME=$(date "+%Y-%m-%d %H:%M:%S")
    
    # 로그 크기가 임계값을 초과하면 경고
    if [ $LOG_SIZE -gt $THRESHOLD ]; then
        echo "[$CURRENT_TIME] Warning: Log size ($LOG_SIZE lines) exceeds threshold ($THRESHOLD) for $CONTAINER_NAME"
        
        # 선택적: 관리자에게 이메일 발송
        # mail -s "Docker Log Alert" admin@example.com <<< "Container $CONTAINER_NAME log size exceeds threshold"
    else
        echo "[$CURRENT_TIME] Log size ($LOG_SIZE lines) is normal for $CONTAINER_NAME"
    fi
    
    # 1초 대기
    sleep 1
done

2.3.2 실습용 로그 생성 컨테이너 생성

# 로그를 지속적으로 생성하는 테스트용 Dockerfile
cat > Dockerfile << 'EOF'
FROM ubuntu:20.04

RUN apt-get update && apt-get install -y stress

CMD while true; do date; echo "Test log message"; sleep 1; done
EOF

# 이미지 빌드
docker build -t log-generator .

# 컨테이너 실행
docker run -d --name log-test1 log-generator

2.3.3 모니터링 실행 및 테스트

# 모니터링 스크립트 실행
./monitor-logs.sh log-test1

# 별도 터미널에서 로그 확인
docker logs -f log-test1

2.3.4 모니터링 설정 (확장편)

#!/bin/bash

CONTAINER_NAME=$1
THRESHOLD=1000
LOG_FILE="container_logs.txt"
ALERT_EMAIL="admin@example.com"

monitor_logs() {
    LOG_SIZE=$(docker logs $CONTAINER_NAME 2>&1 | wc -l)
    CURRENT_TIME=$(date "+%Y-%m-%d %H:%M:%S")
    DISK_USAGE=$(docker inspect $CONTAINER_NAME --format='{{.LogPath}}' | xargs du -h 2>/dev/null | cut -f1)
    
    echo "[$CURRENT_TIME] Container: $CONTAINER_NAME" >> $LOG_FILE
    echo "Log lines: $LOG_SIZE" >> $LOG_FILE
    echo "Disk usage: $DISK_USAGE" >> $LOG_FILE
    
    if [ $LOG_SIZE -gt $THRESHOLD ]; then
        MESSAGE="Warning: Container $CONTAINER_NAME has exceeded log threshold
        Current size: $LOG_SIZE lines
        Disk usage: $DISK_USAGE
        Time: $CURRENT_TIME"
        
        echo "$MESSAGE" >> $LOG_FILE
        echo "$MESSAGE" | mail -s "Docker Log Alert" $ALERT_EMAIL
        
        # 로그 백업 및 클리어
        docker logs $CONTAINER_NAME > "${CONTAINER_NAME}_logs_${CURRENT_TIME}.txt"
        docker container restart $CONTAINER_NAME
    fi
}

# 메인 모니터링 루프
while true; do
    if docker ps | grep -q $CONTAINER_NAME; then
        monitor_logs
    else
        echo "[$CURRENT_TIME] Container $CONTAINER_NAME is not running!" >> $LOG_FILE
    fi
    sleep 60
done
  • 로그용 Dockerfile 및 로그 생성 스크립트 작성
# 로그 생성용 Dockerfile 생성
cat > Dockerfile << 'EOF'
FROM ubuntu:20.04

# 스트레스 테스트 도구 설치
RUN apt-get update && apt-get install -y stress

# 로그 생성 스크립트
COPY generate-logs.sh /
RUN chmod +x /generate-logs.sh

CMD ["/generate-logs.sh"]
EOF

# 로그 생성 스크립트 작성
cat > generate-logs.sh << 'EOF'
#!/bin/bash

while true; do
    date
    echo "Application log: Process running"
    echo "System status: OK"
    echo "Memory usage: $(free -m | grep Mem | awk '{print $3}') MB"
    sleep 1
done
EOF

# 이미지 빌드
docker build -t log-generator .

# 컨테이너 실행
docker run -d --name test-container log-generator
  • 모니터링 실행 및 테스트
# 터미널 1: 모니터링 스크립트 실행
sh advanced-monitor-logs.sh test-container

# 터미널 2: 로그 생성 테스트
docker exec -it test-container bash -c 'for i in {1..1500}; do echo "Test log entry $i"; done'
  • 로그 확인 및 분석
# 모니터링 로그 확인
cat container_logs.txt

# 컨테이너 로그 확인
docker logs test-container

 

3. Docker 디스크 사용량 모니터링 실습

3.1 기본 디스크 사용량 모니터링

# Docker 전체 디스크 사용량 확인
docker system df

# 상세한 디스크 사용량 확인
docker system df -v

# 실시간 디스크 사용량 모니터링 스크립트 생성
cat > monitor-disk-usage.sh << 'EOF'
#!/bin/bash

while true; do
    echo "=== Docker 디스크 사용량 모니터링 === ($(date))"
    echo "1. 컨테이너 디스크 사용량:"
    docker ps --size --format "table {{.Names}}\\t{{.Size}}\\t{{.State}}"
    
    echo -e "\\n2. 이미지 디스크 사용량:"
    docker system df -v | grep "Images space"
    
    echo -e "\\n3. 볼륨 디스크 사용량:"
    docker system df -v | grep "Volumes space"
    
    echo -e "\\n4. 빌드 캐시 사용량:"
    docker system df -v | grep "Build cache"
    echo "======================================="
    sleep 60
done
EOF

chmod +x monitor-disk-usage.sh

3.2 테스트용 컨테이너 생성 및 모니터링

# 테스트용 컨테이너 생성
docker run -d --name disk-test-1 ubuntu:20.04 tail -f /dev/null
docker run -d --name disk-test-2 nginx

# 데이터 생성 스크립트
cat > generate-data.sh << 'EOF'
#!/bin/bash

for i in {1..10}; do
    echo "Generating data file $i..."
    docker exec disk-test-1 dd if=/dev/zero of=/tmp/file$i bs=1M count=100
    sleep 1
done
EOF

chmod +x generate-data.sh

3.3 디스크 모니터링 스크립트 작성

cat > advanced-disk-monitor.sh << 'EOF'
#!/bin/bash

LOG_FILE="disk_usage_log.txt"
THRESHOLD_PERCENT=80
ALERT_EMAIL="admin@example.com"

log_message() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG_FILE
}

get_container_sizes() {
    docker ps -s --format "{{.Names}}: {{.Size}}" | while read line; do
        log_message "Container $line"
    done
}

check_disk_usage() {
    # Docker 루트 디렉토리 사용량 확인
    DOCKER_ROOT=$(docker info | grep "Docker Root Dir" | cut -d: -f2 | tr -d ' ')
    USAGE_PERCENT=$(df -h $DOCKER_ROOT | awk 'NR==2 {print $5}' | tr -d '%')
    
    log_message "Docker root directory ($DOCKER_ROOT) usage: ${USAGE_PERCENT}%"
    
    if [ $USAGE_PERCENT -gt $THRESHOLD_PERCENT ]; then
        MESSAGE="Warning: Docker disk usage is at ${USAGE_PERCENT}%"
        log_message "$MESSAGE"
        
        # 가장 큰 컨테이너 5개 리스트
        log_message "Top 5 largest containers:"
        docker ps -s --format "{{.Size}}\\t{{.Names}}" | sort -hr | head -n 5 | \\
            while read line; do
                log_message "$line"
            done
        
        # 사용하지 않는 리소스 정리 추천
        log_message "Recommended cleanup commands:"
        log_message "docker system prune -f"
        log_message "docker volume prune -f"
        log_message "docker image prune -a -f"
    fi
}

monitor_volumes() {
    log_message "Volume usage:"
    docker volume ls -q | while read vol; do
        USAGE=$(docker run --rm -v $vol:/vol alpine du -sh /vol)
        log_message "Volume $vol: $USAGE"
    done
}

# 메인 모니터링 루프
while true; do
    log_message "=== Starting disk usage check ==="
    check_disk_usage
    get_container_sizes
    monitor_volumes
    log_message "=== Completed disk usage check ==="
    echo -e "\\n"
    sleep 300  # 5분 간격으로 체크
done
EOF

chmod +x advanced-disk-monitor.sh

3.4 실습 시나리오

3.4.1 모니터링 시작

# 터미널 1: 기본 모니터링 실행
./monitor-disk-usage.sh

# 터미널 2: 데이터 생성
./generate-data.sh

3.4.2 모니터링 테스트

# 터미널 1: 고급 모니터링 실행
./advanced-disk-monitor.sh

# 터미널 2: 대용량 파일 생성
docker run -d --name big-data-test ubuntu:20.04 tail -f /dev/null
docker exec big-data-test dd if=/dev/zero of=/bigfile bs=1G count=2

3.4.3 디스크 정리

# 사용하지 않는 리소스 정리
docker system prune -f

# 사용하지 않는 볼륨 정리
docker volume prune -f

# 사용하지 않는 이미지 정리
docker image prune -a -f

# 정리 후 사용량 확인
docker system df -v

3.4.4 컨테이너 별 상세 모니터링

# 컨테이너별 디스크 사용량 모니터링 스크립트
cat > container-disk-monitor.sh << 'EOF'
#!/bin/bash

echo "=== Container Disk Usage Details ==="
docker ps --format "{{.Names}}" | while read container; do
    echo "Container: $container"
    echo "Size details:"
    docker ps -s --filter "name=$container" --format "Virtual Size: {{.Size}}"
    echo "Top 5 largest directories:"
    docker exec $container du -h / 2>/dev/null | sort -hr | head -n 5
    echo "------------------------"
done
EOF

chmod +x container-disk-monitor.sh
./container-disk-monitor.sh
LIST

Claude Code나 GPT-5 Codex를 매일 쓰는 개발자라면 한 번쯤 마주치는 역설이 있다.
같은 프롬프트를 두 번 넣으면 다른 코드가 나온다. 어떤 때는 Python 함수가 한 줄짜리 list comprehension으로 나오고, 다음엔 for 루프 세 줄로 나온다. 변수명이 다르고, 주석 표현이 다르고, 예외 처리 순서까지 바뀐다. 결정적(deterministic)이지 않다. 학부 시절 우리가 "좋은 소프트웨어"의 필수 조건으로 배운 바로 그 속성이 깨진다.
그런데 막상 빌드는 성공한다. 테스트는 통과한다. 종종 사람보다 낫다. 2026년 현재 "시니어 엔지니어 대부분이 실제 코드는 거의 쓰지 않는다"는 보고가 나온다. 이 사람들이 순진한 것도, 품질을 포기한 것도 아니다. 오히려 그 반대다 — 프로덕션 배포되는 코드의 양은 늘었는데 사람이 직접 타이핑한 비율은 줄었다.
이 역설이 이 글의 출발점이다. 주사위를 굴려 나온 코드가 왜 빌드에 성공하는가. 그리고 우리는 그 사실을 어떻게 받아들여야 하는가.


1. 먼저 — LLM 비결정성의 구조

이 논의를 하려면 비결정성이 어디서 오는지부터 정확히 써야 한다.
LLM은 다음 토큰을 확률 분포로 예측한다. 어휘에 있는 모든 토큰마다 "이 자리에 이 토큰이 올 확률"이 매겨진다. 생성 단계에서 모델은 이 분포에서 하나를 샘플링한다. 이 샘플링 과정에 세 종류의 무작위성이 개입한다.

  • Temperature: 분포를 평평하게 할지 뾰족하게 할지 결정하는 스칼라. T=0이면 가장 확률 높은 토큰만 뽑고, T가 높아질수록 덜 확률 높은 토큰에도 기회가 간다.
  • Top-k / Top-p 샘플링: 후보를 상위 k개 또는 누적 확률 p까지로 제한.
  • Random seed: 같은 분포에서도 난수 시드가 다르면 다른 토큰이 뽑힌다.

"온도 0으로 하면 결정적이지 않나?"라는 질문이 자주 나온다. 답은 '아니오'다. 2024년 ACM Transactions on Software Engineering and Methodology에 실린 실증 연구는 ChatGPT의 코드 생성 비결정성을 829개 과제에 대해 측정했는데, 기본 설정에서는 같은 프롬프트에 대해 "모든 요청이 동일한 테스트 출력을 내는 과제 비율"이 CodeContests에서 24.24%, APPS에서 49%, HumanEval에서 52.44%에 불과했다. 온도를 0으로 내려도 비결정성은 줄어들지만 완전히 사라지지 않았다.
그 이유는 여러 층에 있다. GPU 병렬 연산의 부동소수점 누적 순서, batching에서의 다른 요청과의 상호작용, 모델 서빙 인프라의 라우팅 비결정성 등. 결국 LLM은 구조적으로 비결정적이고, 이는 "설정으로 끌 수 있는 버그"가 아니라 시스템의 본질적 속성이다.


2. 그런데도 빌드는 성공한다 — 두 번째 역설

여기가 흥미로워지는 지점이다. LLM이 비결정적이라는 사실은 10년 전이라면 "소프트웨어 엔지니어링 도구로는 실격"이라는 결론으로 가야 했다. 그런데 2026년 현실은 반대다.

  • Rakuten은 Opus 4.1로 오픈소스 프로젝트를 7시간 자율 리팩터링, 코드 품질 30% 개선.
  • Anthropic 내부 팀은 Claude Code로 PR 처리 속도 3배 개선.
  • 시니어 엔지니어들이 "코드의 100%는 AI가 쓰고, 내가 쓴 건 0%다"라고 말하고 있다.

"비결정적 생성"과 "프로덕션 품질"이 어떻게 공존 가능한가. 이게 이 글이 파고들 질문이다.


3. 해답의 첫 번째 층 — 분포가 충분히 뾰족하면 된다

가장 먼저 짚어야 할 지점은 "비결정적"과 "무작위"가 다르다는 것이다.
온도 0.7의 LLM이 def factorial(n): 다음에 생성할 토큰을 상상해보자. 확률 분포를 보면 \n if n 같은 토큰 시퀀스의 확률이 압도적으로 높고, 그 외의 선택지들은 사실상 0에 가깝다. 분포가 뾰족하다(peaked). 이런 경우 샘플링을 10번 해도 거의 매번 같은 토큰이 뽑힌다. 겉보기에 "결정적으로 보이는 비결정성"이다.
이 관찰을 실증적으로 확인한 것이 2024년의 AdapT Sampling 연구다. 연구자들은 코드 토큰을 "도전적 토큰(challenging tokens)"과 "자신 있는 토큰(confident tokens)" 으로 분류했다. 자신 있는 토큰은 분포가 뾰족해서 어떤 온도를 써도 같은 것이 뽑힌다. 도전적 토큰은 분포가 상대적으로 평평해서 실제로 다른 선택이 나온다.
흥미로운 발견은 도전적 토큰은 코드 블록의 첫 번째 위치에 집중적으로 나타난다는 것. 파이썬에서 이는 79.8%(HumanEval), 83.4%(MBPP), 82.5%(APPS)의 예측 난도를 보였다. 즉 LLM이 고민하는 지점은 "어떤 제어 구조를 쓸 것인가"(if/for/while/try)와 같은 구조 결정 순간이고, 그 이후의 토큰들은 상당히 결정론적으로 이어진다.
코드의 많은 부분이 "주사위"가 아니라 "자석"이다. 분포가 충분히 뾰족해서 샘플링해도 같은 결과로 끌려간다. 이게 첫 번째 해답이다.


4. 해답의 두 번째 층 — 원래 결정적이었던 적 없다

Paul Bernard의 2026년 2월 Medium 에세이 "Your Code Was Never Deterministic"이 찌르는 지점이 이것이다.
그는 이렇게 쓴다. 우리가 "결정적 코드"라고 부르던 것의 실체를 보자. 컴파일러는 결정적이다. 같은 소스 코드를 두 번 컴파일하면 같은 바이너리가 나온다. 맞다. 그런데 "같은 소스 코드"가 과연 결정적으로 쓰여졌는가는 다른 문제다.
10명의 시니어 개발자에게 "이 요구사항을 코드로 옮겨 주세요"라고 똑같이 말해보자. 10개의 다른 구현이 나온다. 변수명, 함수 분리 방식, 에러 처리 전략, 성능과 가독성의 트레이드오프 선택. 이 모든 것이 사람의 비결정성이다. 그런데 우리는 이걸 "비결정성"이라고 부르지 않았다. "스타일"이라고 불렀다.
더 중요한 지점. 우리가 실제 프로덕션에서 겪는 대부분의 장애는 컴파일러가 주사위를 굴려서 생긴 것이 아니라, 사람이 불완전한 의도를 코드로 옮기면서 생긴 것이다. 요구사항을 오해했거나, 엣지 케이스를 놓쳤거나, 동시성 가정을 틀리게 했거나.
Bernard의 핵심 통찰은 이것이다. "LLM은 비결정적이니까 믿을 수 없다"고 말할 때, 우리는 이전에 도대체 무엇을 믿고 있었는가? 테스트 커버리지를 믿고 있었나? 대부분 시스템에서 실질적 커버리지는 30~50% 수준이다. 코드 리뷰를 믿고 있었나? 진지한 리뷰와 LGTM 스탬프를 구분해야 한다. 우리가 믿었던 건 "코드가 동일 입력에 동일 출력을 낸다"는 컴파일러의 충실함이지, "코드가 올바르다" 는 보장이 아니었다.
그런데 컴파일러의 충실함은 LLM 코드에도 적용된다. LLM이 생성한 코드도 컴파일러가 결정적으로 처리한다. 같은 테스트를 돌리면 같은 결과가 나온다. 생성 단계는 비결정적이지만, 생성된 결과물은 여전히 결정적으로 검증 가능하다.
이것이 두 번째 해답이다. 비결정성의 위치가 "실행 시점"에서 "생성 시점"으로 이동했을 뿐이고, 우리는 둘 중 후자를 훨씬 잘 다룰 수 있다.


5. 해답의 세 번째 층 — 검증이 생성을 잡는다

가장 근본적인 해답은 여기 있다. 현대 AI 코딩 워크플로우는 "생성-검증 루프"로 설계되어 있다.
Claude Code, GPT-5 Codex, Cursor가 작동하는 방식을 생각해보자.

  1. 모델이 코드를 생성한다 (비결정적).
  2. 그 코드가 컴파일된다 (결정적).
  3. 타입 체커가 돌아간다 (결정적).
  4. 린터가 돈다 (결정적).
  5. 테스트 스위트가 실행된다 (결정적).
  6. 실패가 있으면 그 정보가 다시 모델에 피드백된다.
  7. 모델이 수정한 코드를 재생성한다 (비결정적).
  8. 루프가 수렴할 때까지 반복.

이 루프에서 비결정성의 역할은 "탐색(exploration)"이다. 모델이 여러 후보 구현을 시도해볼 수 있게 해주는 도구. 결정적(T=0) 모델은 한 번 막히면 계속 같은 곳에서 막힌다. 비결정적 모델은 같은 프롬프트에서 다른 경로를 시도해볼 수 있고, 그중 하나가 검증을 통과하면 그게 답이다.
이 구조가 진화적 알고리즘과 비슷하다는 점에 주목할 필요가 있다. 유전 알고리즘은 무작위 변이와 결정적 선택을 조합해서 해를 찾는다. 변이 단계는 비결정적이어야 한다 — 그래야 새로운 해공간을 탐색할 수 있다. 선택 단계는 결정적이어야 한다 — 그래야 나쁜 해가 걸러진다.
Claude Code 같은 에이전틱 코딩 도구는 정확히 이 구조다. 모델은 변이를 만들고, 테스트와 타입 시스템이 선택을 한다. 최종 산출물의 품질은 모델의 비결정성이 아니라 검증 시스템의 엄격함에 의해 결정된다.
이 관점으로 보면 우리가 AI 시대에 개발 인프라에 투자해야 할 것이 바뀐다. "모델을 더 정확하게 만드는 것"보다 "검증을 더 촘촘하게 만드는 것" 이 레버가 된다.


6. 한 층 더 — 명세는 변하지 않는다

Bernard의 에세이가 말하는 더 깊은 층이 있다. "명세"의 역할.
AI 코딩 파이프라인에서 개발자가 하는 일은 무엇인가. 명세를 쓴다. 자연어로, 구조화된 acceptance criteria로, property-based test로, 실행 가능한 스펙으로. 이걸 LLM이 구현으로 옮긴다. 구현은 run마다 다를 수 있다. 하지만 명세는 다르지 않다.
검증 인프라가 명세 층에 anchoring되어 있으면, 구현의 변동성은 더 이상 무서운 것이 아니다. 내부 구현 디테일일 뿐이다. 레지스터 할당이나 분기 예측처럼, 한 추상화 층 위로 올라갔을 때 보이지 않게 되는 종류의 디테일.
이 비유는 정확하다. 1970년대 개발자는 어셈블리로 레지스터 할당을 직접 했다. C 컴파일러가 등장하면서 레지스터 할당은 컴파일러가 상황에 따라 다르게 하는 내부 디테일이 됐다. 개발자는 더 이상 레지스터를 보지 않는다. 레지스터 할당의 "비결정성"(컴파일러 버전에 따라, 최적화 플래그에 따라 다른 결과)은 문제가 되지 않는다. 왜냐하면 C 명세가 그대로 유지되기 때문이다.
AI 코딩이 만드는 추상화의 도약도 같은 구조다. 개발자가 명세를 쓰고, 구현은 AI가 매번 다르게 할 수 있다. 명세 층에서 검증만 제대로 되면 구현의 변동은 무관한 내부 디테일이 된다.
이 틀에서 보면 "AI가 쓴 코드를 한 줄 한 줄 리뷰해야 한다"는 주장은 1970년대 C 개발자에게 "레지스터 할당을 한 줄 한 줄 리뷰해야 한다"고 말하는 것과 같다. 아래 층위에서의 결정을 위 층위에서 감시하려는 시도. 추상화의 이점을 스스로 포기하는 것.


7. 반론과 그 반론에 대한 반박

여기까지 읽은 독자의 머릿속에 떠오르는 반론들이 있을 것이다. 하나씩 다룬다.

반론 1: "테스트 코드도 코드다. 그걸 누가 검증하나?"

맞다. 그런데 비대칭이 존재한다. 명세는 프로덕션 코드보다 훨씬 짧고, 훨씬 선언적이며, 훨씬 변경이 드물다. 10,000줄의 구현을 생성하기 위한 명세는 200줄일 수 있다. 200줄은 사람이 리뷰할 수 있다. Property-based test로 쓰면 더 짧아진다. 리뷰해야 할 표면적 자체가 50배 줄어든다.

반론 2: "LLM이 테스트도 같이 생성한다. 같이 틀릴 수 있다."

타당한 지적이다. 하지만 해결책은 있다. 테스트와 구현을 다른 세션에서, 다른 모델로, 다른 프롬프트로 생성하는 것. 한 Claude가 테스트를 쓰고, 다른 Claude가 그 테스트를 통과하는 구현을 쓴다. 두 인스턴스가 공통으로 틀리려면 훨씬 정교한 체계적 오류가 필요하다. 이는 다수 인스턴스 합의(N-of-M consensus)와 구조적으로 같다.

반론 3: "미세한 엣지 케이스는 테스트로 못 잡는다."

사실이다. 하지만 이건 AI 시대 이전에도 사실이었다. 사람이 쓴 코드도 엣지 케이스에서 터진다. 오히려 AI는 알려진 엣지 케이스 패턴에 대한 기억이 사람보다 넓다. HackerNews의 C++/Qt 프로젝트 사례에서 Claude Code가 20분에 해결한 UI 버그는 숙련된 사람이 2시간 걸린 것이었다. 엣지 케이스 커버리지에서 AI가 사람보다 낫다는 증거가 이미 쌓이고 있다.

반론 4: "생성 루프가 무한히 돌 수 있다."

구조적 문제가 있을 때 그렇다. 해결은 타임아웃과 예산 설정, 그리고 "실패 시 사람에게 에스컬레이션"이다. 이는 마이크로서비스에서 circuit breaker가 하는 역할과 같다. 무한 루프는 아키텍처 결함이지 AI 코딩의 본질이 아니다.

반론 5: "그래도 나는 한 줄씩 읽어야 마음이 놓인다."

그건 정서의 문제지 엔지니어링의 문제가 아니다. 공감은 한다. 나도 처음에 Claude Code가 쓴 PR을 마우스로 한 줄씩 긁으며 읽었다. 그런데 그 시간이 실제로 버그를 잡았는가? 대부분은 아니었다. 마음이 편해지는 효과는 있었지만, 품질 보장 효과는 테스트와 타입 시스템이 이미 다 하고 있었다. 불안은 대체 가능한 의식이지, 대체 불가능한 검증이 아니다.


8. 실무자의 관점 — 이 통찰을 어떻게 써야 하나

이론을 썼으니 실무로 내려간다. 이 통찰이 2026년 개발자의 하루하루에 어떻게 적용되는가.

8.1 투자 우선순위를 다시 짜라

"좋은 AI 코딩 환경"의 조건이 바뀌었다.

  • 빠른 타입 체커 (rustc, tsc, mypy)
  • 빠른 테스트 러너 (pytest-xdist, jest parallel, cargo nextest)
  • 엄격한 린터 (ruff, eslint, clippy)
  • Property-based testing 프레임워크 (hypothesis, fast-check, proptest)
  • 계약 기반 테스트 (contract testing, consumer-driven contracts)

이 인프라가 허약한 프로젝트에서는 AI 코딩의 ROI가 낮다. 강한 프로젝트에서는 ROI가 폭발적이다. "검증 인프라에 투자한 프로젝트가 AI 시대의 수혜를 독점한다."

8.2 명세 쓰기가 핵심 역량이 된다

개발자의 시간 배분이 바뀐다.

  • 과거: 70% 구현, 20% 설계, 10% 테스트
  • AI 시대: 20% 명세 작성, 30% 검증 설계, 30% 리뷰·디버깅, 20% 통합

이 변화를 빨리 받아들이는 개발자일수록 경쟁력이 크다. "타이핑 속도가 더 이상 개발 속도와 상관없다." 대신 "명확한 명세를 언어로 옮기는 능력"이 중요해진다.

8.3 Writer/Reviewer 패턴을 기본 워크플로우로

지금 가장 효과적인 AI 코딩 방법론 중 하나는 두 개의 모델 인스턴스를 병렬 운영하는 것이다.

  • Writer: 구현을 생성하는 역할. 빠르고 공격적으로.
  • Reviewer: 별도 세션에서 구현을 평가하는 역할. 의심 많고 보수적으로.
  • Human: 두 결과를 보고 최종 판단.

Claude Code와 GPT-5 Codex를 병렬로 돌리며 서로를 교차 검증하게 하는 기법은 2026년 현재 시니어 엔지니어들 사이에서 표준이 되어가는 중이다. 한 모델의 비결정성을 다른 모델의 비결정성으로 상쇄시키는 설계.

8.4 명세 자체를 재사용 자산으로

한 번 잘 쓴 명세는 모델이 바뀌어도, 언어가 바뀌어도, 프레임워크가 바뀌어도 재사용 가능하다. Ruby on Rails로 구현한 명세를 Spring Boot로 다시 구현하는 것이, 명세가 있을 때는 AI에게 일이고 없을 때는 사람에게 대공사다.


9. 한 걸음 물러서서 — 추상화의 역사에 놓기

이 모든 변화가 어디에 놓이는지 역사적 관점으로 보면 선명해진다.
소프트웨어 역사에서 추상화는 한 방향으로만 올라왔다.

  • 기계어 → 어셈블리: 니모닉과 레이블의 도입.
  • 어셈블리 → C: 레지스터, 스택, 메모리 할당을 컴파일러에 맡김.
  • C → Java/Python: 수동 메모리 관리를 GC에 맡김.
  • Java/Python → 프레임워크: HTTP 파싱, ORM, 의존성 주입을 프레임워크에 맡김.
  • 프레임워크 → 서비스: 인증, 결제, 검색을 외부 서비스에 맡김.

각 단계에서 아래 층의 "결정"들이 비가시적이 됐다. 그 비가시성은 처음엔 두려운 것이었다. "GC가 언제 돌지 모르는데 어떻게 실시간 시스템을 짜냐" "ORM이 어떤 SQL을 쏠지 모르는데 어떻게 성능을 보장하냐." 이런 질문들이 그 시대의 엔지니어들을 실제로 밤잠 못 이루게 했다.
답은 매번 같았다. 한 층 위에서 추상화를 붙들면 아래 층의 비결정성은 관리 가능한 내부 디테일이 된다. GC의 동작은 JVM 튜닝 파라미터로 관리되고, ORM의 쿼리는 모니터링으로 잡히고, 서비스의 장애는 circuit breaker로 격리된다.
2026년 AI 코딩도 같은 위치에 있다. 개발자가 명세를 쓰고, 구현의 비결정성은 검증 층에서 흡수된다. 처음에는 두렵지만, 한 세대 지나면 "이 레벨에서 왜 걱정했지?"라고 돌이켜볼 추상화 층이 된다.


10. 마무리 — 주사위는 구르고, 빌드는 선다

글의 처음으로 돌아간다. 주사위를 굴려 나온 코드가 왜 빌드에 성공하는가.
세 층의 답을 정리하면.
첫 번째 층: 분포가 뾰족해서 대부분의 토큰은 사실상 결정적이다. "비결정적"이라는 꼬리표가 주는 인상만큼 혼란스럽지 않다.
두 번째 층: 우리가 "결정적 코드"라고 불렀던 것들도 인간의 비결정성 위에 서 있었다. 위치만 바뀐 것이지 전에 없던 성질이 생긴 것이 아니다.
세 번째 층: 현대 AI 코딩은 비결정적 생성과 결정적 검증의 루프로 설계된다. 생성의 변동성은 탐색의 도구이고, 품질은 검증의 엄격함에서 온다.
이 셋을 합쳐 보면 역설은 역설이 아니다. 비결정성은 문제가 아니라 구조다. 그리고 그 구조 위에서 잘 작동하는 개발 인프라를 가진 팀이 2026년의 승자가 될 것이다.
Paul Bernard가 에세이 끝에 던진 질문을 조금 바꿔 다시 쓴다.

LLM이 비결정적이라서 쓸 수 없다고 말하기 전에, 우리는 이전에 무엇을 믿고 있었는지 먼저 물어야 한다. 우리의 테스트? 우리의 리뷰? 우리의 직감? 그중 무엇도 "결정적"이지 않았다. 우리가 진짜로 믿었던 건 단 하나, 검증 가능한 명세를 세우는 우리의 능력이었다. 그 능력은 AI 시대에도 그대로 우리 것이고, 오히려 레버가 훨씬 길어진다.

주사위는 계속 구른다. 그런데 빌드는 선다. 그 사실이 우리에게 말하는 것은 이것이다. 우리는 이미 답을 알고 있었다. 답의 위치가 바뀌었을 뿐이다.


참고 자료

LIST

 

 

1.

프로그래밍 방식 트랜잭션 정리

프로그래밍 방식 트랜잭션 관리는 다음과 같은 경우에 유리합니다.

  • 세밀한 제어가 필요한 경우:
  • 복잡한 비즈니스 로직이나 특정 조건에 따라 트랜잭션의 경계를 유연하게 제어해야 할 때.
  • 예외 처리 로직 구현:
  • 일부 로직만 롤백하거나, 특정 예외 발생 시 다른 보상 로직을 추가로 구현할 때 유용합니다.

 

@Transactional 어노테이션은 여러 속성을 지원합니

  • propagation: 트랜잭션 전파 규칙을 설정합니다. (예: REQUIRED, REQUIRES_NEW 등)
  • isolation: 트랜잭션 격리 수준을 설정합니다.
  • readOnly: 읽기 전용 트랜잭션으로 최적화할 수 있습니다.
  • rollbackFor: 롤백 대상으로 처리할 예외를 지정할 수 있습니다.

 

2. 동시성 문제 개념 및 사례 분석

 

이번 학습에서는 동시성 문제의 개념과 필요성을 이해하며, 데이터 정합성이 깨질 수 있는 다양한 상황과 실무에서 발생할 수 있는 문제를 다룹니다. 이를 위해 주문 처리나 재고 관리 시스템과 같은 사례를 통해 동시성 문제가 실제로 어떤 영향을 미치는지 살펴보고, Dirty Read, Non-Repeatable Read, Phantom Read, Lost Update와 같은 대표적인 동시성 문제의 동작 원리와 발생 조건을 학습합니다.

트랜잭션 격리 수준은 데이터 정합성시스템 성능 사이의 트레이드오프(Trade-off) 관계에 있습니다.

  • 대부분의 애플리케이션: Read Committed 수준으로 충분합니다. 데이터 부정합 문제의 가장 큰 원인인 Dirty Read를 막아주면서 좋은 성능을 보장합니다.
  • 데이터의 일관성이 중요한 경우: 하나의 트랜잭션 내에서 조회한 값이 변하지 않아야 한다면 Repeatable Read를 고려할 수 있습니다.
  • 극도의 정합성이 요구되는 경우: 동시성 저하를 감수하고서라도 데이터 불일치가 절대로 발생해서는 안 되는 시스템이라면 Serializable을 사용해야 합니다.

MySQL 격리 수준 변경

MySQL에서 트랜잭션 격리 수준을 설정하여 트랜잭션 간의 동작 방식을 제어할 수 있습니다. 격리 수준은 데이터베이스 전체(Global) 또는 특정 세션(Session)에 대해 설정 가능합니다.

 

 Dirty Read

READ UNCOMMITTED

격리 수준에서 발생하는 심각한 데이터 부정합 문제입니다. 이 문제를 해결하기 위해서는 최소

READ COMMITTED

이상의 격리 수준을 사용해야 합니다

 

 

이번 학습에서는 트랜잭션 격리 수준의 개념과 종류를 이해하고, 이를 활용해 동시성 문제를 해결하는 방법을 배웁니다. 먼저 트랜잭션의 ACID 원칙을 복습하며, 격리 수준이 데이터 정합성과 시스템 성능에 미치는 영향을 살펴봅니다.

Read Uncommitted,Read Committed,Repeatable Read,Serializable

등 네 가지 격리 수준의 특징과 동작 방식을 학습하며, 각각이 Dirty Read, Non-Repeatable Read, Phantom Read와 같은 동시성 문제에 어떤 영향을 미치는지 분석합니다. 이를 바탕으로 적절한 격리 수준을 설정해 동시성 문제를 해결하는 방법을 익히고, MySQL 실습을 통해 각 격리 수준에서 발생하는 문제를 재현하고 해결 과정을 경험함으로써 데이터 정합성을 유지하는 실질적인 기술을 습득합니다.

 

 

4. DB 락 메커니즘 이해와 실습

이번 학습에서는 데이터베이스 락의 개념과 이를 활용해 데이터 충돌을 방지하는 방법을 배웁니다. 비관적 락(Pessimistic Lock)은 데이터를 선점해 충돌을 방지하는 방식이며, 낙관적 락(Optimistic Lock)은 충돌이 발생했을 때 버전 검사를 통해 문제를 해결합니다. 두 방식의 차이와 활용 사례를 살펴보고, Shared Lock과 Exclusive Lock, 테이블 레벨 락과 행(Row) 레벨 락의 특징을 통해 락의 다양한 종류를 이해합니다. 또한, 비관적 락을 활용한 재고 관리와 낙관적 락을 이용한 데이터 충돌 방지 실습을 통해 락을 효과적으로 적용하는 방법을 익힙니다.

 

. 비관적 잠금(Pessimistic Locking)

  • 데이터를 업데이트하기 전에 해당 행을 잠금(Lock) 처리하여 다른 트랜잭션이 접근하지 못하도록 합니다.

비관적 락은 여러 사용자가 동시에 동일한 데이터를 수정하려고 시도할 때 데이터 정합성을 유지하는 데 적합합니다.

 

. 낙관적 잠금(Optimistic Locking)

  • 데이터에 version 필드를 추가하고, 업데이트 시 조건으로 사용합니다.
  • 트랜잭션이 변경한 데이터가 다른 트랜잭션에 의해 변경되었는지 확인하고, 충돌 시 롤백 처리합니다.

데이터를 읽을 때는 락을 사용하지 않으며, 데이터를 수정할 때 버전 번호를 통해 충돌 여부를 판단합니다.

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Lock;

import jakarta.persistence.LockModeType;

public interface ProductRepository extends JpaRepository<Product, Long> {

  @Lock(LockModeType.PESSIMISTIC_WRITE)
  @Query("SELECT p FROM Product p WHERE p.id = :id")
  Optional<Product> findByIdForUpdate(@Param("id") Long id);

  @Lock(LockModeType.PESSIMISTIC_WRITE)
  Optional<Product> findFirstByName(String name);    
}

 

LIST

앞선 두 편에서 같은 결론에 도달했다. 웹스퀘어도, OZ Report도, Claude Code로 "기술적으로 비슷한 것"은 만들 수 있다. 그러나 실제 제품을 대체하는 길은 거의 닫혀 있다. 이유는 기술이 아니라 다른 층위에 있기 때문이다.
그 다른 층위가 정확히 무엇인지 이번 글에서 본격적으로 분해한다. 기술적 해자와 제도적 해자가 어떻게 다르게 작동하는가. 왜 한국  솔루션 시장은 글로벌 표준과 다른 방식으로 잠겨 있는가. 그리고 AI 시대에 이 구조는 어떻게 변하거나 유지될 것인가.
이 글은 두 가지 용도로 읽힐 수 있다. 첫째, 개발자가 자기 커리어의 판을 이해하는 지도. 둘째, 스타트업이나 해외 솔루션 진출 기획자가 한국 시장 진입 전략을 짜기 위한 분석.


1. "해자"라는 단어의 정확한 의미

워런 버핏이 대중화한 "economic moat"는 원래 중세 성 주변의 해자(垓字)에서 온 비유다. 성을 직접 공격하는 것보다 해자 때문에 접근 자체가 불가능해진다. 경제적 맥락에서도 같다. 경쟁자가 "제품 품질"로 싸우기 전에 "시장에 진입하는 것 자체"가 막혀 있는 구조가 해자다.
해자의 유형은 일반적으로 다섯 가지로 분류된다.

  1. 규모의 경제 — 크기가 커질수록 단위 비용이 내려감
  2. 네트워크 효과 — 사용자가 많아질수록 제품 가치가 올라감
  3. 전환 비용(Switching Cost) — 바꾸는 데 드는 돈·시간·리스크가 큼
  4. 무형 자산 — 브랜드, 특허, 규제 라이선스
  5. 비용 우위 — 독점적 자원 접근, 지리적 이점

한국 SI 솔루션의 해자는 이 중 3번 전환 비용과 **4번 무형 자산(특히 규제·관계)**에 압도적으로 치우쳐 있다. 그런데 이게 해외 엔터프라이즈 SaaS의 해자와 질적으로 다른 구조라는 점이 핵심이다.


2. 글로벌 SaaS의 해자 vs 한국 SI의 해자

2.1 글로벌 SaaS의 해자는 "기술 + 네트워크"

Salesforce, ServiceNow, Snowflake, Datadog 같은 글로벌 엔터프라이즈 SaaS의 해자는 주로 기술과 네트워크 효과 위에 서 있다.

  • 플랫폼 확장성: AppExchange, ServiceNow Store 같은 서드파티 마켓플레이스
  • 데이터 중력: 한 번 모인 데이터가 이동하기 어렵다는 물리적 제약
  • API 생태계: 수많은 통합(integration) 파트너가 제품 가치를 확장
  • 지속적 R&D 투자: 매출의 20~30%를 제품에 재투자하는 규모의 힘

이 해자는 측정 가능하고, 벤치마크 가능하며, 시간이 지나면 복제도 가능하다. 실제로 지난 10년간 수많은 "Salesforce 킬러"들이 등장해서 일부 영역을 뺏어갔다. 해자가 있지만 "공격받을 수 있는 해자"다.

2.2 한국  솔루션의 해자는 "제도 + 관계 + 도메인"

반면 한국 엔터프라이즈 소프트웨어의 해자는 다른 세 층으로 구성된다.
1층 — 제도적 해자 (Institutional Moat)

  • 정부·공공·금융 RFP의 레퍼런스 요구사항
  • 한국인터넷진흥원(KISA), 국가정보원, 금융감독원, 개인정보보호위원회의 규제·인증 체계
  • GS인증, CC인증, K-ISMS, ISMS-P 같은 국내 전용 인증
  • 조달청 나라장터 등록 자격
  • 전자금융거래법, 전자문서법, 개인정보보호법의 업계별 시행령

2층 — 관계적 해자 (Relational Moat)

  • 대형 SI(삼성SDS, LG CNS, SK C&C, 포스코DX, 현대오토에버)와 솔루션 벤더의 오랜 파트너십
  • 금융·공공 IT 기획 담당자와 벤더 영업의 10년 단위 네트워크
  • 장애 발생 시 한국어로 즉시 연결되는 기술지원 체계
  • 차세대 프로젝트 제안 참여 경험 자체가 다음 프로젝트의 자격 요건이 되는 구조

3층 — 도메인 해자 (Domain Moat)

  • 한국식 행정 양식 (직인 영역, 한글 금액 표기, 주민번호·사업자번호 마스킹 규칙)
  • 한국 금융권 특화 처리 (예수금 계산 규칙, 금리 적용 방식, 세무 원천징수)
  • 공공 업무 특화 워크플로우 (결재선, 감사 대응, 민원 응대)
  • 한국어 IME·폰트·검색 엣지 케이스 수만 가지
  • 20년간 프로젝트 장애 대응으로 축적된 암묵지

이 3층 해자가 서로를 강화한다는 점이 결정적이다. 제도가 관계를 만들고, 관계가 도메인 축적을 가능하게 하고, 도메인 축적이 다시 제도 대응의 근거가 된다. 이 사이클을 바깥에서 뚫고 들어가는 것은 거의 불가능에 가깝다.


3. 사례 분석 — 이 해자가 실제로 어떻게 작동하는가

3.1 티맥스소프트의 코어뱅킹 독주

2007년 국민은행 차세대 프로젝트 당시 디지털데일리 보도에 이미 핵심 구절이 나온다. "현재 코어뱅킹 솔루션 시장을 독주하고 있는 티맥스소프트와 경쟁할 코어뱅킹 솔루션업체는 딱히 없는 상황이다."
2026년 현재도 이 구조는 크게 달라지지 않았다. 시중은행의 차세대 프로젝트에 글로벌 코어뱅킹 솔루션(Temenos, FIS, Oracle FLEXCUBE)이 들어오려 해도 — 심지어 한국IBM이 Temenos를 가지고 복수 제안을 해도 — 최종 선정에서 국내 솔루션이 이기는 일이 반복된다.
이 구조를 기술로 설명할 수 있을까? 어렵다. Temenos T24는 전 세계 150개국 이상의 은행에서 쓰이는 검증된 제품이다. 그런데도 한국 시중은행 코어뱅킹에서 이기지 못한다.
이유는 3층 해자가 모두 작동하기 때문이다.

  • 제도: 금융감독원 검사 대응, 전자금융거래법 해석, 한국은행 금통위 결정 반영 같은 국내 규제 대응이 필수.
  • 관계: 은행 IT 기획팀과 10년 넘게 쌓인 신뢰 네트워크.
  • 도메인: 한국 금융상품의 복잡성(적금·예금 해지 이자 계산, 주택청약저축 규정, 연금저축 세제 혜택) 전수 내장.

Claude Code로 코어뱅킹 시스템을 "만드는" 것은 기술적으로 가능하다. 그러나 위 3층을 뚫고 신한·국민·하나은행에 납품하는 것은 완전히 다른 게임이다.

3.2 인스웨이브 웹스퀘어의 지속성

웹스퀘어가 수년간 "곧 React로 대체될 것"이라는 예측을 듣고도 국내 금융·공공 UI 시장에서 지위를 유지하는 이유도 같은 구조다.

  • 제도: 웹 접근성 인증(KWCAG), 국가정보원 보안 적합성 검증 통과.
  • 관계: 대형 SI의 공통 컴포넌트로 자리 잡아 프로젝트 관리 표준화에 기여.
  • 도메인: 한글 IME 처리, 공인인증서 연동, 한국식 폼 검증 로직 전부 내장.

React는 기술적으로 우월할 수 있어도, 이 3층 중 어느 하나도 자동으로 해결해주지 않는다. 오히려 React로 프로젝트를 하면 위 3층을 각 SI 업체가 매번 새로 구축해야 한다. 그게 리스크다.

3.3 포시에스 OZ Report의 4천여 고객사

OZ Report 공식 자료에 따르면 "대법원, 행정안전부, 국토교통부 등 국내 주요 정부부처와 공공기관의 대국민 서비스는 물론 금융, 제조, 유통, 통신, 교육 등에서 4천여 개 이상의 고객사를 확보"했다.
이 숫자가 갖는 의미는 단순 시장 점유율이 아니다. 4천여 고객사 각각이 OZ Report 기반으로 수십~수백 개의 리포트 템플릿을 축적하고 있다. 한 번 쌓인 템플릿 자산은 솔루션을 바꾸는 순간 재작성해야 한다. 공공기관 한 곳에서만 수백 개, 전국 단위로는 수만 개다. 이 전환 비용이 해자의 거대한 폭을 만든다.


4. AI 시대의 해자 — 무엇이 흔들리고 무엇이 굳어지는가

이제 본격적인 질문. Claude Code, Cursor, Copilot 같은 AI 도구가 일반화된 2026년 이후, 이 3층 해자는 어떻게 변할까.

4.1 흔들리는 것 — 도메인 해자의 일부

세 층 중 가장 먼저 침식되는 쪽은 도메인 해자의 표층이다.
한국식 양식 구현, 한글 IME 엣지 케이스, 공인인증서 연동 같은 패턴은 이미 충분한 공개 정보가 축적되어 있다. Claude Code가 학습 데이터에서 이 패턴들을 상당 부분 내재화하고 있고, 앞으로 더 많이 흡수할 것이다.
그 결과:

  • "한국식 행정 양식을 지원하는 오픈소스 리포트 엔진"을 만드는 비용이 급격히 낮아진다.
  • "한글 IME를 올바르게 처리하는 Grid 컴포넌트"가 React 생태계에 빠르게 등장한다.
  • SI 업체가 차세대 프로젝트에서 상용 솔루션을 선택하지 않고 자체 구현 + AI 가속을 택할 여지가 확대된다.

즉, 표층의 도메인 지식은 공공재화된다.

4.2 흔들리지 않는 것 — 심층 도메인과 제도 해자

그러나 도메인 해자의 심층부는 흔들리지 않는다.

  • 특정 은행의 특정 금융상품이 30년 동안 겪은 법령 변경 이력
  • 공공기관 A의 민원 응대 워크플로우가 B에게는 없는 이유
  • 2018년 개인정보보호법 개정 시 각 산업이 대응한 패턴의 차이
  • 금융감독원 검사 지적 사항에 대한 업계별 대응 문서들

이 지식은 공개된 적이 없고 앞으로도 공개되지 않는다. 각 SI 업체의 프로젝트 방법론 문서, 솔루션 벤더의 대응 매뉴얼, 현업 담당자의 머릿속에만 있다. Claude Code가 학습할 데이터가 애초에 존재하지 않는다.
제도 해자는 더 확고해진다.

  • AI 시대에 규제 기관은 "AI로 만든 시스템의 검증"을 새로 요구할 것이다.
  • 그 새 규제를 최초로 통과시킨 벤더가 다음 10년의 시장을 잡는다.
  • 이는 AI 도구로 기술을 복제해도 시장에 진입할 수 없는 새로운 제도 해자의 등장이다.

4.3 강화되는 것 — 관계 해자의 역설적 확대

이게 가장 반직관적인 지점이다. AI가 기술을 상향 평준화할수록 관계 해자는 더 중요해진다.
이유는 간단하다. AI로 누구나 비슷한 수준의 기술 역량을 갖출 수 있으면, 구매자 입장에서 의사결정 기준은 "누구를 믿을 수 있는가" 로 이동한다. 제품이 대동소이하면 관계가 결정적 차이가 된다.
실제로 금융·공공 RFP에서 점점 더 많이 등장하는 조항들:

  • "해당 솔루션을 3년 이상 유지보수한 경험이 있는 파트너사"
  • "본 기관 유사 규모 프로젝트 수행 이력"
  • "솔루션 벤더의 국내 법인 및 24시간 기술지원 체계"

AI 시대에 기술 차별화가 어려워질수록 이런 조항의 무게가 커진다. 이것이 "관계 해자의 역설적 확대" 다.


5. 그래서 무엇을 해야 하는가 — 세 가지 포지셔닝 전략

이 해자 분석을 바탕으로 한국 SI 업계 개발자·기획자가 취할 수 있는 전략은 크게 세 가지다.

5.1 전략 A — "해자 안쪽"에서 AI 레버리지

기존 대형 SI나 솔루션 벤더에 속해 있다면, 가장 합리적인 포지셔닝은 3층 해자를 받침점으로 두고 AI를 지렛대로 삼는 것이다.

  • 제도·관계·심층 도메인은 회사가 20년간 쌓은 자산.
  • 자신은 그 자산 위에서 AI로 개발·운영 생산성을 2~5배 올린다.
  • 결과: 같은 자원으로 더 많은 프로젝트, 더 높은 마진.

이게 티맥스·인스웨이브·포시에스 같은 국내 벤더들이 자사 제품에 AI를 내장하는 이유다. 그리고 그 제품을 쓰는 SI 개발자 입장에서도, "AI를 활용해 같은 솔루션으로 더 빠르게 프로젝트를 종료시키는 사람" 이 가장 경쟁력 있다.

5.2 전략 B — "해자 주변부"에서 틈새 제품

솔루션 벤더가 직접 만들기엔 우선순위가 낮은 주변부 도구는 여전히 공백이 많다.

  • 웹스퀘어 XML → React JSX 마이그레이션 도구
  • OZ Report 템플릿 자동 생성기
  • JasperReports → 상용 리포트 툴 변환기
  • 특정 벤더 솔루션의 QA 자동화 SaaS
  • 차세대 프로젝트 RFP 자동 분석 도구

이 영역은 솔루션 벤더가 만들면 "자기 제품을 대체하는" 것이라 만들지 않고, 순수 오픈소스는 한국 도메인을 충분히 이해하지 못해서 만들기 어렵다. 그 사이의 작은 틈에 1~3인 팀이 만들 수 있는 B2B 도구들이 숨어 있다.
개발자가 도메인 이해 + AI 구현 역량을 조합할 수 있다면 이 전략이 가장 유리하다. 프리모아 청각재활 프로그램 외주나 ASAT 프로젝트가 본질적으로 이 구조에 해당한다.

5.3 전략 C — "새 해자"를 만드는 신규 도메인

기존 솔루션이 장악하지 못한 신규 도메인에서는 한국 특화 제도·도메인 해자를 처음부터 구축할 수 있다.
최근 10년간 이런 영역이 몇 개 있었다.

  • 모바일 간편결제 (카카오페이, 토스)
  • 클라우드 기반 회계·세무 (이파이낸셜그룹, 삼쩜삼)
  • 전자계약 (모두싸인, 이싸인온)
  • AI 기반 법률·의료 (로앤굿, 코어라인소프트)

이들 중 성공한 곳들은 공통적으로 "기술"이 아니라 "제도 적응 + 업계 관계 + 특화 UX" 로 해자를 만들었다. Claude Code 같은 AI 도구는 이 해자 구축의 속도를 끌어올리지만, 해자 자체를 만들지는 않는다.


6. 개발자 개인 관점 — 이 해자 구조가 커리어에 의미하는 것

지금까지 이야기한 구조는 개발자 개인 커리어에도 직접적 함의를 가진다. 세 가지로 정리한다.

6.1 "기술 스택 지상주의"의 한계

"React가 웹스퀘어보다 나은가" "Spring Boot가 코어뱅킹 솔루션보다 우월한가" 같은 논쟁은 기술적으로는 의미 있지만 산업 현실에서는 핵심을 비껴간다. 기술만 놓고 보면 오픈소스가 더 나은 경우가 많다. 그런데 시장은 3층 해자 위에서 움직인다.
이 지점을 이해하는 것과 못 하는 것 사이에는 큰 차이가 있다. 이해하면 "기술 스택"과 "솔루션"의 차이를 구분할 수 있고, 둘 중 어느 쪽에 자신의 커리어를 배팅할지 결정할 수 있다.

6.2 "깊이 있는 도메인 경험"의 희소성

해자 분석이 알려주는 가장 실용적인 교훈은 이것이다. 한국 엔터프라이즈 소프트웨어에서 가장 희소한 자원은 "한국어로 도메인을 이해하는 개발자" 다.
글로벌 수준의 코딩 능력을 가진 개발자는 점점 많아진다. AI로 더 많아진다. 그러나 "금융감독원 검사 대응 경험이 있으면서 Spring Boot로 코드를 잘 짜는 사람"은 여전히 드물다. 리포트 툴 개발 경험과 Next.js를 동시에 다룰 수 있는 사람은 더 드물다.
이 교집합이 AI 시대 개발자의 진짜 경쟁력이다.

6.3 "해자 안쪽에서 밖으로 vs 밖에서 안쪽으로"

커리어 궤적을 생각할 때, 해자를 넘나드는 방향성이 중요하다.

  • 해자 안쪽에서 밖으로: SI에서 시작해 해자 바깥의 스타트업·해외 기업으로 이동. 해자 안쪽의 지식을 외부 가치로 전환. 포지셔닝 난이도 높지만 성공 시 희소 가치 큼.
  • 해자 밖에서 안쪽으로: 스타트업·해외 경험을 가지고 SI 솔루션 벤더에 합류. 관계 해자 진입이 어려우나, AI 활용·제품 기획에서 차별화 가능.
  • 해자 내부 이동: SI 내부에서 도메인·관계 자산을 누적. 안정적이지만 산업 전체가 구조 변화를 겪을 때 리스크 분산이 어려움.

정답은 없다. 다만 자기가 어느 방향으로 움직이고 있는지를 의식하는 것 자체가 중요하다.


7. 시리즈를 닫으며 —  2026년에 서 있는 자리

3편에 걸친 이 시리즈의 공통 결론은 이것이다.

Claude Code가 한국 생태계를 "대체"하지 않는다. 그러나 Claude Code를 어떻게 쓰느냐에 따라 한국 생태계 안에서 각 참여자의 위치가 재편된다.

웹스퀘어를 대체하는 UI 플랫폼을 만드는 것도, OZ Report를 대체하는 리포트 툴을 만드는 것도 AI 시대에 현실적인 목표가 아니다. 그 이유는 기술의 문제가 아니라 제도·관계·도메인 해자의 구조 때문이다.
그러나 같은 해자가 AI를 활용하는 개발자에게는 오히려 기회를 연다. 해자 안쪽에 있는 개발자는 AI를 지렛대로 생산성을 몇 배 끌어올릴 수 있고, 해자 주변부를 공략하는 개발자는 기존 벤더가 만들지 않는 틈새 제품을 만들 수 있으며, 해자 바깥에서 새 영역을 여는 개발자는 한국 도메인 특화 제품으로 새로운 해자를 구축할 수 있다.
어느 쪽을 선택하든, 핵심은 해자가 작동하는 원리를 정확히 이해하는 것이다. 기술 스택의 우열만 보는 사람에게는 한국 SI 시장이 비합리적으로 보이고, 제도·관계·도메인을 함께 보는 사람에게는 그 안의 논리가 보인다. 그 차이가 앞으로 10년의 커리어를 가르는 분기점이 될 것이라고 본다.
Claude Code는 우리에게 더 긴 지렛대를 쥐여줬다. 그 지렛대를 어느 받침점 위에 놓을지는 여전히 우리가 결정한다. 그 결정이 곧 커리어의 방향이다.


참고 자료

LIST

앞선 글에서 "Claude Code로 웹스퀘어를 만들 수 있을까" 를 다뤘다. 결론은 "기술적 구축은 가능한 구간이 있지만, 대체라는 프레임이 잘못됐다"였다. 그럼 같은 질문을 리포트 툴에 던지면 어떨까. 

결론부터 말하면 리포트 툴은 UI 플랫폼보다 "만들기"가 더 어렵다. 직관과 반대다. 프레임워크보다 리포트 툴이 기능적으로 단순해 보이지만, 실제로는 "픽셀 퍼펙트 출력"과 "법적·규제적 유효성"이라는 두 벽이 훨씬 높다. 이 글은 왜 그런지, 그리고 Claude Code가 이 맥락에서 실제로 만들 수 있는 것과 없는 것을 엔지니어링 관점에서 분해한다.


1. OZ Report가 실제로 해결하는 문제

OZ Report를 "PDF 생성기"로 생각하면 이 글을 읽을 필요가 없다. 실체는 훨씬 복잡하다.

1.1 네 가지 핵심 축

축 1 — Designer (개발자 도구) ActiveX 시절부터 시작해 현재는 HTML5 기반으로 전환된 데스크톱 디자이너. 픽셀 단위 레이아웃, 밴드(Title/PageHeader/ColumnHeader/Detail/ColumnFooter/PageFooter/Summary) 구조, 차트·바코드·QR 컴포넌트, 다국어 리소스 바인딩, 데이터 쿼리 연결 등을 드래그 앤 드롭으로 구성.

축 2 — Server (실행 엔진) .ozr(리포트 템플릿)과 .odi(데이터 정의)를 받아 .ozd(최종 문서)로 렌더링하는 서버. 데이터 스트리밍, 디스크·메모리 캐싱, 데이터 압축을 통해 네트워크 부하를 최소화. 스케줄러 기능으로 무인 리포팅도 지원.

축 3 — Viewer (런타임 클라이언트) ActiveX → Flash → Applet → HTML5 → Silverlight → WPF로 시대에 따라 진화해온 다종 뷰어. "원 소스 멀티 플랫폼(OSMP)" — 하나의 리포트 파일로 PC 웹/모바일 앱/데스크톱 앱 모두에서 동일하게 출력되는 것이 핵심 가치.

축 4 — 보안·전자문서 레이어 QR 바코드로 위변조 검증, PDF/XLSX/HTML 저장 시 암호 설정, DRM 연동, 인쇄 제한, 캡처 방지. OZ Report의 "전자문서 보안 출력 시스템"은 특허 등록된 기술이다.

1.2 "리포트 툴"의 본질적 제약

리포트 툴이 다른 UI 프레임워크와 결정적으로 다른 지점은 출력물이 법적·규제적 효력을 가진다는 사실이다.

  • 대법원·행정안전부·국토교통부의 대국민 서비스에서 쓰이는 OZ Report 출력물은 공문서다.
  • 은행의 예금 거래명세서, 증권사의 매매 확인서는 법정 보관 의무가 있는 금융 기록물이다.
  • 건강보험공단·국민연금공단의 증명서는 위변조 시 문서 위조죄 대상.
  • 의료기관의 진단서, 처방전은 의료법상 법정 서식.

이 영역은 "그려지면 되는" 것이 아니다. 특정 폰트가 특정 좌표에 mm 단위로 정확히 찍혀야 하고, 용지 경계 안에서 잘림 없이 완전해야 하며, 여러 페이지에 걸친 양식도 장번호·총장수가 정확히 일치해야 한다. 금융감독원 검사에서 "출력물 폰트가 지정된 규격과 다르다"는 이유로 지적받는 일이 실제로 일어난다.

1.3 왜 JasperReports가 있는데 OZ Report가 팔릴까

JasperReports는 Jaspersoft Studio라는 Eclipse 기반 디자이너를 가진 오픈소스 리포트 툴이고, .jrxml 확장자의 XML 템플릿을 쓴다. 밴드 구조(Title, PageHeader, ColumnHeader, Detail, ColumnFooter, PageFooter, Summary)는 OZ Report와 거의 동일한 개념이다. 기능만 보면 유사한 것도 많다. Pentaho, BIRT, Helical Insight 같은 오픈소스 대안도 있다.

그런데도 국내 SI에서 OZ Report, Crownix, UbiReport 같은 상용 툴이 계속 팔린다. 이유는 세 가지다.

  1. 기술지원 — 특히 한국어 지원. 장애가 터졌을 때 한국어로 전화 걸 수 있는 벤더의 가치는 엔터프라이즈 환경에서 계산하기 어려울 정도로 크다.
  2. 한국식 양식의 내장. 한글 세로쓰기, 원화 표기(₩), 주민등록번호 포맷, 사업자등록번호 하이픈 처리, 한국 세무 양식(부가세, 원천징수) 같은 도메인 특성이 라이브러리 수준에서 최적화되어 있다.
  3. 레퍼런스 장벽. 금융권·공공 RFP에서 "해당 리포트 툴 기반 프로젝트 5건 이상 구축 경험"을 요구하는 경우가 많다. 오픈소스로는 이 요건을 충족하는 SI 파트너사를 찾기 어렵다.

포시에스의 OZ Report는 이런 배경에서 "4천여 개 이상의 고객사"를 확보했다. 대법원, 행정안전부, 국토교통부를 포함한 공공, 금융, 제조, 유통, 통신, 교육 전 산업에 깔려 있다.


2. Claude Code가 만들 수 있는 리포트 툴의 범위

2.1 "PDF 생성 라이브러리 래퍼" 수준 (1~3일)

이게 가장 간단한 구간이다.

  • 스택 후보: React-PDF, Puppeteer, Playwright, jsPDF, PDFKit, @react-pdf/renderer, Weasyprint
  • 구조: JSON 데이터 + JSX/HTML 템플릿 → PDF 출력
  • 기능: 제목/본문/표/차트/페이지 번호 기본 지원

Claude Code로 이 정도는 하루면 돌아간다. 포트폴리오용 "리포트 엔진" 데모로는 충분하다. 단, 이건 리포트 툴이 아니라 "프로그래밍 가능한 PDF 생성기" 다. 개발자만 쓸 수 있다는 뜻.

2.2 "밴드 기반 리포트 엔진" 수준 (2주~2개월)

한 단계 올라가면 JasperReports 최소 호환 수준이 목표가 된다.

  • JSON/YAML/XML 기반 템플릿 포맷 정의
  • 밴드 구조(Title/PageHeader/Detail/PageFooter/Summary)
  • 데이터 바인딩 표현식 (예: ${row.amount}, ${SUM(amount)})
  • 페이지 분할 로직 (한 밴드가 용지 경계를 넘어갈 때 처리)
  • 간단한 차트(Recharts·Chart.js 래핑)
  • 바코드·QR (기존 라이브러리 래핑)
  • HTML/PDF/XLSX 내보내기

Claude Code의 에이전틱 리팩터링(예: Rakuten 사례의 7시간 자율 리팩터링, 코드 품질 30% 향상)과 Writer/Reviewer 패턴으로 이 범위는 1~2개월에 완성 가능하다고 본다. 단, 이 범위에서 만들어진 결과물은 "교육 목적의 자체 리포트 엔진" 정도. 실제 금융권·공공 프로젝트에 투입하기엔 한참 모자란다.

2.3 "디자이너 + 엔진 + 뷰어" 전체 스택 (1년 이상)

OZ Report 축에 근접하려면 세 개의 독립 제품을 각각 만들어야 한다.

  • 디자이너(1인 기준 4~6개월): 드래그 앤 드롭 캔버스, 속성 인스펙터, 밴드·컴포넌트 팔레트, 수식 에디터, 미리보기, 데이터 소스 관리 UI
  • 서버 엔진(3~4개월): 템플릿 파싱, 데이터 쿼리 실행, 페이지 네이션, 캐싱, 포맷 변환(PDF/XLSX/HTML), 스케줄러
  • 뷰어(2~3개월): 브라우저·iOS·Android에서 동일 출력, 줌·검색·인쇄·저장, 접근성(ARIA)

각각이 6개월~1년짜리 독립 제품이다. Claude Code로 속도를 2~5배 끌어올려도 혼자 1년 이상 걸리는 규모다. 그리고 여기까지 와도 여전히 OZ Report 8.0의 기능적 서브셋일 뿐이다.


3. 넘을 수 없는 벽 — 리포트 툴 특유의 난점

3.1 벽 1: 픽셀 퍼펙트의 악몽

브라우저 기반 PDF 생성의 근본 문제는 렌더링 결과가 환경에 따라 미세하게 달라진다는 것이다.

  • 같은 Chrome이라도 버전별로 폰트 렌더링 hinting이 다름
  • Puppeteer 기반 PDF는 사용자 OS에 설치된 폰트에 의존
  • 프린터 DPI와 화면 DPI 차이에 따른 좌표 변환
  • CSS @page 규칙의 브라우저별 구현 차이
  • 다단 레이아웃에서 텍스트 분할 처리 차이

OZ Report가 30년 가까이 쌓은 노하우의 상당 부분이 이 지점에 있다. 같은 .ozd 파일이 Windows/Mac/Linux/iOS/Android 어디에서 열리든 픽셀 단위로 동일해야 하는 요구사항. 이건 엔지니어링으로 "노력하면 되는" 영역이 아니라 렌더링 엔진을 자체 구현하는 수준의 투자가 필요한 문제다.

Claude Code로 "대체로 비슷하게 보이는 PDF"는 만들 수 있다. 그러나 "금감원 검사에서 지적받지 않는 수준의 픽셀 퍼펙트" 는 다른 차원의 문제다.

3.2 벽 2: 한국식 양식의 암묵지

국내 리포트 툴 개발자가 해결한 문제들의 예시:

  • 행정 문서의 "직인 영역": 용지 오른쪽 하단에 직인이 찍힐 공간을 확보하는 관례
  • 한글 숫자 표기: "일금 삼천오백만원정" 같은 한글 금액 표기
  • 주민번호·계좌번호 마스킹: 규정에 따라 다른 마스킹 규칙 적용
  • 세금계산서 양식의 필수 필드 배치: 국세청 고시 양식 준수
  • 한글 세로쓰기와 섞어쓰기: 한자 혼용, 영문 혼용 처리
  • 다국어 폰트 폴백: 한글+영문+숫자가 섞인 문장의 폰트 통일성

이런 요구사항이 라이브러리 기능으로 내장되어 있지 않으면 매 프로젝트마다 개별 구현해야 한다. SI 현업에서 웹스퀘어와 OZ Report를 묶어서 쓰는 이유가 여기 있다 — "한국식 업무 요구사항을 기본 제공" 하기 때문.

Claude Code가 이 암묵지를 모를 수는 없다. 한국어 학습 데이터에 상당 부분 있다. 다만 요구사항을 수집해서 기능으로 구체화하는 작업 자체가 여러 해의 현장 경험을 필요로 하는 일이다.

3.3 벽 3: 전자문서의 법적 유효성

OZ Report의 "전자문서 보안 출력 시스템 및 그 방법" 특허는 단순 기술이 아니라 법적 유효성 체계를 함께 설계한 결과다.

  • 위변조 방지 QR을 포함한 출력물의 해시 검증
  • 인증서 기반 전자 서명과의 통합
  • DRM 연동을 통한 인쇄·캡처 제어
  • 감사 로그(누가 언제 어떤 문서를 출력했는가) 체계
  • 보안 등급별 출력 제한(기밀/대외비/공개)

이 레이어는 기술 구현만으로 끝나지 않는다. 법무팀·규제 대응팀·감사팀이 함께 설계하고 인증 기관의 검증을 받아야 상용 제품으로 출시 가능하다. 전자금융거래법, 전자문서법, 개인정보보호법, 각 산업별 규제가 얽혀 있다.

Claude Code는 여기서 코드는 짤 수 있어도, 법적 유효성을 보증할 수는 없다.

3.4 벽 4: OSMP의 비대칭

OZ Report의 핵심 가치 중 하나는 "원 소스 멀티 플랫폼". 하나의 리포트 파일로 PC 웹, iOS 앱, Android 앱, 데스크톱 네이티브까지 동일 출력.

이 요구사항을 만족하려면 각 플랫폼의 렌더링 엔진을 별도로 유지해야 한다. iOS의 Core Graphics, Android의 Skia, 웹의 Canvas/SVG, 데스크톱 GDI 각각에 대해 동일한 출력을 보장하는 테스트 스위트가 필요하다.

Claude Code 혼자서는 이 크로스 플랫폼 QA를 감당할 수 없다. 이건 수십 명의 QA 엔지니어가 수년간 축적한 테스트 케이스의 문제다.


4. 그럼에도 AI가 실질적으로 가치 있는 지점

앞 섹션이 "만들지 마라"로 읽혔을 수 있다. 그게 의도가 아니다. 목표를 재정의하면 Claude Code가 리포트 툴 맥락에서 엄청난 레버리지를 만든다.

4.1 리포트 템플릿 자동 생성기

리포트 툴 개발자들이 가장 많이 하는 반복 작업은 "요구사항 문서나 엑셀 양식을 보고 리포트 템플릿을 처음부터 그리는 일" 이다. 이걸 Claude Code로 자동화하는 것이 가장 현실적인 AI 가치.

  • 엑셀 양식 업로드 → JasperReports .jrxml 자동 생성
  • PDF 샘플 업로드 → 밴드 구조 역추출 → OZ Report 템플릿 생성
  • "월별 매출 현황 리포트를 만들어줘. 컬럼은 월/매출/전년비/성장률" → 완성된 템플릿

이 도구 하나만 잘 만들어도 SI 리포트 개발자 1인의 생산성이 3~5배 오를 수 있다. 인스웨이브도 웹스퀘어에 AI 기능을 넣고 있다고 공식 사이트에서 밝혔다. 포시에스 역시 OZ Report에 유사한 기능을 추가하고 있을 가능성이 높다.

4.2 레거시 리포트 마이그레이션 도구

공공·금융 SI에서 주기적으로 발생하는 작업이 "차세대 프로젝트에서 기존 리포트 수백 개를 새 플랫폼에 이식" 하는 것이다. 예를 들어:

  • Crystal Reports → OZ Report 이식
  • OZ Report → JasperReports 이식 (오픈소스 전환 시)
  • ActiveX 기반 레거시 → HTML5 기반 신규

이 작업은 단가가 높다. 리포트 1건당 수십만 원 선에서 견적이 나오기도 한다. Claude Code로 자동 변환기를 만들면 월 수백 건 규모의 이식 작업을 자동화할 수 있다. 이건 정확하게 Claude Code의 장점(코드 변환, 패턴 일치, 대량 작업)이 맞아떨어지는 영역이다.

4.3 리포트 QA 자동화

리포트 QA는 "수동 눈으로 확인"이 절대 다수다. "이 숫자가 맞나? 레이아웃이 틀어지지 않았나?" 같은 검증을 사람이 일일이 눈으로 본다. 이 영역에 AI를 붙이면:

  • 기준 리포트 이미지와 신규 출력 이미지 비교 → 시각적 회귀 테스트
  • 데이터 값 불일치 자동 검출
  • 페이지 경계 넘침·잘림 자동 감지
  • 한글 폰트 렌더링 이상 탐지

Claude Code + 비전 모델 조합으로 리포트 회귀 테스트 자동화 플랫폼을 만들 수 있다. 이것도 B2B SaaS로 가능한 범위다.

4.4 "자연어 → 리포트" 도구

업무 사용자가 "지난달 강남 지점 매출을 지점별·상품군별로 보여줘"라고 말하면 자동으로 리포트 템플릿과 쿼리를 생성하는 도구. Power BI, Tableau 같은 대기업 제품들이 이미 비슷한 기능을 내놓고 있고, Claude Code는 여기에 오픈소스 구현을 붙일 수 있는 적절한 도구다.


5. 그래서 OZ Report 같은 리포트 툴을 만들 수 있을까

질문을 다시 세 구간으로 나눠서 답한다.

구간 1 — "리포트 엔진 데모" (1~2개월) JasperReports 최소 호환 수준의 자체 엔진. Claude Code로 충분히 가능. 오픈소스로 공개하면 개발자 포트폴리오로서는 훌륭.

구간 2 — "소규모 내부용 리포트 솔루션" (6개월~1년, 1~3인 팀 기준) 디자이너 + 엔진 + 뷰어 세 축의 최소 기능 구현. 사내 관리 리포트, 작은 SaaS의 리포트 기능 정도는 가능. Claude Code가 팀 생산성을 크게 끌어올리면 이 구간이 과거 대비 반으로 줄어든다.

구간 3 — "OZ Report 수준의 상용 제품" (현실적으로 불가능) 20년치 도메인 축적, 4천여 고객사 레퍼런스, 법적 유효성 검증, OSMP 크로스 플랫폼 QA, 한국식 양식 암묵지 — 이 조합을 AI로 단축할 방법이 없다. 이건 기술 스택이 아니라 제도·관계·운영의 문제다.

그래서 웹스퀘어 글에서 던진 것과 같은 질문으로 이 글도 닫는다.

  • ❌ "Claude Code로 OZ Report를 만들 수 있을까?"
  • ✅ "Claude Code로 OZ Report 개발 생산성을 2배 올릴 수 있을까?"
  • ✅ "Claude Code로 레거시 리포트 수백 건의 이식 비용을 1/10로 낮출 수 있을까?"
  • ✅ "Claude Code로 엑셀 양식에서 리포트 템플릿을 자동 생성하는 도구를 만들어 SaaS로 팔 수 있을까?"

6. 한국 관점의 실용적 결론

6.1 리포트 툴 "만들기"는 개인 프로젝트로 부적절

웹스퀘어 클론보다도 이쪽이 더 단호하다. 리포트 툴은 제품이 아니라 제도에 가까운 영역이라, 개인이 만들어서 상업적으로 성공할 확률이 극히 낮다. 포트폴리오로 "리포트 엔진 구현" 자체는 의미 있지만, "OZ Report 대체품을 만들었다"는 주장은 신뢰성을 오히려 해칠 수 있다.

6.2 리포트 툴 "주변부"는 기회가 많다

반면 리포트 툴 주변의 자동화 도구는 공백이 많다.

  • OZ Report 템플릿 자동 생성 CLI
  • Crystal → Jasper 마이그레이션 봇
  • 리포트 출력 회귀 테스트 자동화
  • 엑셀 양식 → 리포트 템플릿 변환기
  • 리포트 메타데이터 관리 대시보드

이 영역들은 상용 리포트 툴 벤더가 직접 만들 인센티브가 낮고(고객에게 팔 제품의 주변부라), 개인 개발자가 Claude Code로 만들어서 상용 라이선스 형태로 제공할 여지가 있다.

 


7. 마무리 — "리포트 툴"이라는 장르의 본질

웹스퀘어와 OZ Report를 두 편에 걸쳐 분석하며 공통 결론에 도달했다. 국내 엔터프라이즈 SI 솔루션의 진짜 해자(moat)는 기술이 아니라 제도·관계·도메인 축적이다. Claude Code 같은 강력한 AI 코딩 도구가 이 해자를 "기술적으로" 우회하는 길은 거의 없다.

그런데 이 결론이 비관적이기만 한 것은 아니다. 오히려 반대 방향으로 해석하면:

"기술 복제가 해자를 무너뜨리지 못한다면, 우리는 해자 안쪽에서 AI 도구로 생산성을 몇 배 올려 시장 점유율을 지키거나 확장할 수 있다."

이것이 포시에스, 인스웨이브, 미라콤아이앤씨 같은 국내 벤더들이 자사 제품에 AI를 통합하는 전략의 배경이다. 그리고 그 벤더의 제품을 쓰는 SI 개발자 입장에서도, AI를 대체 도구가 아닌 증강 도구로 쓰는 쪽이 훨씬 합리적이다.

결국 질문은 이렇게 정리된다. 20년간 축적된 한국 엔터프라이즈 소프트웨어 생태계 안에서, AI 도구를 어떻게 쓰는 개발자가 다음 20년을 선도할 것인가. 그 답을 찾는 사람에게는 웹스퀘어와 OZ Report는 "대체해야 할 낡은 것"이 아니라, "레버리지의 받침점"이다.


참고 자료

LIST

+ Recent posts