GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

LLM은 영상을 만들지 않습니다.
영상은 결정론 코드가 만들고,
LLM은 사람의 말을 코드로 번역할 뿐입니다.

 

우선 작업 결과부터 보고 가겠습니다.

프롬프트

우리 토리 소개 영상 만들자. 먼저 토리가 가만히 있는 장면 하나를 가지고 토리 얼굴을 천천히 클로즈업으로 5초 보여주고 아래에 텍스트로 우리 토리를 소개합니다를 띄워줘. 그 다음 애견카페에서 뛰어 노는 모습 10초 편집해서 아래에 똥꼬발랄한 우리 토리 띄워주고 산책하고 비오는 날 뛰고 밤에 달리는 신나게 뛰어노는 모습을 10초 보여주면서 산책도 좋아하는 우리 토리를 아래에 텍스트로 띄워줘. 그리고 마지막으로 하이파이브 하는 장면으로 우리 예쁜 토리입니다. 텍스트를 아래에 띄우면서 끝내줘.



"영상 편집 AI면, LLM한테 영상이랑 요청을 통째로 주면 되는 거 아냐?"

 

매번 같은 질문을 하게 됩니다. 요즘 멀티모달 모델은 영상도 보고 말도 알아듣습니다. 하지만 이 출발점에는 함정이 있습니다. 같은 요청을 두 번 돌리면 다른 결과가 나오고, 프레임 단위 타이밍은 어긋나고, 왜 그렇게 잘렸는지 아무도 설명할 수 없습니다. 그래서 역할을 분리했습니다.

 

LLM에게는 '의미'만 맡긴다.
픽셀, 프레임, 타이밍은 전부 결정론 함수가 만진다.

 

지난 편에서 강아지를 찾고(검출), 다시 알아보고(재식별), 움직임을 재는(모션) 첫 레이어를 세웠습니다. 이번 편은 그 위에 올라가는 나머지, 동작 판별(네번째 단계), 편집 실행(여섯번째 단계), 그리고 자연어 한 문단이 완성 숏폼이 되는 통합 테스트까지 다룹니다. 다섯번째 단계(내레이션)를 왜 건너뛰었는지도요. 결론부터 말하면 이번 구간에서 배운 가장 값진 교훈은 이것입니다. LLM은 못 하는 일에서 아주 자신있게 틀립니다.

LLM Interpreter

4. 동작을 판별하다 - LLM에게 오지선다를

네번째 단계는 동작 판별입니다. 모션 곡선이 "움직인다"까지는 말해주지만, 그게 달리기인지 걷기인지 점프인지는 모릅니다. 여기서 처음으로 로컬 LLM(Gemma)이 등장합니다.

 

방식이 재미있었습니다. LLM에게 "이 강아지 뭐 하고 있어?"라고 서술형으로 묻지 않습니다. 오지선다 객관식(점프·달리기·걷기·앉기·엎드림)으로 묻고, 답의 첫 토큰 확률(logprob)을 읽습니다. 동적 동작들의 확률 합과 정적 동작들의 확률 합을 비교해 "동적인가 정적인가"부터 확정합니다. 서술형 답변은 파싱도 채점도 어렵지만, 객관식 확률은 숫자라서 파이프라인이 다룰 수 있습니다.

 

여기에 안전장치를 겹칩니다. LLM 판정이 모션 곡선과 일치하고 확신도 높을 때만 확정(commit), 아니면 모름(uncertain)으로 폴백. 네번째 단계는 혼자 다 맞혀야 하는 시험이 아니라, 애매하면 모르겠다고 말해도 되는 과정입니다. 모호함은 상위 레이어가 흡수합니다.

 

정답 라벨은 이번에도 사람이 만들었습니다. 모션 곡선으로 구간 후보를 자동으로 잘라 사람은 동작 이름만 채우게 해 품은 줄였지만 라벨 자체를 AI에게 맡기지는 않았습니다. 모델이 모델을 채점하는 자기참조가 되니까요.

 

결과는 확정된 구간의 동적/정적 정확도 1.00, 모름 비율 0.15(나쁜 footage 제외 시 0.07). 게이트(정확도 >= 0.90, 모름 <= 0.25) 통과입니다. 정직하게 적어두면 100%는 테스트 영상의 경계가 뚜렷했던 덕도 있습니다. 쉬운 건 확정하고 애매한 건 물러선다. 설계가 의도대로 작동했습니다.


5. 다섯번째 단계를 건너뛰다 - 완결된 경로부터

원래 설계에는 저작 모드가 두 개 있습니다.

  • 모드 A (편집만) - "인트로 몇 초, 다음에 노는 장면…" 같은 편집 요청만으로 자막 숏폼을 만듭니다. TTS가 필요 없습니다.
  • 모드 B (내레이션) - 대본을 쓰면 TTS 음성이 깔리고, 영상이 거기에 맞춰 편집됩니다. 다섯번째 단계(TTS)가 필요합니다.

그래서 일단 다섯번째 단계는 스킵하였습니다. 우선순위에서 뺐습니다. 이유는 단순합니다. 모드 A만으로도 처음부터 끝까지 완결된 제품 경로 하나가 성립하기 때문입니다. 부품을 하나 더 얹어 두 경로를 어설프게 구현하는 것보다 한 경로를 끝까지 뚫어 raw 폰 영상 -> 완성 숏폼을 증명하는 쪽이 먼저였습니다.


6. 자연어를 편집으로 - 번역가와 실행자

여섯번째 단계는 편집 실행입니다. 편집 요청 한 문단이 들어오면 LLM이 이를 블록 시퀀스로 번역합니다. "먼저 인트로, 그 다음 노는 장면 10초, 마지막에 하이파이브" — 블록마다 어떤 구간을(동적/정적), 몇 초로, 어떤 자막과 전환으로 쓸지가 담긴 편집 계획이 됩니다.

 

그리고 LLM의 일은 거기서 끝납니다. 계획을 받아 실제로 자르고 붙이고 줌하는 것은 전부 ffmpeg와 OpenCV, 즉 결정론 코드입니다. 같은 입력이면 같은 출력이 나옵니다.

 

실행 쪽에서 제일 고생한 건 줌이었습니다. 점차 확대되는 줌을 ffmpeg 내장 필터로 만들면 정수 반올림 때문에 화면이 부들부들 떨렸습니다. 세 번 갈아엎은 끝에 OpenCV로 float 보간 + 서브픽셀 렌더링을 직접 구현해 떨림을 잡았습니다. 한글 자막은 이 ffmpeg 빌드에 drawtext가 없어 PIL로 텍스트 이미지를 만들어 overlay로 태웠는데, 오히려 폰트와 스타일이 자유로워졌습니다. 출력은 분석용 저해상도가 아니라 원본 화질에서 다시 추출하고, 가로 / 세로가 섞인 폰 영상은 9:16 세로 + 블러 배경으로 통일했습니다.

 

편집 감각도 코드에 넣었습니다. 같은 장면을 잘게 쪼개 여기저기 흩뿌려진 영상이 나왔는데 어지러웠습니다. 소스당 한 컷, 길게. 이런 선호도 결정론 규칙이라 일관되게 지켜졌습니다.


7. 통합 테스트 - LLM의 자신감은 증거가 아니다

이제 전체를 처음부터 끝까지 돌립니다. 폰에서 갓 꺼낸 raw 영상 9개, 그리고 편집 요청 한 문단, "우리 토리를 소개합니다. 가만히 있는 인트로, 카페에서 노는 장면 10초, 산책, 비 오는 날, 밤에 달리기 합쳐서 10초, 하이파이브로 엔딩." 목표는 개발용 정답 라벨 없이 검출 결과만으로 도는 26.5초 세로 숏폼입니다.

 

당연히 한 번에 되지 않았습니다. 이 디버깅이 이번 편의 하이라이트입니다.

 

하이파이브가 사라졌습니다. 하이파이브는 앉아서 발을 드는 재주라, 오지선다(점프, 달리기, 걷기, 앉기, 엎드림)에는 자리가 없습니다. LLM은 가장 가까운 "앉기"로 분류했고, 동적 장면을 찾던 엔딩 블록은 빈손이 됐습니다. 수정은 라벨셋에 재주(묘기) 축을 추가하고, 편집 계획이 "묘기 장면"을 직접 고를 수 있게 했습니다. 이건 모델의 문제가 아니라 라벨셋 설계의 문제였습니다.

 

진짜 문제는 다음이었습니다. "가만히 있는 인트로"에 밤에 질주하는 영상이 들어왔습니다. 어둡고 흐릿한 밤 크롭을 LLM이 "정적"이라고 판단했습니다. 그것도 확신도 0.99로 판정한 겁니다. 처음엔 LLM과 모션 곡선의 판정을 확신도만큼 가중 투표시키는 절충안을 시도했다가 기각했습니다. LLM이 스스로 헷갈려할 때(확신 0.51)는 투표가 작동하지만, 확신하며 틀릴 때(0.99)는 투표가 오히려 오답에 힘을 실어줬습니다.

 

핵심 통찰은 이겁니다. LLM이 보는 건 정지된 크롭입니다. 흐릿하게 찍힌 강아지 사진 한 장으로는 뛰는 중인지 서 있는지 원리적으로 알 수 없습니다. 반면 모션 곡선(세번째 단계)은 카메라 움직임을 빼고 프레임 사이의 변화를 직접 잽니다. 그래서 절충 대신 역할을 완전히 분리했습니다.

축 담당 이유
동적/정적 (모션) 모션 곡선 전담 움직임의 직접 측정
동작 이름·재주 (의미) LLM 전담 선명한 한 장의 의미 판정

LLM은 밤 크롭을 여전히 오판합니다. 하지만 이제 그 오판이 결과를 흔들지 못합니다. 못 하는 일을 아예 맡기지 않았으니까요. 재테스트 결과는 인트로, 카페 두 영상, 밤 질주 포함, 하이파이브 엔딩까지 전부 정상. 서로 물고 물리던 문제들이 역할 분리 하나로 동시에 풀렸습니다.

 


맺음말

여기까지로 자연어 한 문단에서 완성 숏폼으로 파이프라인이 생겼습니다.

  • 동작 판별은 오지선다 확률로 묻되, 애매하면 물러서게 하고,
  • 내레이션은 완결 경로를 먼저 증명하기 위해 뒤로 미루고,
  • 편집 실행은 LLM이 번역한 계획을 결정론 코드가 재현 가능하게 렌더하고,
  • 통합 테스트에서 LLM이 못 하는 축(모션)을 측정에게 완전히 넘겼습니다.

지난 편의 교훈이 눈보다 정답지였다면 이번 편은 그 연장입니다. LLM의 확신이 보는 것과 같이 증거가 아닙니다. 측정할 수 있는 것은 측정으로 판정하고, LLM에게는 의미의 번역만 맡긴다. 이러한 설계가 흐릿한 밤에 질주하는 영상 하나에서 실제로 값을 했습니다.

 

남은 것은 뒤로 미룬 다섯번째 단계(TTS 내레이션), 수동으로 적고 있는 장면 태그의 자동화, 그리고 이 파이프라인을 제품으로 감싸는 일입니다. 다음 편에서 이어가겠습니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


다음글

 

로컬 TTS(mlx-audio)로 영상 타임라인 컨트롤 - Narration First AI

GitHub - chessire/shelter-puppyContribute to chessire/shelter-puppy development by creating an account on GitHub.github.com목소리를 얹는 순간,화면이 음성에 맞춰 늘어나고,타임라인은 음성이 컨트롤합니다. 우선 작업 결과

chessire.tistory.com

이전글

 

YOLO11 해상도 함정: 골든셋으로 검증 - Measurement First AI

좋은 모델이 좋은 파이프라인을 만드는 게 아닙니다.좋은 채점 기준이,모델을 갈아끼울 수 있게 해줍니다. "AI 영상 분석이면 일단 모델부터 돌려보는 거 아냐?" 틀린 말은 아닙니다. YOLO를 붙이

chessire.tistory.com

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com

 

 

GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

좋은 모델이 좋은 파이프라인을 만드는 게 아닙니다.
좋은 채점 기준이,
모델을 갈아끼울 수 있게 해줍니다.

 

"AI 영상 분석이면 일단 모델부터 돌려보는 거 아냐?"

 

틀린 말은 아닙니다. YOLO를 붙이면 박스가 강아지를 따라다니고, 뭔가 되는 것처럼 보입니다. 하지만 이 출발점은 정작 중요한 질문을 가립니다. 모델을 바꿨을 때 정말 좋아진 건지, 그냥 좋아 보이는 건지, 눈으로 데모 몇 개를 비교하는 방식으로는 영영 답할 수 없습니다.

 

"모델을 채점할 '정답지'부터 만들고 그 다음에 모델을 붙인다."

 

지금 만들고 있는 것은 영상 속에서 강아지 한 마리를 찾아내고, 계속 따라가고, 언제 움직이고 언제 쉬는지 이해하는 파이프라인입니다. 이번 편은 그 첫 레이어(검출, 재식별, 모션)를 위 순서로 세운 과정만 다룹니다. 결론부터 말하면, 네 번의 큰 결정 중 두 번은 정답지가 제 직관을 기각했습니다.

 

Model vs Golden-Set

0. 골든셋을 세우다 - 채점기 검증

골든셋(golden set)은 "정답이 확정된 표준 데이터셋"입니다. 한 번 만들어두면 어떤 모델을 넣든 어떤 파라미터를 바꾸든 항상 같은 표준으로 채점해 객관적으로 비교할 수 있습니다.

 

이 단계에서 만든 것이 정확히 이것입니다.

  • fail-case가 서로 다른 영상 5개를 골라 CVAT로 라벨링한 정답(GT)
  • 단계별 메트릭 라이브러리 (IDF1, MOTA, ID-switch, miss rate …)
  • 언제든 한 줄로 재채점하는 eval / report CLI
  • 그리고 채점기 자체를 검증하는 합성 단위테스트 30개

마지막 항목이 핵심입니다. 메트릭이 맞아야 모듈을 검증할 수 있습니다. 손으로 계산한 답과 채점기 출력이 일치하는지부터 확인했습니다. 자가 휘어 있으면, 그 뒤의 모든 측정이 무의미하니까요.

 

비유하면 골든셋은 운전면허 시험 코스입니다. 정답이 정해진 코스에서 합격하면 도로에서는 시험관 없이 달립니다. 골든셋은 "이 자동화를 믿어도 되는가?"를 증명하는 일회성 관문이지 운영 중에 반복하는 절차가 아닙니다.


1. 검출을 올리다 - 직관 검증

첫번째 단계는 YOLO + ByteTrack 기반의 검출, 추적입니다. 베이스라인(yolo11n)을 골든셋으로 채점해 보니 병목은 검출 누락이었습니다. 개선점은 두 군데 였습니다. 모델 체급을 올리거나(11n -> 11m), 입력 해상도를 올리거나(1536).

 

직관적으로는 둘 다 도움이 될 것 같습니다. 결과는 달랐습니다.

골든셋 채점 결과판정
체급 ↑ (11n→11m) miss rate 0.40→0.27, ID-switch 48→23, IDF1 0.44→0.54 채택
해상도 ↑ (1536) 오히려 악화 기각

해상도를 올렸는데 왜 나빠질까요? 640으로 학습된 YOLO는 1536 입력에서 크고 가까운 물체를 오히려 놓쳤습니다. 학습 분포와 추론 분포가 어긋나기 때문입니다. "해상도가 높을수록 잘 보인다"는 사람이 보는 눈의 이야기일 뿐, 모델의 이야기가 아니었습니다.

 

골든셋이 없었다면 어땠을까요. 데모 영상 몇 개를 보고 "1536이 더 선명한데?"라며 역효과 나는 설정을 채택했을 가능성이 큽니다. 눈으로 하는 비교는 확증 편향일 뿐입니다.

 

또 하나의 발견은 역할 분리입니다. 검출 누락은 이 단계에서 잡되 같은 강아지를 계속 같은 강아지로 보는 정체성 문제는 다음 단계의 몫으로 넘겼습니다. 한 모듈에서 모든 걸 해결하려 하지 않았습니다.


2. 그 강아지를 다시 알아보다 - 애매하면 유저에게

두번째 단계는 re-ID입니다. 영상에 여러 마리가 나올 때, 그 중 주인공 강아지 한 마리를 화면에서 사라졌다 돌아와도 계속 같은 강아지로 인식해야 합니다.

 

구성은 세 가지입니다.

  1. DINOv2 임베딩 - 강아지의 외형을 벡터로.
  2. 시간 상호배제 - 같은 프레임에 동시에 등장하면 서로 다른 강아지라는 논리 제약. 임베딩이 헷갈려도 물리적으로 확정되는 정보입니다.
  3. 유저 1탭 - 애매하면 유저에게 묻습니다.

세 번째가 설계의 갈림길입니다. 생김새가 비슷한 두 마리는 임베딩으로 완벽하게 구분할 수 없었습니다 (실측 오부착 1/12). 이걸 모델 개선으로 끝까지 밀어붙이는 대신, 애매한 케이스는 카드 한 장을 보여주고 탭 한 번으로 확정받는 구조를 택했습니다. 목표는 영상당 2탭 미만, 실측은 1.75탭이었고 통과였습니다.

 

"AI가 다 한다"보다, "AI가 90%를 하고 나머지는 유저가 탭 한 번으로 끝낸다"가 더 정직하고, 실제로 더 잘 작동했습니다.

 

이 단계에서도 골든셋이 직관을 한 번 더 기각했습니다. 레퍼런스 이미지를 여러 장 쓰는 멀티레퍼런스 매칭이 당연히 나을 거라 가정했는데 채점 결과 단순 평균 임베딩이 최고였습니다. 가설을 세우고, 채점하고, 데이터가 기각하면 접는다. 이 사이클이 두 단계 연속으로 "당연해 보이는 개선안"을 걸러냈습니다.

 

부수입도 있었습니다. 두번째 단계의 정답은 첫번째 단계의 골든셋에서 자동으로 도출됐습니다. 추가 라벨링 0. 정답지를 제대로 만들어두면 이렇게 재활용됩니다.

(추후에는 이미지 검증 프로세스도 추가해 유저의 탭도 제거하게 됩니다.)


3. 움직임을 숫자로 - 카메라 보정

세번째 단계는 모션 곡선입니다. 주인공 강아지의 박스 안에서 프레임 차이를 계산해, 동적/정적 구간을 나눕니다.

 

가장 재미있던 문제는 강아지가 아니라 카메라였습니다. 손으로 찍은 영상은 손떨림과 팬(pan)이 섞여 있어서, 가만히 앉아 있는 강아지도 화면상으로는 계속 움직인다고 판정했습니다. 해법은 배경 특징점으로 카메라의 움직임을 추정해 빼는 것. 카메라를 빼고 나면 강아지의 움직임만 남습니다.

 

검증 결과, 앉아 있는 강아지는 모션 값 0.8(정적), 노는 강아지는 13.8(동적), 앉아 있다가 화면 밖으로 나가는 전환도 잡았습니다.

 

다만 정직하게 적어두면, 세번째 단계는 정식 게이트가 아니라 눈검증으로 진행했습니다. 동적/정적 분리가 육안으로 명확해서 v1은 이 수준으로 갈음했습니다. 모든 단계에 같은 무게의 검증을 거는 건 오버엔지니어링입니다. 검증 강도는 리스크에 비례해서 정했습니다.

 

마지막으로 두번째 단계와 세번째 단계를 합쳤습니다. 모션을 전체 화면이 아니라 두번째 단계의 주인공으로 묶은 박스 기준으로 계산했습니다. 같은 영상이라도 주인공이 누구냐에 따라 답이 달라집니다. dog0을 주인공으로 잡으면 "정지", dog1로 잡으면 "화면 밖으로 나감." 박스가 그 강아지를 프레임 내내 따라간다는 증거입니다.


맺음말

여기까지가 첫 레이어입니다.

  • 골든셋 하나로 이후 모든 결정을 채점하고,
  • 검출에서 직관 대신 숫자로 레버를 고르고,
  • 재식별은 애매한 나머지를 유저의 탭 한 번에 맡기고,
  • 모션은 카메라를 빼고 강아지만 남겼습니다.

겉으로는 그저 모델 네 개의 나열처럼 보입니다. 하지만 이 배치에는 일관된 선택이 하나 깔려 있습니다. 모델은 갈아끼우는 부품이고, 골든셋은 그 부품을 채점하는 기준이라는 것. 부품은 계속 바뀝니다. 기준을 먼저 세운 쪽만 부품을 바꿀 수 있습니다.

 

다음 단계는 검출한 후, 추적된 강아지가 무엇을 하고 있는지(앉기, 뛰기, 놀기)를 판별하는 일입니다. 여기서부터 로컬 LLM이 등장합니다. 픽셀의 세계에서 의미의 세계로 넘어가는 경계인데, 그 경계선을 어디에 긋는지는 다음 편에서 풀어가겠습니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


다음글

 

결정론적 AI 파이프라인 설계 - Deterministic First AI

GitHub - chessire/shelter-puppyContribute to chessire/shelter-puppy development by creating an account on GitHub.github.comLLM은 영상을 만들지 않습니다.영상은 결정론 코드가 만들고,LLM은 사람의 말을 코드로 번역할 뿐입니

chessire.tistory.com

이전글

 

로컬 LLM 아키텍처 - Local First AI

GitHub - chessire/shelter-puppyContribute to chessire/shelter-puppy development by creating an account on GitHub.github.com1. 강아지를 입양했다기술을 먼저 정하고 문제를 찾은 게 아닙니다.한 마리를 데려오고 나서야풀어

chessire.tistory.com

 

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com

 

 

GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

1. 강아지를 입양했다

기술을 먼저 정하고 문제를 찾은 게 아닙니다.
한 마리를 데려오고 나서야
풀어야 할 문제가 보였습니다.

 AI로 무언가를 만들어보려던 참이었습니다. 도구는 손에 있는데 무엇을 풀지가 없던 흔한 상태였습니다.

 

 최근에 강아지를 입양해왔습니다. 5월 초부터 마음을 정해두고, 아내가 매일같이 보호 글을 들여다보며 인연을 찾았습니다. 그렇게 5월 24일, 강릉의 한 보호소와 연계된 임시보호자님에게 있던 아기 강아지를 데려왔습니다.

 

 이 아이의 사정은 이랬습니다. 떠돌이 어미가 낳은 새끼들 중 하나였고, 새끼들만 구조되었고 어미는 끝내 구조되지 못했다고 들었습니다. 세 마리 중 둘은 먼저 가족을 찾았고, 가장 오래 떠나지 못하고 남아 있던 아이가 우리에게 왔습니다.

 

 데려오는 과정에서 예상하지 못한 걸 봤습니다. 임시보호자와 입양 희망자 사이에서 오가는 소통, 정보, 그 사이의 불편함이 분명히 존재했습니다. 사람 손만으로는 메우기 버거운 자리였습니다.

 

 그 순간 두 가지가 겹쳤습니다. 풀 문제를 찾던 나, 그리고 눈앞에 보인 진짜 불편함. 마침 AI를 만들고 싶었던 저는 그 틈을 AI로 메울 수 있겠다 싶었습니다. 그래서 결심했습니다. 이 틈을 내 능력으로 메워보자고요.

 

 이 글은 그 결심에서 출발한 기록입니다. 그리고 다음 이야기들은 "왜 로컬인지", "왜 이렇게 도구를 쪼갰는지"를 다뤄보고자 합니다.

 

귀여운 토리


2. 영상 - 왜 전부 LLM에 맡기지 않을까?

"AI한테 영상 던지고 '이 강아지 뭐 해?' 물어보면 되잖아."
이 방법은 가장 비싸고 가장 느리게 도착하는 방법입니다.

 

 보호소 강아지의 영상들을 가지고 입양 공고 콘텐츠로 만든다고 해봅시다. 멀티모달 LLM에 영상을 통째로 밀어 넣으면 끝날 것 같습니다. 하지만 이 출발점은 비용도 속도도 가장 안 좋은 형태로 시작됩니다.

 

 여기서 짚어야 할 게 있습니다. "AI"는 한 덩어리가 아니라 파이프라인입니다. 영상 한 편을 처리하는 데에도 성격이 다른 셋이 섞입니다.

  1. 고전 알고리즘 (OpenCV) - 학습이 없습니다. 수학과 규칙으로 돕니다. 빠르고, 싸고, 결과가 일정합니다.
  2. 학습된 전용 신경망 (YOLO) - AI지만 "검출" 한 가지에 특화돼 있습니다. 프레임마다 강아지의 박스와 위치를 찾습니다.
  3. 생성형 LLM (gemma) - 의미를 판단합니다. "이 강아지는 지금 사람을 반기는 중"이라는 해석. 강력하지만 느리고, 비싸고, 가끔 틀립니다.

 만약 강아지가 화면 어디 있는지 박스 좌표를 찾는 일까지 LLM에 시키면 어떻게 될까요? 느리고, 부정확하고, 비용만 듭니다. 그건 YOLO가 훨씬 잘하고 싸게 하는 일입니다.

 

 그래서 설계의 핵심은 이렇게 정리됩니다.

 

 잘 만든 설계는 무거운 생성형 LLM을 '꼭 필요한 한 스텝'에만 씁니다. 나머지는 싸고 빠른 도구로 분리합니다.

 

 실제 영상 한 편은 이렇게 흘러갑니다.

  • 프레임 자르기·인코딩·자막 입히기 -> ffmpeg (그냥 도구)
  • 강아지가 어디 있나(박스) -> YOLO (검출)
  • 그 강아지를 영상 내내 같은 ID로 유지 -> ByteTrack (추적)
  • 마지막에 딱 한 번, "이 장면을 입양 공고용으로 어떻게 설명할까"를 저작 -> gemma (의미·생성)
  • 대본을 나레이션 음성으로 입힌다 -> mlx-audio (TTS)

 작은 비영리에서 이건 취향이 아닌 비용 절감입니다. LLM 호출 한 번이 곧 비용이고 솔로 운영에서 느린 파이프라인은 곧 굴리지 못하는 파이프라인이 됩니다. 무거운 도구를 아껴 쓰는 설계가 비영리 사업을 지속 가능하게 만드는 조건이 될 수 있습니다.

 

 이 "계층 분리"는 영상에만 해당하는 이야기가 아닙니다. 사실 이 프로젝트 전체를 관통하는 질문이고 다음 DB 이야기도 똑같은 자리에서 출발합니다.


3. DB - 왜 Postgres와 벡터DB를 같이 둘까?

하나는 "정확히 일치하는 것"을 찾고
다른 하나는 "비슷한 의미"를 찾습니다.

 

 DB를 두 개나 쓴다고 하면 과해 보입니다. "그냥 데이터베이스 하나면 되지 않나?" 하지만 이건 중복이 아니라 2번에서 본 것과 똑같은 역할 분리입니다. 두 DB는 애초에 답하는 질문이 다릅니다.

  1. PostgreSQL
    "정확히 일치하는 것"을 찾는다. 회원, 강아지 공고, 입양 신청처럼 행과 열로 떨어지는 데이터입니다. "보호 중이고, 중성화 완료이고, 3살 이하인 강아지" — 조건이 명확하고, 답도 딱 떨어집니다. 이러한 과정은 관계형 DB가 최적입니다.
  2. 벡터DB(chromadb)
    "비슷한 의미"를 찾는다. "사람을 잘 따르고 아파트에서도 키울 만한 순한 아이" 같은 질문은 다릅니다. 어느 칸에도 그렇게 적혀 있지 않습니다. 이건 의미가 가까운 데이터를 찾는 일이고 텍스트를 숫자 벡터로 바꿔(임베딩) 거리로 비교해야 됩니다. 관계형 DB의 WHERE 조건으로는 풀기 어려운 문제입니다.

 그래서 둘은 대체재가 아닙니다. 하나는 사실을 정확히 찾고, 하나는 의미를 비슷하게 찾습니다. 응대봇이 "이 조건에 맞는 강아지"(Postgres)와 "이 느낌의 강아지"(벡터DB)를 동시에 답하려면 둘 다 필요합니다.

 

 이게 2번의 영상 이야기와 똑같은 자리인 이유가 여기 있습니다. "만능 도구 하나로 다 해결한다"가 아니라 "질문의 성격에 맞는 도구를 선택한다". LLM에 전부 안 시키듯, DB도 한 종류에 전부 안 맡깁니다.

 

 다만 솔직히 둘 다 끌고 가는 건 비용입니다. 운영할 DB가 둘이 되니까요. 그래서 지금은 명확히 분리해 두되, 규모가 커지면 한쪽으로 합치는 길도 열어둡니다. PostgreSQL에 벡터 검색 확장(pgvector)을 얹어 한 DB에서 사실과 의미를 같이 다루는 선택지입니다. 초기에는 역할을 선명하게 분리하고, 규모가 커지면 운영 부담을 줄이는 쪽으로 통합하려 합니다.


4. 응대봇 - 왜 로컬이며, 왜 이 모델일까?

똑똑한 모델 하나를 고르는 문제가 아닙니다.
"무엇을 사용해야 하는가"와
"어떤 일에 어떤 모델을 쓰는가"입니다.

 

 입양 문의에 답하는 봇을 만든다고 해봅시다. 가장 쉬운 길은 외부 API(예: GPT)를 부르는 겁니다. 그런데 그 길을 택하는 순간, 1편 첫머리에서 던졌던 질문이 그대로 돌아옵니다. 무엇을 로컬에 두고 무엇을 밖에 맡길 것인가.

 

 왜 로컬(Ollama)인가. 세 가지 이유입니다.

  • 데이터. 강아지 정보, 보호 기록, 입양 신청자 문의가 매 호출마다 외부 서버로 나갑니다. 명분 사업에서 보호 동물과 신청자의 데이터를 남의 서버에 흘리는 것은 가볍지 않습니다. 로컬은 그 정보들이 내 기기 밖으로 안 흘러나갑니다.
  • 비용. 외부 API는 호출당 과금입니다. 응대봇은 많이 부를수록 가치가 큰데 많이 부를수록 그대로 토큰 값이 됩니다. 로컬 모델은 전기값 외에 호출당 비용이 0입니다.
  • 통제. 모델이 갑자기 바뀌거나 가격이 오르거나 정책으로 막히는 일에서 자유롭습니다. 예측 가능함이 곧 안정성입니다.

왜 이 모델 조합인가. 여기서도 2번의 "계층 분리"가 그대로 반복됩니다. 응대봇은 모델 하나가 아니라 역할이 다른 둘로 작동합니다.

  • bge-m3 (임베딩) — 대화하지 않습니다. 텍스트를 숫자 벡터로 바꾸는 일만 합니다. "이 강아지 설명"과 "사용자 질문"을 같은 좌표 공간에 올려 의미가 가까운지를 잴 수 있게 만듭니다. 한국어 성능이 좋아 강아지 데이터에 적합합니다.
  • gemma (생성) — 답을 만듭니다. 검색해 온 데이터를 읽고, 영상을 해석하며, 사람에게 건넬 문장으로 풀어냅니다.

이 둘이 3번의 두 DB와 맞물려 하나의 흐름이 됩니다. 이게 RAG입니다.

  1. 사용자가 묻습니다. "사람 잘 따르고 아파트에서도 키울 만한 아이 있어요?"
  2. bge-m3가 질문을 벡터로 바꾸고 벡터DB에서 의미가 가까운 강아지를 꺼냅니다. (동시에 Postgres로 "보호중, 입양가능" 같은 사실 조건도 거릅니다.)
  3. 그렇게 추려진 진짜 데이터를 gemma에게 건네며 답을 만들게 합니다.

 RAG의 핵심은 gemma가 아는 척 지어내지 않게 만드는 겁니다. 모델의 메모리가 아니라 우리 DB의 실제 강아지 데이터를 근거로 답하니까요. 있지도 않은 강아지를 추천하는 환각은 단순 오류가 아니라 신뢰 문제입니다. RAG는 그 위험을 구조로 막습니다.

 

모델 사양의 선택. gemma는 26B 규모지만 MoE 구조라 토큰당 약 4B만 활성화됩니다. 전체는 메모리에 올라가되 속도는 작은 모델처럼 나오는 절충입니다. 거기에 4비트 양자화(q4_K_M)를 더해 메모리를 줄여 워크스테이션이 아닌 한 대의 맥 안에 들어오게 맞췄습니다. "가장 큰 모델"이 아니라 "이 기기에서 굴러가는 충분히 똑똑한 모델"을 고른 겁니다.

 

 결국 이 섹션도 같은 자리로 돌아옵니다. 강력한 모델 하나로 다 하는 게 아니라 일을 쪼개 각자에게 맞는 도구를 붙이고, 그 전부를 로컬에 두는 것입니다.


5. 프론트엔드와 백엔드 - 왜 Django며 왜 Vite에서 Remix일까?

지금 필요한 것과 나중에 필요할 것은 다릅니다.
좋은 선택은 지금을 빠르게 해결하고
나중의 선택지도 닫지 않습니다.

 

 플랫폼엔 두 축이 있습니다. 데이터를 다루는 백엔드와 화면을 그리는 프론트엔드입니다. 둘 다 선택지가 넘치는데 기준은 하나였습니다. 지금 단계에서 가장 적은 공수로 가장 멀리 가는 길.

왜 FastAPI가 아니라 Django인가.

 파이썬 백엔드라면 가벼운 FastAPI도 강력한 후보입니다. 그런데 이 프로젝트는 CRUD가 많은 플랫폼으로 예상됩니다. 강아지 공고 등록과 수정, 임보자 관리, 입양 신청 처리에 데이터를 넣고 빼는 화면이 끝없이 필요합니다.

 

 Django는 여기서 결정적인 걸 공짜로 줍니다.

  • Admin 패널 내장. 공고·신청·회원 관리 화면을 자동으로 생성해줍니다. 프론트를 한 줄도 안 짜도 데이터를 넣고 바로 운영·테스트할 수 있습니다. 솔로 운영에서 이건 몇 주를 아끼는 차이입니다.
  • ORM과 마이그레이션, 인증 기본 제공. FastAPI라면 직접 골라 붙여야 할 조각들이 처음부터 한 묶음으로 옵니다.

 무엇보다 4번까지 만든 AI 코드(Ollama, RAG, YOLO)가 전부 파이썬입니다. Django도 파이썬이라 같은 백엔드 안에서 AI와 플랫폼 로직이 함께 돕니다. 언어가 갈리지 않는다는 것은 곧 개발 속도를 빠르게 합니다.

 

 FastAPI의 가벼움이 빛나는 자리도 분명 있습니다. 다만 CRUD가 많은 플랫폼, 1인 운영이라는 이 조건에선 덜어주는 일의 양이 Django 쪽이 압도적이었습니다.

왜 한 번에 안 가고 단계적으로 Vite에서 Remix인가.

프론트는 처음부터 정답을 고르지 않았습니다. 단계마다 필요가 다르기 때문입니다.

  • MVP 단계 = Vite + React. 지금 필요한 건 빠른 출시입니다. Vite는 가볍고 빠른 개발 환경으로 React 화면을 즉시 띄웁니다. 단점도 분명합니다. 순수 SPA라 SSR이 없어 검색 노출(SEO)과 링크 미리보기가 약합니다. MVP에선 감수할 수 있는 약점입니다. 아직 공개 공고를 검색에 태울 단계가 아니니까요.
  • 프로덕트 단계 = Remix(SSR). 공개 입양 공고가 검색에 잡히고, 카톡·인스타에 링크를 붙였을 때 미리보기가 떠야 하는 순간이 옵니다. 그때 SSR이 필요해지고, Remix로 넘어갑니다. Django API 백엔드와 역할이 겹치지 않아 조합도 깔끔합니다.

 그래서 핵심은 단계 전략입니다. 지금은 발견성(SEO)을 포기하고 출시 속도를 빠르게 합니다. 나중에 발견성이 필요해지면 그때 SSR로 갈아탑니다. 처음부터 Remix로 시작해 무겁게 가는 대신 지금의 문제를 가장 빨리 풀면서 나중의 선택지도 닫지 않습니다.


맺음말

 다섯 개의 질문은 사실 하나인 듯합니다. 영상도, DB도, 응대봇도, 프론트엔드도, 백엔드도 전부 같은 자리로 수렴했습니다. 강력한 도구 하나로 다 풀지 않는다. 일을 쪼개 각자에게 맞는 도구를 붙인다. 그리고 가능한 많은 것을 로컬에 둔다는 것입니다.

 

 이건 취향이 아니었습니다. 개인이 굴리는 작은 비영리에서 무거운 것을 아껴 쓰는 설계만이 지속 가능하니까요. 가장 비싼 도구를 가장 적게 쓰는 법을 아는 것, 그것이 노하우가 됩니다.

 

도구는 이렇게 깔았고(1편), 왜 그렇게 골랐는지도 풀었습니다(2편). 다음은 이걸로 실제로 무엇을 만드는가입니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


다음글

 

YOLO11 해상도 함정: 골든셋으로 검증 - Measurement First AI

좋은 모델이 좋은 파이프라인을 만드는 게 아닙니다.좋은 채점 기준이,모델을 갈아끼울 수 있게 해줍니다. "AI 영상 분석이면 일단 모델부터 돌려보는 거 아냐?" 틀린 말은 아닙니다. YOLO를 붙이

chessire.tistory.com

이전글

 

맥 로컬 LLM 구축 - Local First AI

AI는 한 덩어리가 아닙니다.역할이 다른 도구들을,한 대의 맥 위에 포개는 일입니다."유기견 입양에 AI를 붙인다"고 하면, 보통 이렇게 떠올립니다."그냥 ChatGPT API 부르면 되는 거 아냐?" 틀린 말은

chessire.tistory.com

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com

 

 

GitHub - chessire/shelter-puppy

Contribute to chessire/shelter-puppy development by creating an account on GitHub.

github.com

AI는 한 덩어리가 아닙니다.
역할이 다른 도구들을,
한 대의 맥 위에 포개는 일입니다.

"유기견 입양에 AI를 붙인다"고 하면, 보통 이렇게 떠올립니다.
"그냥 ChatGPT API 부르면 되는 거 아냐?"

 

틀린 말은 아닙니다. 하지만 이 출발점은 정작 중요한 걸 가립니다. 강아지 정보와 영상 같은 데이터가 매 호출마다 외부로 나가고, 호출량만큼 비용이 붙고, 모델·데이터의 통제권은 내 손을 떠납니다. 작은 비영리 명분 사업에서 이 셋은 가볍지 않은 비용입니다.

그래서 질문을 바꿨습니다.

 

"무엇을 내 손 안(로컬)에 두고, 무엇만 밖에 맡길 것인가?"

 

이 글은 그 경계를, 노트북 한 대 위에 세우는 과정입니다. 선택의 이유는 다음 편으로 미루고, 이번 편은 "무엇을 어떻게 깔았는가"만 다룹니다.


1. AI 플랫폼을 설계하다 - 한 대의 맥에 무엇이 올라가나

세울 것은 세 개의 계층입니다.

  1. Python 계층 — AI 코드(LLM·RAG·영상)와 백엔드(Django)가 같은 언어로 도는 곳.
  2. 데이터 계층 — 표 형태 데이터를 담는 PostgreSQL.
  3. Node 계층 — 화면(React)을 띄우는 프론트엔드 런타임.

핵심은, 이질적으로 보이는 이 조각들이 대부분 파이썬 하나로 묶인다는 점입니다. 강아지 공고 관리도, 응대봇도, 영상 분석도 전부 파이썬 위에서 돕니다. 그래서 환경설정의 무게중심도 자연히 파이썬에 실립니다.

AI Pipeline


2. Python 계층 - AI와 백엔드가 같은 언어로 돈다

맥 기본 파이썬은 버전이 낡아 개발용으로는 권장되지 않습니다. brew로 최신을 깔고, 프로젝트 전용 가상환경(venv)을 만듭니다.

brew install python
python3 --version          # 3.12 / 3.13 나오면 OK
which python3              # /opt/homebrew/bin/python3 이면 brew 거 잡힌 것

# 프로젝트 전용 가상환경
python3 -m venv ~/ai/venv
source ~/ai/venv/bin/activate   # 프롬프트에 (venv) 뜨면 활성
pip install -U pip

그 위에 세 묶음을 올립니다. LLM·RAG 스택, 영상 파이프라인, 그리고 백엔드(Django).

# LLM·RAG — 로컬 모델 클라이언트 + 벡터DB
pip install ollama chromadb

# 영상 파이프라인
brew install ffmpeg                            # 영상 자르기·인코딩·자막 입히기
pip install opencv-python                      # 프레임 추출
pip install ultralytics                        # YOLO(검출) + ByteTrack(추적)
pip install mlx-audio soundfile sounddevice    # TTS — 대본을 나레이션 음성으로 (애플 실리콘 최적)

# 백엔드 — Django + REST + DB 드라이버
pip install django djangorestframework django-cors-headers psycopg2-binary
django-admin startproject config .        # 현재 폴더에 프로젝트 생성
python manage.py runserver                # http://127.0.0.1:8000 뜨면 OK

로컬 LLM 본체인 Ollama는 코드 라이브러리가 아니라 독립 서버라 brew로 깝니다. 위에서 깐 ollama(pip)는 그 서버를 파이썬에서 부르는 리모컨이고, 지금 까는 본체가 TV입니다.

brew install ollama
brew services start ollama        # 재부팅해도 자동으로 뜨는 백그라운드 서비스

ollama pull bge-m3                 # 임베딩 모델 (작고 빠름, 첫 성공)
ollama pull gemma4:26b-a4b-it-q4_K_M   # 멀티모달 본체 (비전+텍스트)

ollama run gemma4:26b-a4b-it-q4_K_M "안녕, 한국어 돼?"   # 응답 나오면 /bye

이 한 덩어리만으로 "텍스트를 벡터로 바꾸고(bge-m3), 의미가 비슷한 데이터를 꺼내(chromadb), 그걸 보고 답하는(gemma)" 응대봇의 뼈대가 한 대의 맥 안에 들어옵니다.


3. PostgreSQL - 구조화 데이터의 자리

회원, 강아지 공고, 입양 신청처럼 행과 열로 떨어지는 데이터는 관계형 DB에 둡니다.

brew install postgresql
brew services start postgresql    # ollama처럼 백그라운드 서비스로

조금 전 깐 chromadb와는 역할이 다릅니다. postgres는 구조화 데이터, chromadb는 의미 검색용 벡터입니다. 대체재가 아니라 함께 씁니다. (왜 둘 다 필요한지는 다음 편에서.)


4. Node 계층 - 화면을 띄우다

프론트엔드(React)를 돌릴 런타임입니다.

brew install node          # node + npm 같이 들어옴
node --version             # 20 이상

npm create vite@latest     # React 선택 (MVP용 SPA)

MVP는 가볍고 빠른 Vite + React로 갑니다. 검색 노출(SEO)이 필요한 프로덕트 단계에서 SSR 프레임워크로 넘어갈 여지를 남겨두지만, 그 갈림길의 이야기도 다음 편 몫입니다. (참고로 create-react-app은 폐기되어 쓰지 않습니다.)


맺음말

여기까지가 골격입니다.

  • Python 하나에 AI와 백엔드를 포개고,
  • Postgres로 구조화 데이터를 받치고,
  • Node로 화면을 띄운다.

겉으로는 그저 brew install과 pip install의 나열처럼 보입니다. 하지만 이 배치에는 일관된 선택이 하나 깔려 있습니다. 가능한 많은 것을 외부 API가 아니라 내 손 안에 두는 것. 왜 굳이 로컬인지, 왜 FastAPI가 아니라 Django인지, 그리고 영상 분석을 왜 전부 LLM에 맡기지 않는지 — 이 환경을 이렇게 세운 이유는 다음 편에서 풀어가겠습니다.


이 글에서 다룬 고정 함수 분류층은 ProjectDavid의 일부입니다.

전체를 바닥부터 만드는 과정은 강의 3부작으로 정리하고 있고, 10월에 오픈 예정입니다.

→ 오픈 알림 받기


다음글

 

로컬 LLM 아키텍처 - Local First AI

1. 강아지를 입양했다기술을 먼저 정하고 문제를 찾은 게 아닙니다.한 마리를 데려오고 나서야풀어야 할 문제가 보였습니다. AI로 무언가를 만들어보려던 참이었습니다. 도구는 손에 있는데 무

chessire.tistory.com

관련글

 

오픈소스 LLM vs 빅테크 LLM API: AI 사업의 비용과 리스크

모델은 커모디티가 된다.마진은 판단에 남는다. 2025년 2월, OpenAI는 당시 가장 비싼 모델 GPT-4.5를 출시했습니다. 백만 토큰당 입력 75 달러, 출력 150 달러. 그리고 다섯 달이 지나기 전, API에서 제거

chessire.tistory.com

 

OS는 하나지만 경계만 여럿입니다.
컨테이너는 단순한 가벼움이 아니라 다른 경계가 됩니다.
그 경계를 만드는 것이 네임스페이스와 cgroup입니다.

처음 컨테이너를 접했을 때의 잘못된 질문

컨테이너를 처음 접했을 때, 저는 자연스럽게 VM과 비교하기 시작했습니다. 그리고 이런 식으로 정리했습니다.

"VM은 OS까지 통째로 올리니까 무겁고, 컨테이너는 OS를 공유하니까 가볍다."

틀린 말은 아닙니다. 하지만 이 비교에서 출발하면 정작 중요한 부분을 놓치고 있었습니다. 가벼움은 결과이지, 컨테이너의 본질이 아니기 때문입니다.

파고들다 보니 질문이 바뀌었습니다.

"VM과 컨테이너는 무게가 다른 게 아니라, 경계를 어디에 그었느냐가 다른 것 아닐까?"

이 질문에서 출발하면 컨테이너의 구조가 훨씬 선명하게 보이기 시작했습니다.


VM과 컨테이너: 경계의 위치가 다릅니다

2편에서 VM은 OS 단위로 경계를 세운다고 정리했습니다. 게스트 OS 전체가 격리의 단위였습니다. 커널이 분리되기 때문에 한 VM에서 발생한 사고가 다른 VM으로 번지지 않았습니다. 강한 격리였지만, 그만큼 운영 단위가 컸습니다.

컨테이너는 다른 선택을 합니다.

OS 커널은 공유합니다. 대신 그 위에서 돌아가는 프로세스(집합)를 격리합니다.

격리의 단위가 머신(OS)에서 프로세스로 내려온 것입니다. 커널은 하나지만, 각 컨테이너는 자신만의 독립된 세계가 있는 것처럼 동작합니다.

여기서 제가 막혔던 지점이 있습니다.

"커널을 공유하면 격리가 제대로 되는 건가? VM보다 경계가 약한 것 아닌가?"

이 의문을 풀려면 컨테이너가 경계를 어떻게 만드는지 들여다봐야 했습니다.


경계를 만드는 첫 번째 도구: 네임스페이스(Namespace)

컨테이너의 격리는 네임스페이스라는 Linux 커널 기능에서 시작됩니다.

네임스페이스가 하는 일은 단순합니다. 프로세스가 볼 수 있는 범위를 제한합니다.

예를 들어 컨테이너 A 안에서 실행 중인 프로세스는 이렇게 동작합니다.

  • 자신의 PID가 1번이라고 알고 있습니다. (실제로는 호스트에서 다른 번호입니다.)
  • 자신만의 네트워크 인터페이스가 있다고 알고 있습니다.
  • 자신만의 파일 시스템 루트(/)를 가진 것처럼 동작합니다.

이것은 2편에서 본 VM의 게스트 OS와 비슷한 구조입니다. 차이는 커널이 분리된 것이 아니라, 커널이 제공하는 뷰(View)만 분리됐다는 점입니다.

프로세스는 자신이 격리된 세계에 있다고 믿습니다. 실제로는 같은 커널 위에 있지만, 네임스페이스가 그 뷰를 잘라냅니다. 저는 이것이 Virtual Memory에서 봤던 구조와 같다고 느꼈습니다. 주소가 가짜였던 것처럼, 여기서는 프로세스가 보는 세계가 가짜입니다.

네임스페이스는 컨테이너의 경계(Boundary) 입니다.

커널 하나에 여러 개의 뷰


경계에 의한 자원 배분: cgroup

네임스페이스로 프로세스가 보는 세계를 격리했다고 해서 끝이 아니었습니다. 여기서 또 막혔습니다.

"보이는 것만 격리하면 자원은 어떻게 나누나? 컨테이너 하나가 CPU를 다 써버리면?"

이 문제를 해결하는 것이 cgroup(Control Group)입니다.

cgroup은 프로세스 집합이 사용할 수 있는 자원의 양을 제한합니다.

  • 이 컨테이너는 CPU의 몇 %까지만 사용할 수 있다.
  • 이 컨테이너는 메모리를 얼마까지만 점유할 수 있다.
  • 이 컨테이너의 네트워크 대역폭은 여기까지다.

2편에서 하이퍼바이저가 VM들의 자원 충돌을 정책으로 조정했던 것처럼, cgroup은 컨테이너 수준에서 같은 역할을 합니다.

cgroup은 컨테이너의 정책(Policy) 입니다.


컨테이너에서 경계 + 매핑 + 정책

이전 편들에서 반복됐던 프레임을 컨테이너에 대입하면 이렇게 정리됩니다.

  • 경계(Boundary): 네임스페이스가 프로세스가 볼 수 있는 범위를 자릅니다.
  • 매핑(Mapping): 컨테이너 안의 가상 경로, 네트워크, PID가 호스트의 실제 자원과 연결됩니다.
  • 정책(Policy): cgroup이 각 컨테이너의 자원 사용량을 제한하고 조정합니다.

VM에서는 하이퍼바이저가 이 세 가지를 담당했습니다. 컨테이너에서는 Linux 커널 자체가 이 역할을 수행합니다. 별도의 하이퍼바이저 레이어 없이, 커널 기능만으로 경계와 매핑과 정책이 작동합니다. 이것이 컨테이너가 가벼운 이유입니다. 하지만 본질은 가벼움이 아니라, 경계를 어디에 그었느냐의 차이입니다.


"약한 격리 아닌가?"라는 질문에 대한 답

컨테이너가 커널을 공유한다는 사실은 분명히 VM보다 격리가 약하다는 의미이기도 합니다. 커널 취약점이 있으면 컨테이너 경계가 뚫릴 수 있습니다. VM이라면 커널이 분리되어 있어서 같은 상황에서 더 강하게 막아냅니다.

이 트레이드오프는 실제로 존재합니다. 컨테이너는 격리를 포기하고 효율을 택한 것이 아닙니다. 격리의 단위를 바꾼 것입니다.

VM은 "다른 머신"을 만듭니다. 컨테이너는 "같은 머신 안의 다른 세계"를 만듭니다.

어느 쪽이 옳다는 이야기가 아닙니다. 운영하려는 단위가 무엇이냐에 따라 적합한 경계가 달라집니다.


요약

  • 컨테이너는 VM보다 가벼운 기술이 아니라, 경계를 OS에서 프로세스로 내린 기술입니다.
  • 네임스페이스가 프로세스가 볼 수 있는 범위를 격리합니다. (경계)
  • 컨테이너 안의 가상 자원은 호스트의 실제 자원과 매핑됩니다. (매핑)
  • cgroup이 컨테이너별 자원 사용량을 제한하고 조정합니다. (정책)
  • 커널을 공유하기 때문에 격리 수준이 다르며, 이는 트레이드오프입니다.

다음 편: Docker, 경계를 배포 단위로 굳히다

컨테이너라는 경계가 만들어졌지만, 그것을 어떻게 묶고 옮기고 재현할 것인가는 별개의 문제였습니다. Docker는 컨테이너에 이미지라는 포장을 씌워 배포 경험을 표준화했습니다. 다음 편에서는 Docker가 컨테이너의 어떤 문제를 풀었는지 따라가보겠습니다.

다음글

 

Virtual Machine은 도대체 무엇을 가상화했을까? - 가상화의 역사 2

VM은 OS가 보는 컴퓨터를 만듭니다.하이퍼 바이저가 매핑, 정책으로 운영합니다.그 결과, OS 단위 격리가 만들어집니다.1. 하드웨어 가상화라는 말은 어디까지를 포함할까?하드웨어 가상화라는 표

chessire.tistory.com

 

VM은 OS가 보는 컴퓨터를 만듭니다.
하이퍼 바이저가 매핑, 정책으로 운영합니다.
그 결과, OS 단위 격리가 만들어집니다.

1. 하드웨어 가상화라는 말은 어디까지를 포함할까?

하드웨어 가상화라는 표현을 들으면, 흔히 CPU나 메모리 같은 부품을 소프트웨어로 그대로 재현하는 기술을 떠올리게 됩니다. 하지만 Virtual Machine(VM)을 정리하다 보니 제가 처음에 잘못된 질문을 하고 있었다는 걸 깨달았습니다. “VM은 도대체 하드웨어의 무엇을 가상화했는가?”입니다.

정리를 해보면 다음 문장에 닿게 됩니다. VM이 가상화한 대상은 CPU나 메모리 자체가 아니라, 운영체제가 하드웨어라고 전제하고 기대하는 인터페이스 전체입니다. 정확히 말하면 VM은 OS가 보게 되는 한 대의 컴퓨터를 가상화합니다.

이 구조 덕분에 게스트 OS는 가상 환경임을 전제로 하지 않아도, 지금 물리 머신 위에 있다는 규약 하에 부팅하고, 드라이버를 로드하며, 스케줄링과 메모리 관리를 수행합니다.


2. 운영체제가 말하는 하드웨어는 부품이 아니라 규약입니다

여기서 중요한 전제를 하나 고정하고 가야 합니다. 운영체제 입장에서 하드웨어는 손에 잡히는 부품 목록이 아니라, 약속된 규약(인터페이스)들의 집합입니다.

  • CPU 인터페이스: 특권 명령 실행, 인터럽트 처리, 유저/커널 모드 전환 규칙
  • 메모리 인터페이스: 주소 공간 접근 방식, 페이지 폴트 같은 이벤트 처리
  • I/O 인터페이스: 디스크·네트워크 장치에 데이터를 쓰고 읽는 방식(DMA 등)
  • 부팅 인터페이스: 전원이 켜진 뒤 어느 지점에서 실행을 시작할지에 대한 약속

VM은 이 규약 묶음을 게스트 OS마다 한 세트씩 제공해, OS가 독립된 머신을 가진 것처럼 동작하게 만듭니다.


3. 그렇다면 현실 자원(CPU/메모리/디스크)은 어떻게 공유될까?

게스트 OS마다 “내가 주인인 머신”이 존재한다면, 그 규약을 현실의 물리 자원과 연결해 줄 구성 요소가 필요합니다. 여기서 하이퍼바이저(Hypervisor)가 등장합니다.

0편에서 정의했던 가상화의 3요소를 대입하면 VM의 구조는 이렇게 압축됩니다.

VM = OS 단위의 추상화 경계 + 현실 자원을 연결하는 매핑 + 충돌을 조정하는 정책

 

이 프레임을 잡고 나서야 VM이 왜 그렇게 설계됐는지가 비로소 납득됐습니다


4. VM이 세운 경계: 프로세스가 아니라 OS

VM이 제공하는 격리는 프롤로그에서 본 가상 메모리와 격리 단위가 다릅니다.

가상 메모리가 프로세스 단위의 경계를 만들었다면, VM은 OS 단위의 경계를 세웁니다. 커널 자체가 분리되기에, 한 VM에서 발생한 커널 패닉이나 보안 사고가 다른 VM으로 번질 가능성이 크게 줄어듭니다.

즉, VM의 운영 단위는 개별 프로그램이 아니라 머신(OS 포함) 전체가 됩니다. 이 경계 덕분에 서로 다른 OS를 한 서버에서 동시에 운영할 수 있게 되었습니다.


5. 경계가 생기면, 현실과 연결하는 매핑이 필요합니다

게스트 OS가 보는 세계가 실제 자원을 쓰려면 연결 표(매핑 테이블)가 작동해야 합니다. VM의 핵심 동작은 게스트의 관점을 현실의 물리 자원에 맞춰 지속적으로 매핑하는 일입니다.

  • 게스트가 보는 4개의 CPU 코어는 실제 CPU의 시간 조각(Time Slice)에 매핑됩니다.
  • 게스트가 물리 메모리라고 믿는 주소는 호스트 메모리의 특정 영역으로 재매핑됩니다.
  • 게스트의 디스크 쓰기 요청은 물리 장치 자체가 아니라, 호스트의 파일 또는 가상 장치 모델로 연결될 수 있습니다.

여기서 핵심은 자원의 복제가 아니라 연결입니다. 현실 자원은 하나지만, 매핑을 통해 여러 게스트에게 각자의 자원처럼 보이게 만드는 구조입니다.


6. 여러 VM이 동시에 돌면, 정책이 필요합니다

매핑이 가능해졌다고 해서 끝나지는 않습니다. 여러 VM이 동시에 자원을 사용하면 충돌은 피할 수 없습니다. 그래서 하이퍼바이저는 정책(Policy)을 집행합니다.

  • 어떤 VM에게 CPU 시간을 우선적으로 배분할 것인가(스케줄링)
  • 특정 VM이 메모리를 어디까지 점유할 수 있게 할 것인가(제한 및 회수)
  • 네트워크 대역폭을 어떻게 나눌 것인가(QoS 등)

이 구조를 따라가다 보니, 0편에서 세운 공식이 VM에서도 똑같이 나타났습니다.

VM은 OS가 보는 한 대의 컴퓨터를 만든다.


7. “하드웨어를 속인다”보다 정확한 표현은 “규약을 만족시킨다”입니다

저도 처음엔 하드웨어를 속이는 기술이라고 생각했는데, 직접 파고들어 보니 더 정확한 표현이 따로 있었습니다. OS의 기대를 만족시키는기술이라고 쓰는 편이 더 정확해보입니다.

게스트 OS는 특정 명령을 실행하면 하드웨어가 정해진 방식으로 응답할 것이라는 규약을 전제로 설계되어 있습니다. 하이퍼바이저는 그 규약이 안전하게 성립하도록, 필요할 때 실행을 트랩(Trap)으로 전환해 처리하거나, 하드웨어 지원(VT-x 등)을 통해 가능한 범위에서 직접 실행되도록 구성합니다.

OS가 믿는 머신의 규약을 재현하고, 현실 자원으로 매핑하며, 정책으로 조정한다.
이것이 VM의 핵심입니다.


8. 다음 편으로 넘어가는 질문

VM은 OS라는 큰 경계를 세움으로써 강한 격리를 얻었습니다. 다만 운영 단위가 OS인 만큼, 상황에 따라 오버헤드가 생깁니다. 그렇다면 이런 질문이 가능합니다.

“격리는 유지하면서, OS까지 통째로 포함하지 않고 더 가볍게 만들 순 없을까?”

이 고민이 바로 컨테이너(Container)의 시작점입니다. 경계가 바뀌면, 매핑과 정책도 바뀝니다. 다음 편에서는 경계가 머신에서 프로세스 쪽으로 이동했을 때 무엇이 달라지는지 살펴보겠습니다.


9. 요약 (5줄)

  • VM은 물리 부품이 아니라 하드웨어 인터페이스 규약을 가상화합니다.
  • 운영 단위(경계)를 OS 전체로 잡기 때문에 강한 격리 능력을 갖습니다.
  • 게스트의 자원 요청은 하이퍼바이저의 매핑을 거쳐 물리 자원으로 연결됩니다.
  • 자원 공유의 질서는 하이퍼바이저가 집행하는 정책에 의해 유지됩니다.
  • VM은 OS의 기대를 만족시키는 인터페이스 재현 레이어입니다.

이전글

 

첫 번째 가상화 레이어 Virtual Memory - 가상화의 역사 1

주소는 가짜입니다.매핑과 정책이 경계를 만듭니다.가상화는 여기서 시작됩니다.1. “Virtual Memory가 가상화 맞아요?”이 시리즈를 시작하며 저는 “VM → 컨테이너 → Docker → Kubernetes” 흐름을

chessire.tistory.com

다음글

 

OS-Level Virtualization - 가상화의 역사 3

OS는 하나지만 경계만 여럿입니다.컨테이너는 단순한 가벼움이 아니라 다른 경계가 됩니다.그 경계를 만드는 것이 네임스페이스와 cgroup입니다. 처음 컨테이너를 접했을 때의 잘못된 질문컨테이

chessire.tistory.com

 

주소는 가짜입니다.
매핑과 정책이 경계를 만듭니다.
가상화는 여기서 시작됩니다.

1. “Virtual Memory가 가상화 맞아요?”

이 시리즈를 시작하며 저는 “VM → 컨테이너 → Docker → Kubernetes” 흐름을 잡았습니다. 그런데 첫 번째 주인공으로 Virtual Memory를 꺼내면 보통 이런 질문이 돌아옵니다.

  • “메모리 관리는 그냥 OS의 기본 기능 아닌가요?”
  • “본격적인 가상화 이야기로 넘어가기 전에 왜 갑자기 메모리 쪽으로 새는 거죠?”

저 역시 처음에는 Virtual Memory를 ‘메모리 부족을 해결하는 기술’ 정도로만 생각했습니다. 하지만 관점을 [경계 + 매핑 + 정책]이라는 프레임으로 옮겨 보면, Virtual Memory는 오히려 가상화의 원형에 가깝다는 사실을 확인하게 됩니다.

결론은 단순합니다.

  • 경계: 프로세스마다 나만의 독립된 주소 공간이 존재하는 것처럼 보이게 합니다.
  • 매핑: 가상 주소를 실제 물리 메모리 위치에 연결하는 번역층을 둡니다.
  • 정책: 서로의 영역을 침범하지 못하도록 보호 규칙을 강제합니다.

이 구조는 이후에 다룰 VM이나 Kubernetes에서도 이름만 바뀐 채 반복됩니다. 차이는 “무엇을 경계로 잡느냐”에 가깝습니다.


2. Virtual Memory가 만든 환상: “각 프로세스는 자기 주소 공간을 가진다”

Virtual Memory가 만든 첫 번째 효과는 단순하면서도 강합니다.

  • 프로세스 A는 자신이 0번지부터 끝번지까지의 메모리를 ‘가진 것처럼’ 동작합니다.
  • 프로세스 B도 똑같이 0번지를 사용하지만, A의 0번지와는 다른 세계입니다.
  • 서로의 메모리가 어디에 있는지 알 수도, 볼 수도 없습니다.

여기서 핵심은 주소(Address)의 의미가 바뀐다는 점입니다. 우리는 흔히 주소를 ‘실제 메모리의 물리적 위치’라고 생각합니다. 하지만 Virtual Memory에서 주소는 그렇게 취급되지 않습니다.

  • 주소는 ‘실제 위치’가 아니라, 자원을 식별하기 위한 이름에 가깝습니다.
  • 실제 위치는 운영체제가 관리하고, 프로세스에는 이름(주소)만 제공합니다.

이 순간부터 주소 공간(Address Space)은 물리적 현실이 아니라 운영체제가 설계한 인터페이스가 됩니다. 저는 이 지점을 “첫 번째 가상화 레이어라고 봅니다.

주소는 가짜, 매핑이 진짜


3. 그럼 질문이 바뀝니다: “이름이 인터페이스라면, 실체는 누가 관리하죠?”

주소가 인터페이스라면 저는 이런 생각이 들었습니다.

  • “그럼 실제 물리 메모리는 누가, 어떻게 나눠주지?”
  • “프로세스들이 각자 주소를 쓰는데 충돌이 안 나는 이유가 뭐지?”

이 의문을 해소하면 Virtual Memory의 구조는 세 가지로 정리됩니다.

3-1) 경계: 주소 공간이라는 소유권 경계

운영체제는 각 프로세스가 볼 수 있는 주소의 범위를 논리적으로 확정합니다. 이 범위 밖은 프로세스 입장에서 원칙적으로 접근할 수 없는 영역입니다. 저는 이것을 격리(Isolation)의 시작이라고 봅니다.

3-2) 매핑: 가상 주소 → 물리 메모리

운영체제는 “이 주소는 실제로 여기다”를 연결하는 매핑 정보를 유지합니다. 프로세스는 주소만 사용하고, 실제 물리적 배치는 운영체제가 결정합니다. 저는 이것이 간접화(Indirection)의 핵심이라고 정리했습니다.

3-3) 정책: 보호 규칙(Permission)으로 침범을 막는다

매핑에는 위치 정보만 있는 게 아닙니다. 읽기만 가능, 쓰기 가능, 실행 가능 같은 규칙이 붙습니다. 이것은 구현 디테일을 넘어선 정책(Policy)입니다. 가상화는 환상을 보여주는 것으로 끝나지 않고, 그 환상을 유지하기 위해 정책을 강제해야 합니다.


4. Virtual Memory가 남긴 핵심: “환상은 운영 모델을 바꾼다”

여기까지 오면 질문은 이렇게 바뀝니다.

  • “그래서, 주소를 가상으로 만들어서 얻는 진짜 이득이 뭔가요?”

Virtual Memory는 단순한 편의 기능을 넘어 운영 모델을 바꿨습니다.

  • 프로세스는 물리 메모리의 구조를 몰라도 됩니다. (추상화)
  • 운영체제는 상황에 맞춰 데이터를 배치하거나 이동시킬 수 있습니다. (유연성)
  • 잘못된 접근이나 침범은 정책 위반으로 차단됩니다. (안정성)

즉, 현실 자원을 직접 다루던 방식이 [간접화 + 정책] 구조로 넘어간 것입니다. 저는 이 패턴이 뒤에서 다룰 VM / 컨테이너 / Kubernetes로 이어지는 “공통 골격”이라고 봤습니다.


5. 오늘날의 연결: 주소 공간에서 머신으로의 확장

여기까지 정리하고 나서, 저는 가상화를 보는 시각이 바뀌었습니다. 가상화는 단순한 속임수가 아니라, 경계와 매핑, 그리고 정책을 통해 자원을 운영하는 시스템 설계입니다.

그리고 그 첫 번째 성공 모델이 Virtual Memory였습니다. 그렇다면 다음 질문은 자연스럽습니다.

  • “주소 공간(메모리)을 가상화할 수 있다면, CPU와 디스크를 포함한 머신 전체도 같은 방식으로 가상화할 수 있지 않을까?”

이 질문에 대한 대답이 다음 편의 주제인 Virtual Machine(VM)입니다.

 

이전글

 

하드웨어 가상화란 무엇인가? - 가상화의 역사 0

OS까지 쪼개는 가상화경계를 세우는 순간 따라오는 매핑과 정책컨테이너, Docker, Kubernetes1. 하드웨어 가상화는 도대체 무엇을 가상화한 걸까?하드웨어 가상화라는 단어를 처음 접하면 보통 비슷

chessire.tistory.com

다음글

 

Virtual Machine은 도대체 무엇을 가상화했을까? - 가상화의 역사 2

VM은 OS가 보는 컴퓨터를 만듭니다.하이퍼 바이저가 매핑, 정책으로 운영합니다.그 결과, OS 단위 격리가 만들어집니다.1. 하드웨어 가상화라는 말은 어디까지를 포함할까?하드웨어 가상화라는 표

chessire.tistory.com

 

OS까지 쪼개는 가상화
경계를 세우는 순간 따라오는 매핑과 정책
컨테이너, Docker, Kubernetes

1. 하드웨어 가상화는 도대체 무엇을 가상화한 걸까?

하드웨어 가상화라는 단어를 처음 접하면 보통 비슷한 이미지를 떠올리게 됩니다. CPU나 메모리를 교묘하게 속이는 기술 같은 느낌 말입니다. 하지만 이 개념을 파고들다 보면 정작 중요한 질문은 다른 곳에 있다는 것을 알게 됩니다. 대체 무엇을 하드웨어처럼 보이게 만들고 싶었던 걸까?

저도 처음엔 복잡하게 접근했는데, 파고들수록 오히려 단순한 질문으로 좁혀졌습니다. 바로 한 대의 물리 머신 위에 여러 대의 머신이 있는 것처럼 보이게 만드는 구조입니다. 여기서 말하는 머신은 단순한 프로그램 하나가 아니라, OS까지 포함한 실행 환경 전체를 의미했습니다.

결국 하드웨어 가상화라는 말은, 사실상 OS 단위의 경계를 하나 더 만드는 작업에 가까웠습니다. 근데 여기서 저는 한 가지가 걸렸습니다.

OS를 통째로 올리는 건 너무 무겁지 않았을까?
그냥 프로세스만 잘 돌리면 충분했을 텐데, 왜 굳이 이런 선택을 했을까?

그래서 저는 경계를 어디에 그었는지부터 먼저 따라가봤습니다.


2. 하드웨어 가상화를 왜 쓰게 됐을까?

단순히 “필요해서 썼다”는 설명은 큰 도움이 되지 않았습니다. 구체적으로 어떤 불편함이 우리를 OS까지 통째로 가두는 구조로 이끌었는지 들여다볼 필요가 있었습니다. 그 불편함의 장면들을 복기해 보면 세 가지 키워드가 선명해집니다.

(1) 같이 돌리면 같이 망가지는 순간 (Isolation)

서버 한 대에서 여러 서비스를 돌리다 보면, 한쪽의 자원 폭주가 전체 시스템을 흔들어놓는 장면을 흔히 봅니다. 프로세스 수준에서 주의를 기울이는 것만으로는 해결되지 않는 결함들이 존재했습니다. 그래서 아예 운영 단위를 OS 레벨에서 격리해버리는 선택이 매력적으로 다가왔던 겁니다.

(2) 환경이 다르면 생기는 끝없는 변수 (Illusion)

“내 로컬 PC에서는 잘 되는데요”라는 말이 운영 환경에서 통하지 않을 때의 당혹감은 익숙합니다. OS 버전, 라이브러리, 미세한 설정 차이가 결과값을 바꿨습니다. 결국 프로그램만 옮기는 게 아니라, 그 프로그램이 믿고 있는 환경 자체를 복제해 일관된 실행 환경을 만들어낼 필요가 있었습니다.

(3) 하드웨어가 바뀌면 위쪽도 흔들리는 구조 (Indirection)

서버를 교체하거나 스토리지 구성이 바뀔 때마다 상위 서비스가 영향을 받는다면 운영의 유연성은 떨어질 수밖에 없습니다. 인프라와 서비스 사이의 직접적인 연결을 끊어줄 간접화의 층이 절실해진 지점이었습니다.

그런데 여기서 또 막혔습니다. 격리(Isolation)만 잘하면 되는 것 아닐까? 왜 환상(Illusion)과 간접화(Indirection)는 항상 세트처럼 따라다니는 걸까?


3. 왜 Illusion / Isolation / Indirection은 늘 한 세트일까?

이건 철학의 문제라기보다, 구조를 따라가다 보면 자연스럽게 그렇게 보였습니다. 격리를 위해 경계를 긋는 순간, 연쇄적인 반응이 시작되기 때문입니다.

격리를 하려면 명확한 경계(Boundary)를 세워야 합니다.

경계를 세우고 나면, 그 경계 안에서 보이는 자원과 실제 물리 자원을 연결하는 매핑(Mapping)이 필요해집니다. (이 연결을 상위 레이어 관점에서 보면 환상이 되고, 구조 관점에서 보면 간접화가 됩니다.)

매핑이 생겨 자원을 공유하기 시작하면, 이를 공정하게 나누기 위한 정책(Policy)이 필요해집니다.

격리라는 목표를 세우는 순간, 환상과 간접화, 그리고 정책이 따라오는 것은 매우 자연스러운 흐름이었습니다. 이 과정을 거치면 가상화의 정체가 하나의 명료한 문장으로 정리됩니다.

가상화는 결국 경계(Boundary) + 매핑(Mapping) + 정책(Policy)으로 굴러가는 구조였습니다.


4. VM에서 “경계 + 매핑 + 정책”은 어떤 모습이었을까?

이 구조를 구체적인 가상 머신(VM)의 모습에 대입해 보면 더 명확해집니다.

(1) 경계(Boundary): 운영 단위를 OS로 설정

하드웨어 가상화는 격리의 단위를 프로세스가 아닌 머신(OS 포함)으로 잡았습니다. 무겁다는 단점이 있지만, 경계가 매우 선명하고 강력하다는 확실한 이점을 얻었습니다.

(2) 매핑(Mapping): 가상 자원을 현실 자원에 연결

VM 안의 CPU, 메모리, 디스크가 실제로 작동하려면 물리 자원과의 연결이 필요합니다. 가상 디스크가 실제 스토리지의 어디에 위치하는지, 네트워크 패킷이 어떤 물리 스위치로 나가는지를 관리하는 식입니다. 이 매핑(연결)을 유지하는 여러 구성요소가 가상화를 지탱합니다.

(3) 정책(Policy): 공유를 위한 의사결정

자원을 나눠 쓰는 순간 결정해야 할 것들이 폭발합니다. CPU 시간을 어떻게 쪼갤지, 메모리 할당량을 어디까지 허용할지, 특정 VM의 폭주를 어떻게 제한할지 같은 정책들이 시스템의 핵심이 됩니다.

결국 하드웨어 가상화는 단순한 기술이 아니라, 공유를 안전하게 운영하기 위한 제어/관리 계층에 가까웠습니다.


가상화는 경계를 만들고, 매팡하고, 정책으로 운영한다.

5. VM에서 Container, Kubernetes로 이어지는 흐름: 경계의 이동

VM을 이렇게 구조적으로 이해하고 나면, 컨테이너나 쿠버네티스로 이어지는 역사가 단순히 더 가벼운 기술의 등장이 아님을 깨닫게 됩니다. 핵심은 경계의 위치가 어디로 움직였느냐에 있었습니다.

  • Virtual Memory: 경계가 주소 공간(프로세스)에 생겼습니다.
  • VM(하드웨어 가상화): 경계를 머신(OS) 전체로 끌어올려 강력한 격리를 얻었습니다.
  • Container: 경계를 다시 프로세스(집합) 단위로 내려 효율성을 챙겼습니다.
  • Docker: 그 경계를 배포 가능한 패키지로 굳혀 배포 경험을 표준화하고 단순화했습니다.
  • Kubernetes: 경계를 개별 서버를 넘어 클러스터 운영 단위로 확장했습니다.

결국 가상화의 역사는 운영 단위를 무엇으로 잡느냐가 이동해 온 과정이었습니다. 기술이 완전히 바뀐 것이 아니라, 경계/매핑/정책이라는 틀을 유지한 채 운영의 단위만 옮겨 다니며 진화해 온 역사였던 셈입니다. 이 흐름을 파악하고 나면 현대 인프라 기술들의 연결 고리가 비로소 매끄럽게 보이기 시작합니다.


6. 요약 및 다음 편 예고

오늘 정리해 본 내용을 다섯 줄로 요약하면 이렇습니다.

  • 하드웨어 가상화는 OS(머신) 단위로 경계를 세우는 방식이었습니다.
  • 격리를 시작하면 매핑이 필요해지고, 자연스럽게 정책이 따라붙습니다.
  • 따라서 가상화는 “경계 + 매핑 + 정책”의 구조로 이해하는 것이 가장 깔끔합니다.
  • VM에서 쿠버네티스까지의 발전은 이 경계가 이동해 온 역사입니다.
  • 이 패턴을 이해하면 어떤 가상화 기술도 구조적으로 분석할 수 있습니다.

다음 편에서는 이 모든 패턴이 처음으로 가장 선명하게 나타났던 지점을 살펴보려 합니다. 바로 Virtual Memory입니다. 주소 공간이라는 첫 번째 가상화 레이어가 어떤 힌트를 주었는지 함께 따라가 보겠습니다.

 

다음 글

 

첫 번째 가상화 레이어 Virtual Memory - 가상화의 역사 1

주소는 가짜입니다.매핑과 정책이 경계를 만듭니다.가상화는 여기서 시작됩니다.1. “Virtual Memory가 가상화 맞아요?”이 시리즈를 시작하며 저는 “VM → 컨테이너 → Docker → Kubernetes” 흐름을

chessire.tistory.com

 

멀티스레드는 더 많은 코어가 아니라
더 적은 공유 write로 빨라집니다.

멀티스레드에서 제일 흔한 착각이 있습니다.

atomic이 느린 이유 = 락(lock) 때문

 

아니요. 많은 경우 atomic이 느린 이유는 락이 아니라,
캐시 라인 소유권(ownership) 경쟁 때문에 생기는 coherency 트래픽입니다.

3편에서 본 규칙을 그대로 가져오면 됩니다.

  • read는 공유(S)로 버틸 수 있지만
  • write는 독점(M/E)을 요구하고
  • 누가 write를 시작하면 다른 코어의 라인은 invalidate 된다

atomic은 여기서 한 단계 더 빡셉니다.
atomic은 보통 RMW(Read-Modify-Write) 이기 때문입니다.

Write 1번으로 캐시 라인 소유권 전쟁이 시작한다.

1) atomic이 비싼 진짜 이유: RMW는 라인 독점을 강제한다

atomic_fetch_add 같은 연산을 생각해봅시다.

  • 값을 읽고
  • 수정하고
  • 다시 쓴다

이 3단계를 중간에 끼어들지 못하게 보장해야 합니다.
그래서 하드웨어는 대체로 다음을 요구합니다.

해당 캐시 라인을 내가 독점(M/E)해야만 RMW를 수행할 수 있다.

 

문제는 여러 코어가 같은 atomic을 동시에 건드릴 때 터집니다.

  • Core0가 라인 독점 → Core1은 invalidate
  • Core1이 라인 독점 → Core0는 invalidate
  • 결과: 라인이 ping-pong (False Sharing과 구조가 동일)

여기서 중요한 결론:

atomic 경쟁(contended atomic)은 연산 비용이 아니라 라인 이동 비용으로 느려진다.

 

그래서 스레드를 늘리면 오히려 느려지는 현상이 나옵니다.
CPU가 일을 더 하는 게 아니라, 서로 밀어내느라 시간을 씁니다.


2) Uncontended atomic vs Contended atomic (진단 기준)

atomic이 항상 나쁜 건 아닙니다. 구분이 필요합니다.

Uncontended atomic (경쟁 없음)

  • 한 코어만 해당 atomic을 자주 업데이트
  • 또는 업데이트 빈도가 낮아서 충돌이 거의 없음

이 경우는 대체로 괜찮습니다.

Contended atomic (경쟁 있음)

  • 여러 스레드가 같은 atomic(같은 캐시 라인)을 고빈도로 RMW
  • 대표: 전역 카운터, 전역 큐 인덱스, 글로벌 통계, 글로벌 work counter

이 경우는 거의 확실히 병목입니다.
락이 없어도 느립니다. (락이 아니라 coherency 경쟁이니까)


3) 처방의 방향은 단 하나: 공유 write-hot을 없애라

4편의 실전 처방은 여기로 수렴합니다.

  1. 공유 write-hot을 스레드 로컬로 분해
  2. 필요한 순간에만 합친다(reduction)
  3. 합치는 지점도 가능하면 저빈도 / 배치(batch)로 만든다

즉, atomic을 잘 쓰는 법이 아니라

atomic을 ‘뜨거운 루프에서’ 치워버리는 법

이 핵심입니다.


4) 처방 1: per-thread counter + reduction (가장 강력하고 가장 흔함)

나쁜 예: 모든 스레드가 전역 atomic++

std::atomic<uint64_t> gCount = 0;
void Worker()
{
	for (...)
    {
    	gCount.fetch_add(1, std::memory_order_relaxed);
    }
}

이 코드는 연산이 아니라 라인 소유권 ping-pong으로 느려집니다.

좋은 예: 스레드 로컬 누적 + 마지막 합산(reduction)

std::atomic<uint64_t> gCount = 0;
void Worker()
{
	uint64_t local = 0;
    for (...)
    {
    	local++;
    }
    gCount.fetch_add(local, std::memory_order_relaxed); // 빈도 1회
}

핵심은 fetch_add 횟수를 줄이는 게 아닙니다.
라인 소유권 경쟁을 루프 밖으로 빼는 것입니다.

전역 atomic은 루프마다가 아니라 배치로 접근하라.


5) 처방 2: per-thread data layout (스레드별 write-hot을 구조적으로 분리)

reduction이 항상 가능한 건 아닙니다.
큐 인덱스, 작업 분배, 스레드 상태처럼 실시간으로 공유해야 하는 값이 존재합니다.

이때는 최소한 이렇게 해야 합니다.

서로 다른 스레드가 쓰는 데이터가 같은 캐시 라인에 들어가지 않게 한다.

 

즉, 2편에서 다룬 alignas(64)의 진짜 목적이 여기서 다시 등장합니다.

패턴: thread slot을 캐시 라인 단위로 고정

 
struct alignas(64) ThreadSlot
{
	uint64_t counter; // padding은 컴파일러/플랫폼에 따라 달라질 수 있음
    char pad[64 - sizeof(uint64_t)];
};

ThreadSlot slots[MAX_THREADS];
void Worker(int tid)
{
	for (...)
    {
    	slots[tid].counter++; // 각자 자기 라인만 건드림
    }
}

이 설계는 속도 최적화라기보다
coherency 트래픽 최악 케이스 제거(안정화)입니다.


6) 처방 3: 큐/링버퍼는 head/tail(메타데이터)을 분리하라

Lock-free 큐에서 흔한 병목은 알고리즘이 아니라 메타데이터 배치입니다.

  • head와 tail이 같은 캐시 라인에 있으면
  • producer/consumer가 서로 다른 변수를 업데이트해도
  • 같은 라인을 두고 ping-pong이 납니다 (False Sharing)

원칙

  • head와 tail을 다른 캐시 라인으로 분리
  • size/flags 같은 write-hot 메타데이터도 분리

이건 구조가 복잡해지기 전에 적용할수록 효과가 큽니다.


7) atomic을 써야 한다면: 최소한 이 관점으로 사용해라

atomic을 완전히 없앨 수 없는 경우도 있습니다.
그럴 때의 원칙은 단순합니다.

(1) hot loop에서 atomic을 빼라

  • 루프 안에서 1회를 루프 밖에서 1회로 바꿔라 (batch)

(2) 공유 write-hot을 줄여라

  • write 빈도를 낮추거나, 스레드별로 나누고 합쳐라

(3) 같은 라인을 두 스레드가 건드리지 않게 배치하라

  • per-thread slot
  • alignas(64) 또는 명시적 padding

여기까지 하면 atomic이 락처럼 느려지는 대부분의 상황은 정리됩니다.


8) 체크리스트: 이 조건이면 atomic 병목을 의심해라

  • 스레드를 늘렸는데 throughput이 거의 안 오르거나 떨어진다
  • 전역 카운터/통계/큐 인덱스가 hot path에 있다
  • 프로파일에서 모두가 같은 위치를 건드린다는 냄새가 난다
  • 락은 없는데도 CPU가 바쁘고 일이 안 끝난다

이때는 먼저 묻는 게 맞습니다.

“나는 지금 연산을 하고 있나, 아니면 캐시 라인 소유권을 주고받고 있나?”


마무리: 멀티스레드 최적화의 본질

시리즈를 4편까지 오면 결론은 선명해집니다.

  • 1편: CPU는 연산보다 물류(메모리/캐시)가 문제다
  • 2편: 멀티코어는 shared cache line 때문에 무너진다 (False Sharing)
  • 3편: Invalidate는 감이 아니라 MESI 규칙으로 발생한다
  • 4편(오늘): atomic의 비용은 락이 아니라 라인 소유권 경쟁이다

그래서 실전 처방도 단순합니다.

  1. 공유 write-hot을 없애라
  2. 스레드별로 누적하고 마지막에 합쳐라(reduction)
  3. 어쩔 수 없이 공유하면 라인을 분리하라(alignas(64), layout)

이전글

 

Invalidate를 줄이는 법: MESI로 이해하는 캐시 라인 소유권 경쟁

다른 코어가 같은 캐시 라인을 가진 상태에서,누군가 그 라인을 write(특히 RMW)로 독점하려는 순간.Invalidate가 터진다.우리는 False Sharing을 캐시 라인 ping-pong으로 봤습니다.그런데 여기서 한 단계

chessire.tistory.com

 

다른 코어가 같은 캐시 라인을 가진 상태에서,
누군가 그 라인을 write(특히 RMW)로 독점하려는 순간.
Invalidate가 터진다.

우리는 False Sharing을 캐시 라인 ping-pong으로 봤습니다.
그런데 여기서 한 단계 더 정확해져야 해요.

Invalidate는 캐시 미스가 아니라, 쓰기 권한(소유권) 경쟁 때문에 발생한다.

 

즉, 멀티코어에서 느려지는 이유는 데이터가 멀리 있어서가 아니라
누가 그 캐시 라인을 ‘쓰기 가능한 최신 상태’로 갖고 있느냐를 맞추느라 트래픽이 터지기 때문입니다.

이걸 설명하는 최소 모델이 MESI입니다.


0) MESI를 한 문장으로 정의

캐시 라인(보통 64B)마다 CPU는 “이 라인이 지금 어떤 상태인가”를 관리합니다.

  • M (Modified): 내가 수정했고(Dirty), 최신. 다른 코어는 못 가짐
  • E (Exclusive): 나만 가지고 있고 최신. 아직 수정은 안 했음
  • S (Shared): 여러 코어가 읽기용으로 공유 중
  • I (Invalid): 무효(없다고 봐도 됨)

여기서 핵심은 딱 하나:

쓰기(write)는 ‘독점(Exclusive)’ 상태를 요구한다.
그래서 누가 쓰려고 하면, 다른 코어의 같은 라인은 Invalidate(I) 된다.


1) 읽기만 하면 보통 큰 싸움이 없다 (Read / Read)

두 코어가 같은 주소 X를 읽기만 한다고 하자.

  • Core0: X를 읽음 → E 또는 S (상황에 따라)
  • Core1: X를 읽음 → 둘 다 S로 수렴

여기서는 보통 Invalidate 폭발이 없습니다.
왜냐하면 읽기는 최신성만 맞으면 되고, 독점 소유권이 필요 없기 때문입니다.

결론: Read-mostly 공유는 비교적 안전하다.


Write 독점 경쟁 → Invalidate → Ping-Pong (MESI)

2) 누군가 쓰기를 하는 순간부터 Invalidate가 시작된다 (Read / Write)

이제 Core1이 X를 write 한다고 하자. (X++ 같은 수정)

이미 Core0이 X를 S 상태로 갖고 있었다면?

  • Core1은 쓰기 위해 X를 독점 상태(M / E)로 만들어야 함
  • 그래서 Core0의 X는 I(Invalid) 로 바뀜

즉, 규칙은 간단합니다.

다른 코어가 같은 캐시 라인을 가지고 있는데, 누군가 write를 하려는 순간 → Invalidate 발생

 

이때 Core0는 내 캐시에 분명히 있었는데(I로 바뀌어서) 다시 가져와야 하고,
이게 Coherency Miss로 관측됩니다.


3) 최악은 Write / Write: 소유권이 공처럼 튕긴다 (Ping-Pong)

False Sharing이 터지는 대표 상황이 이거죠.

  • Core0: 라인 X의 어떤 필드(변수 A)를 계속 write
  • Core1: 같은 라인 X의 다른 필드(변수 B)를 계속 write

둘은 논리적으로 다른 변수지만, 캐시 라인이 같으면 같은 전쟁입니다.

흐름은 이렇게 됩니다.

  1. Core0 write → X를 M로 만든다 (Core1 쪽 X는 I)
  2. Core1 write → X를 M로 만들려 한다 → Core0 쪽 X는 I
  3. Core0 write → 다시 독점 필요 → Core1 쪽 I
    … 무한 반복

여기서 중요한 포인트:

  • 값 자체의 충돌이 없어도
  • 캐시 라인 소유권 때문에
  • Invalidate가 매번 발생한다.

결론: False Sharing은 shared data가 아니라 shared cache line 문제다.


4) 왜 RMW(atomic)이 특히 비싼가: 독점 + 순서를 강제한다

다음 편에서 atomic을 깊게 다루겠지만, 원리만 여기서 박아두면 이렇습니다.

atomic_fetch_add 같은 연산은 단순 write가 아니라 Read-Modify-Write입니다.

  • 값을 읽고
  • 수정하고
  • 다시 쓰는 작업

이건 의미상 중간에 다른 코어가 끼면 안 되기 때문에,
하드웨어는 보통 해당 라인을 더 강하게 독점하려고 합니다.

즉, 경쟁 상황에서 atomic은 이렇게 동작해요.

  • 여러 코어가 동일 라인에 대해 RMW를 시도
  • 각 코어가 “내가 지금 독점해서 처리해야 함”을 강제
  • 결과적으로 invalidate + 소유권 이동이 더 자주, 더 비싸게 발생

그래서 atomic 비용을 락이니까 느리다고만 보면 진단이 반쯤 틀립니다.

atomic의 핵심 비용은 락이 아니라, 캐시 라인 소유권 경쟁(= coherency 트래픽)이다.

 

이게 4편의 주제가 됩니다.


5) 이 규칙을 코딩 관점으로 번역하면

MESI를 외우는 목적은 상태도를 잘 그리자는 게 아닙니다.
코드를 이렇게 분류하기 위해서예요.

안전한 패턴(대체로)

  • read-mostly 공유 (초기화 후 읽기 전용)
  • 스레드마다 자기 데이터에 write (서로 다른 라인)

위험한 패턴(거의 확실히 터짐)

  • 여러 스레드가 같은 라인에 write
  • 서로 다른 변수라도 같은 캐시 라인이면 동일하게 위험
  • 경쟁 상황의 atomic RMW (특히 hot counter)

여기서 2편에서 말한 alignas(64)가 다시 의미를 갖습니다.

  • alignas(64)는 단순히 빠르게 하는 것이 아니라
  • write-hot 데이터가 같은 캐시 라인을 공유하지 않게 하는 장치
  • 즉 Invalidate 규칙을 회피하는 설계입니다.

마무리

여기까지 정리하면, 멀티스레드에서 성능이 무너지는 이유는 CPU가 느려서가 아닙니다.

대부분은 캐시 라인(64B) 소유권을 두고 벌어지는 규칙적인 싸움입니다.

  • 읽기(Read)는 공유(S)로 공존할 수 있지만
  • 쓰기(Write)는 독점(M/E)을 요구하고
  • 그 순간 다른 코어의 동일 라인은 Invalidate(I) 됩니다.
  • 이 Invalidate가 반복되면, 우리가 2편에서 본 ping-pong(= False Sharing)이 됩니다.

즉, False Sharing은 공유 데이터 문제가 아니라
공유된 캐시 라인(shared cache line) 문제입니다.

이제 다음 질문이 자연스럽게 남습니다.

“그럼 atomic은 왜 이렇게 비싼가? 락이 없는데도 왜 느려지나?”

 

답은 이미 보입니다. atomic은 단순 write가 아니라 RMW(Read-Modify-Write)이고,
RMW는 캐시 라인의 독점 소유권과 순서를 더 강하게 요구합니다.
그래서 병목은 락이 아니라 coherency 트래픽으로 나타납니다.

다음 편에서는 이 지점을 정확히 파고들어,

 

atomic의 비용을 락이 아니라 캐시 라인 소유권 경쟁으로 재정의하고

실전에서 바로 쓰는 per-thread data layout / reduction 패턴으로
멀티스레드 성능을 안정화하는 방법까지 마무리하겠습니다.

이전글

 

현대 CPU 최적화의 본질: 연산(ALU)이 아닌 메모리(LSU)

CPU는 연산보다데이터를 가져오는 방식에훨씬 민감하다.CPU 최적화를 연산을 줄이는 일로만 보면, 체감 성능이 잘 안 나옵니다. 실전 병목은 대개 계산(ALU)이 아니라 메모리 접근(Load/Store)에서 터

chessire.tistory.com

 

다음글

 

Atomic: 락이 아니라 캐시 라인 소유권 경쟁이다.

멀티스레드는 더 많은 코어가 아니라더 적은 공유 write로 빨라집니다. 멀티스레드에서 제일 흔한 착각이 있습니다.atomic이 느린 이유 = 락(lock) 때문 아니요. 많은 경우 atomic이 느린 이유는 락이

chessire.tistory.com