ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 로컬 챗봇 시리즈 #12 (완) — 정책을 데이터로 표현하기: jobs.conf 한 줄이 모든 GPU 동거 정책을 결정한다
    IT 2026. 5. 9. 23:30
    로컬 챗봇 시리즈 #12 (완) — 정책을 데이터로 표현하기: jobs.conf 한 줄이 모든 GPU 동거 정책을 결정한다

    들어가며 — 12편의 시리즈를 닫으며

    11편까지는 챗봇 자체의 기능을 다뤘다. 마지막 편은 그 챗봇이 올라가 있는 시스템 레이어의 디자인 한 가지에 집중한다 — "정책을 코드가 아니라 데이터로 표현"한다는 원칙이 GPU 스케줄러와 메모리 SoT 디자인에 어떻게 적용됐는지를 다룬다.

    "좋은 인프라 디자인은 정책 변경이 데이터 한 줄 수정으로 끝난다"는 격언을 자주 듣는다. 이번 글은 그 격언이 실제 운영 시스템에서 어떻게 보이는지를 한 사례로 풀어쓴다.


    1. jobs.conf 한 줄이 표현하는 것 — GPU 동거 정책 전체

    DGX Spark 통합 메모리 128GB 안에 GPU를 쓰는 작업이 10개 정도 돌아간다. 텍스트 vLLM, vision vLLM, RAG 임베딩, voice-pipeline Whisper, family-album VLM, photo-enhance 등이 그렇다. 누가 누구를 선점하고, 누가 동시에 못 도는지 — 이 정책이 코드 곳곳에 박혀있으면 변경할 때마다 위험한 리팩토링이다.

    해법은 모든 정책을 jobs.conf 파일 한 곳에 데이터로 모은 것이다.

    diagram

    이 그림이 보여주는 표 한 장이 "누가 누구를 선점하고, 누가 동시에 못 도는가"라는 모든 GPU 동거 정책을 단 9줄로 표현한다. 위쪽 회색 영역은 jobs.conf 파일의 실제 내용 — 9개 작업이 priority 순으로 정렬되어 있다. priority 1(vault-search)부터 90(vlm-analysis)까지 — 숫자가 작을수록 우선순위가 높아 다른 작업을 선점할 수 있다. 빨간색으로 강조한 두 줄(gemma-vision, vllm-chat)이 시리즈 #4에서 다룬 순차 스왑 정책의 표현 — vision(25)이 chat(30)보다 우선이라 vision 요청 시 chat이 자동 stop된다.

    아래 녹색 영역은 이 한 표가 발효시키는 정책들을 풀어쓴 것이다. 각 줄이 하나의 운영 규칙이다. 가장 중요한 마지막 줄은 "Python 코드는 acquire/release만 호출하고, 정책은 코드에 일체 박혀있지 않다"는 점이다. vllm_manager.py, vision_manager.py, family-album의 album_vlm.py 등 모든 GPU 사용 코드는 단순히 "작업명을 가지고 acquire/release를 호출"한다. 누가 누구를 선점할지, 메모리를 얼마나 쓸지는 그 코드가 모른다 — 스케줄러가 jobs.conf를 보고 결정한다. 코드와 정책의 분리가 데이터 주도 디자인의 핵심이다.


    2. "정책 변경 = 한 줄 수정"의 위력

    이 디자인의 진짜 가치는 변경 시점에 드러난다. 예를 들어 새 정책 — "이미지 분석 자주 쓰니까 vision을 background로 바꿔서 미리 떠 있게 하자"라는 결정이 들어왔다고 하자. 정책이 코드 곳곳에 박힌 시스템에서는 vision_manager.py의 timeout, daemon 모드, 자동 시작 트리거, idle 정리 등 여러 곳을 수정해야 한다. 위험한 리팩토링이 된다.

    diagram

    이 그림이 보여주는 변경 시나리오의 우아함 — 위쪽 노란색 영역에 변경 전/후 대비를 보면 jobs.conf 한 줄에서 단 한 단어만 바뀐다(on-demandbackground). 그 한 단어가 발효시키는 동작 변화가 아래 녹색 박스에 다섯 개 있다. 가장 중요한 건 네 번째 줄 — "Python 코드, vision_manager.py, proxy.py, 에이전트 도구 — 0줄 수정"이다. 같은 정책 변경이 코드 곳곳에 박힌 시스템에서는 여러 파일의 timeout 조정·자동 시작 트리거 추가·idle 정리 로직 수정 등이 필요하지만, 데이터 주도 디자인에서는 jobs.conf 한 단어만 바뀐다.

    그리고 다섯 번째 줄 — "마음에 안 들면 한 단어만 되돌리기"가 변경의 안전성을 보여준다. 새 정책이 며칠 운영해보니 안 좋다면 즉시 되돌릴 수 있다. 코드 변경이라면 git revert + 재배포 + 검증 사이클이 필요하지만, 데이터 변경은 vim으로 한 단어 수정 + 스케줄러 재시작이면 끝이다. 실험 비용이 낮으면 더 자주 실험하게 된다.

    이 디자인의 진짜 가치는 "정책이 데이터로 표현됐기 때문에 정책 실험이 안전하고 빠르다"는 점이다. 코드 변경 없이 정책 한 줄만 수정해서 며칠 운영해보고, 안 맞으면 되돌리고, 마음에 들면 그대로 둔다. 코드 위에 정책이 박혀 있으면 이런 실험은 매번 위험한 PR이 된다.


    3. 같은 원칙이 적용된 다른 자리 — memory-store SoT

    "정책을 한 곳에 모은다"는 원칙이 GPU 스케줄러에만 있는 게 아니다. 메모리 본문은 memory-store가 진실의 원천(Single Source of Truth)이라는 결정도 같은 발상이다.

    diagram

    이 그림이 보여주는 SoT 디자인은 "같은 정보를 두 곳에 두지 않는다"는 단순한 원칙의 적용이다. 왼쪽(챗봇)은 세션 자체의 메타만 보관한다 — id, 제목, 프로젝트 소속, 생성 시각이 그것이다. 메시지 본문은 보관하지 않는다. 오른쪽(memory-store)은 모든 메시지 본문 + long_term 메모리를 한 곳에 보관한다. 화살표가 보여주듯 챗봇이 메시지를 받으면 memory-store에 저장하고, 회상이 필요하면 memory-store에서 읽는다 — 챗봇이 자체 복사본을 들고 있지 않다.

    아래 박스의 네 가지 효과 중 가장 가치 있는 건 첫 번째 — "두 채널이 같은 long_term을 본다"는 점이다. 사용자가 텔레그램 채널에서 "내가 좋아하는 음식은 한식"이라고 알려주면, 그 사실이 long_term scope에 저장되고 다음에 챗봇 웹 UI를 열었을 때도 모델이 그것을 알고 있다. 두 채널이 따로 메모리를 들고 있다면 사용자가 같은 정보를 두 번 알려줘야 한다 — 사용자 짜증의 원인이 된다. SoT 한 곳에 모이니 한 번 알려준 정보가 모든 채널에 자동으로 퍼진다.


    4. 트레이드오프 — 데이터 주도 디자인의 비용

    4-1. jobs.conf는 신뢰되는 설정이라 보안 경계가 중요

    스케줄러는 start_cmd를 그대로 eval한다 — 임의 명령 실행이 가능하다는 뜻이다. 그러니까 jobs.conf를 신뢰되지 않은 프로세스가 쓰게 두면 시스템 침해다. 누군가 jobs.conf에 "; rm -rf ~" 같은 명령을 넣으면 다음 GPU 작업 시작 때 그게 그대로 실행된다.

    1인 가정용 서버에서는 이 위험이 작다 — jobs.conf 권한이 사용자 본인에게만 있고, 외부에서 수정할 경로가 없다. 그러나 다중 사용자·CI 등으로 확장될 때는 jobs.conf 권한 모델이 즉시 필요해진다. 누가 jobs.conf를 수정할 수 있는지, 변경이 자동 적용 전에 누구의 승인을 받아야 하는지, 변경 이력이 어떻게 추적되는지 — 모두 데이터 주도 디자인의 일반 보안 트레이드오프다.

    완화책은 세 가지다 — (a) jobs.conf를 git 레포에 두고 변경을 PR로 받아 검토하고, (b) 스케줄러가 시작 시 jobs.conf의 SHA-256 해시를 검증해서 무단 수정을 감지하고, (c) 변경 이력을 audit log에 자동 기록한다. 모두 가정용 챗봇에서는 과도하지만 회사 환경에서는 필수다. 데이터 파일이 코드와 같은 신뢰 등급을 갖는다는 점을 인식하는 게 시작이다.

    4-2. 정책이 데이터에 있으면 "코드가 무엇을 하는지" 읽기가 더 어렵다

    vision_manager.py 코드만 봐서는 "vllm-chat을 선점하는구나"를 모른다. 코드는 단순히 scheduler.acquire("gemma-vision")만 호출한다. 누가 누구를 선점하는지 알려면 jobs.conf를 따로 봐야 한다. 로직은 코드에, 정책은 데이터에 있어 두 곳을 같이 봐야 시스템 동작을 이해한다.

    이게 변경에는 좋지만 학습에는 부담이다. 새로 합류한 개발자가 vision_manager.py를 처음 볼 때 — 코드만 보면 "scheduler에 acquire하는구나"는 이해하지만, "왜 이게 vllm-chat을 stop하는 효과가 있지?"는 모른다. jobs.conf를 발견하고 priority 25 vs 30을 비교해야 비로소 전체 그림이 보인다.

    이 분리가 만드는 학습 곡선을 좋은 docs로 보완해야 한다. 시리즈 #12 자체가 그 일환이다 — "정책이 어디 있고 어떻게 표현되는지"를 명시적으로 설명했다. 코드의 docstring에 "이 acquire 호출의 효과는 jobs.conf의 priority 설정에 따라 결정됨" 같은 안내를 덧붙인다. 새 작업 추가 시 README에 "이 작업이 영향받는 다른 작업 목록"을 함께 적는다. 모두 docs 작업이지만, 좋은 데이터 주도 디자인의 필수 동반자다.

    4-3. memory-store SoT 의존이 챗봇의 가동 결합을 만든다

    SoT의 가장 큰 트레이드오프 — 챗봇이 memory-store 없이는 못 뜬다. memory-store가 일시 다운되면 챗봇도 답을 못 한다. 단일 호스트라 둘이 같은 머신에 있으니 가용성 문제는 작지만, 두 모듈의 가동이 결합되어 있다는 의미다.

    구체적인 시나리오 — memory-store 모듈을 업데이트하면 챗봇 동작에 영향을 줄 수 있다. memory-store의 API 시그니처가 바뀌면 챗봇 코드도 같이 수정해야 한다. 두 프로젝트의 배포가 동기화되어야 한다. 1인 운영자라 머릿속에서 이 동기화를 관리할 수 있지만, 다중 메인테이너 환경에서는 명시적인 인터페이스 계약(예: API 버전 관리, breaking change 정책)이 필요해진다.

    또 하나 — 만약 이 챗봇을 다른 머신에 가져가서 띄우려면 memory-store도 함께 옮겨야 한다. 챗봇 단독 분리가 어렵다. 가벼운 챗봇 분리 가동을 원하면 SoT를 챗봇 안으로 가져오는 디자인 (역방향)이 필요하지만, 그러면 위 첫 번째 효과("두 채널이 같은 메모리를 본다")가 깨진다. 디자인 결정의 결과를 받아들이는 셈 — SoT의 단순함과 단독 가동의 자유는 양립하기 어렵다.


    5. 시리즈를 닫으며 — 12편의 한 줄 요약

    이 시리즈를 시작한 가설은 단순했다. "상용 챗봇 기능의 70%는 모델 능력이 아니라 시스템·UX 레벨에서 만들어진다." 12편을 거치며 확인한 셈이다 — 모델은 그대로, 의존성은 거의 추가하지 않았는데 챗봇이 "쓸 만한" 정도가 됐다. 12편의 공통된 패턴을 한 줄씩 정리하면:

    • #1 메시지 편집 — 책임을 한 가지로 좁히면 평상 흐름이 그대로 재사용된다
    • #2 Project 계층 — 같은 정보도 어떤 순서로 모델에 보여주냐가 행동을 바꾼다
    • #3 ContextVar — 도구 시그니처에 노출하지 않는 보이지 않는 의존성을 주입한다
    • #4 Vision 7B — 통합 메모리 시스템에선 마진이 능력보다 중요하다
    • #5 venv 빌려쓰기 — 같은 머신에 있는 인프라는 import 말고 호출하라
    • #6 슬래시 커맨드 — 작은 모델의 메타 판단 한계는 명시 채널로 메운다
    • #7 도구 paradox — 도구가 늘면 호출 정확도가 떨어진다
    • #8 봇 — 도구 풀을 작업 단위로 미리 좁히는 사용자 도구다
    • #9 graceful failure — 외부 의존 실패가 채팅을 막지 않게 데이터 구조를 디자인한다
    • #10 [hidden] 버그 — HTML5 속성도 user-agent CSS 한 줄에 의존한다
    • #11 Esc 우선순위 — 키 한 키의 동작이 손맛의 절반을 만든다
    • #12 정책 데이터화 — 변경이 한 줄이면 실험이 안전하고 빠르다

    나머지 30%는 모델 능력에 진짜로 묶여있다. 다단계 reasoning, 정밀한 멀티모달, 실시간 음성이 그렇다. 그건 모델이 따라올 때까지 기다린다. 그동안 이 챗봇은 우리 가족과 내 작업의 일부로 자리 잡았다.

    다음 시리즈는 운영 6개월쯤 지나서 발견할 "실전에서 깨지는 것들"이 될 것 같다. 시스템 레이어의 약점은 운영 시간이 다 드러내준다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.