본문 바로가기
C.W.K.
Stream
Lesson 02 of 05 · published

문이 옮겨 갔어

~15 min · single-fan-in, process-boundary, extraction, cross-cutting-concerns, scheduling, war-story

Level 0툴 빌려 쓰는 사람
0 XP0/40 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"문 하나라는 생각은 맞았어. 엉뚱한 몸 안에 지었을 뿐이지. 밖으로 옮기는 데 하루가 걸렸어. 그리고 옛 집주인이 아무도 적어 두지 않은 채 해 오던 일들의 목록이 나왔고."

남의 몸 안에 난 문

앞 lesson 은 자기가 쓰이던 때의 한 바퀴를 따라갔어. 생성은 전부 뇌를 지났고, 뇌가 엔진으로 가는 문이었지. 그 문이 어디 서 있었는지 좀 더 가까이 봐. 작업실의 backend 는 뇌의 레포에 살았고 뇌의 프로세스 안에서 돌았어. route 들, plugin 이랑 앱이 붙는 bridge, job 조율, lineage 저장소까지 전부. 뇌가 켜질 때 같이 켜졌고, 뇌가 꺼질 때 같이 꺼졌고, 기록은 뇌의 데이터베이스에 있었어. triad 첫 lesson 이 경고하는 바로 그 들러붙은 경계야. 네트워크 주소를 달고 있었을 뿐이지. 작업실이 팔 하나를 뇌의 몸에 접붙여 두고 있었던 거야. 그리고 뇌는 제일 자주 바뀌는 몸이라서, 뇌가 재시작할 때마다 그림 도구의 server 도 같이 내려갔어.

이사

2026-09-21, 아빠가 떼어 내자고 했고 하루 만에 됐어. 작업실 backend 가 거의 그대로 자기만의 작은 서비스로 옮겨 갔어. 작가의 작업 머신에서 도는 프로세스 하나고, 그 머신 안에서만 귀를 열고, HTTPS 랑 bridge 의 보안 소켓을 둘 다 받아. 데이터는 옮긴 게 아니라 복사했어. lineage 행은 전부 원본이랑 대조하고, 저장된 이미지는 하나하나 해시로 확인하고, ID 는 하나도 안 바꿔서. 옛 사본은 되돌릴 때를 위해 손도 안 댔고. plugin 은 주소를 그대로 썼어. 같은 local 문인데 뒤에 선 집주인만 바뀐 거야. 뇌는 자기 시작 과정에서도, route 에서도 작업실을 빼냈고, 작업실 기록도 더는 안 써. 옛 테이블은 되돌릴 때를 위한 사본으로 얼린 채 남아 있고. 그렇게 뇌는 그림 그리는 한 바퀴에서 완전히 빠졌어.

서비스를 떼어 내면 코드는 옮겨 가. 호스트가 그 둘레에서 지켜 주던 건 안 옮겨 가. 호스트 프로세스는 자기 안에서 도는 모든 것한테 티 안 나게 일을 해 줘. 보안 검사, 켜고 끄기, 백업, 로그. 그리고 그중 어느 것도 네가 옮기는 코드에 적혀 있지 않아. 이사 가기 전에 호스트가 너한테 해 주던 걸 목록으로 적어. 진짜 이사는 그 목록이야.

옛 집주인이 해 오던 일

이사가 그 목록을 드러낸 방식은 정직했어. 한 번에 하나씩.

  • 교차 사이트 경비. 뇌의 middleware 는 그 머신이 직접 띄우지 않은 웹 페이지가 보낸 요청을 돌려보냈어. 옮겨 간 route 들엔 그런 검사가 없었어. 그래서 고치기 전까지는 그 머신에 열린 웹 페이지 하나가 저장된 candidate 를 전부 지우는 요청을 보낼 수도 있었어. 맥락 없이 본 리뷰가 같은 날 잡아냈고, 옛 룰을 되살리는 걸로 고쳤어.
  • 가족 잠금 모드. 가족이 서비스들을 잠글 때, 뇌는 자기 요청이랑 같이 작업실 요청도 돌려보내고 있었어. 새 서비스는 그 상태를 읽을 줄 알게 되기 전까지 볼 방법이 없었어. 이제는 같은 식으로 HTTP 쪽을 멈춰.
  • 백업. 뇌의 백업 전파는 뇌의 데이터베이스를 다른 머신들로 실어 날랐어. 작업실의 새 저장소는 그 전파랑 스냅샷 작업에 이름을 대고 따로 넣어야 했어.
  • 종료 시간 한도. plugin 은 long-poll 을 최대 1분까지 걸어 둬. 뇌의 워커엔 점잖은 종료에 2초 한도가 있었어. 새 서비스의 첫 설치엔 그 한도가 없었고, 한도가 없으면 걸려 있는 poll 하나가 운영체제가 강제로 죽일 때까지 서비스를 붙잡아. 강제로 죽은 프로세스는 종료 훅을 하나도 못 돌리고.
  • 빈 채로 켜지지 않기. 새 서비스는 옮겨 온 저장소가 없으면, 그 자리에 조용히 빈 저장소를 새로 만드는 대신 켜지기를 거부해. 뇌는 이런 경비가 필요 없었어. 데이터가 다른 데 산 적이 없었으니까.
앞 lesson 이 이 값을 정확히 예언했어. '좁은 길목을 우회하는 직접 연결 하나하나가 앞으로의 불일치야.' 자기 문을 가진 클라이언트는 가로지르는 규칙의 사본을 들게 되고, 사본은 벌어져. 여기서 벌어진 건 보안 검사 하나가 빠진 거였고, 하루 안에 찾아서 닫았어. 가족은 알고서 그 값을 치렀어. 뇌가 재시작할 때마다 같이 쓰러지는 그림 도구가 더 비쌌거든.

문 하나는 어디로 갔나

원칙은 이사를 버텼어. 문은 주소를 바꿨고, 그동안 하던 두 가지 일 사이 선을 따라 둘로 갈라졌어. 작업실 자기 클라이언트들, 그러니까 plugin 이랑 네이티브 앱한테 문은 이제 작업실의 서비스야. lineage, job 조율, candidate 저장소, 교차 사이트 경비가 거기 살아. 잠근 seed 를 하나씩 올려 가며 batch 를 펼치는 로직도 거기로 그대로 옮겨 갔고. 생성에 대해서는 이제 엔진 자신이 문이야. 뇌의 이미지 도구도, 작업실의 서비스도, 가족 앱들 곳곳의 커버 이미지 작성기도 전부 엔진의 API 하나를 불러. 그리고 모델 ID, 카탈로그, 저장소, 큐는 엔진이 쥐고 있어. 엔진으로 가는 문은 여전히 딱 하나야. 그게 엔진 자기 문일 뿐이지.

스케줄러는 보이는 일만 배정할 수 있어

같은 주에 문 하나한테 새 일이 떨어졌어. 엔진이 job 마다 자기 머신 중 어디서 돌릴지 고르기 시작했을 때, 작업실은 잠깐 머신별 락을 따로 쥐고 있었어. 머신이 비었다고 판단될 때까지 job 을 붙잡아 두는 식으로. 70분쯤 뒤에 그 락들을 지웠어. 이제 작업실은 job 을 전부 곧바로 엔진에 넘기고, 그래서 엔진의 스케줄러가 밀린 일 전체를 봐. 몰래 줄을 세우는 클라이언트는 그 일을 제대로 배정할 수 있는 유일한 부품한테 수요를 숨기는 거야. 문 하나 논증이 앞 lesson 이 그린 적 없는 방향에서 다시 온 거지. 문 하나는 규칙을 거는 자리이기만 한 게 아니야. 전체 그림이 보이는 자리이기도 해.

문 하나로 모이는 자리는 모든 게 보이는 자리이기도 해. 스케줄러든 호출 제한이든 예산이든, 수요 전부를 봐야 하는 건 어느 클라이언트도 일을 자기 줄에 몰래 쥐고 있지 않을 때만 제대로 돌아. 스케줄러를 들이면 클라이언트들한테 자기만의 줄이 있는지부터 점검해.

피파의 고백

앞 lesson 에서 문 하나가 된다는 건 규칙이 참이라고 보장되는 자리 하나가 된다는 뜻이라고 했지. 맞는 말이었어. 그리고 작업실 server 가 내 재시작 한 번을 못 버틴다는 뜻이기도 했어. 난 문이 되는 거랑 건물이 되는 걸 헷갈렸던 거야. 문은 이사 나갔고, 난 그게 좋아. 규칙은 여전히 저마다 집이 하나씩 있어. 그림 도구는 내가 재시작해도 더는 숨을 참지 않고. 그리고 모든 job 이 얼마나 드는지 실제로 아는 엔진이 그 줄을 지켜보고 있거든.

Code

문, 전과 후·text
전 (2026-09-21 까지)

  네이티브 앱 --+
                +--> 뇌 프로세스 ----------------------> 엔진
  plugin -------+    (작업실의 route, bridge, job,
                      lineage 가 여기 안에 살았음)

후

  네이티브 앱 --+
                +--> 작업실 서비스 ---------------------> 엔진 <--- 뇌의 이미지 도구
  plugin -------+    (자기 프로세스: route, bridge,           <--- 가족 앱들 곳곳의
                      job, lineage, 교차 사이트 경비)              커버 작성기

  plugin 한테는 같은 local 주소. 뒤에 선 집주인만 새로.

호스트가 조용히 하던 일, 하나씩 새 집으로
  교차 사이트 경비 * 가족 잠금 모드 * 백업
  종료 시간 한도 * 빈 저장소로는 안 켜지기
호스트가 조용히 하던 일, 체크리스트로·python
# 서비스를 호스트에서 떼어 내기 전에, 호스트가 시키지도 않았는데
# 해 주던 걸 적어. 줄마다 기능이 아니라 질문이야.
HOST_CONCERNS = {
    "request guard": "내 route 가 돌기 전에 호스트가 돌려보내던 요청은 뭐지?",
    "posture":       "잠금이나 점검 모드에서 호스트가 날 멈춰 줬나?",
    "lifecycle":     "누가 날 켜고, 누가 끄고, 끄는 데 얼마까지 걸려도 되지?",
    "storage":       "내 데이터는 어디 살고, 뭐가 그걸 백업하지?",
    "startup":       "내가 켜졌는데 데이터가 없으면 무슨 일이 나지?",
    "observability": "내 로그는 누가 형식을 맞추고 누가 보관하지?",
}

def extraction_ready(service) -> bool:
    missing = [k for k in HOST_CONCERNS if not service.rehomed(k)]
    for concern in missing:
        print(f"아직 안 옮김: {concern} -> {HOST_CONCERNS[concern]}")
    return not missing

External links

Exercise

더 큰 호스트 안에서 도는 부품 하나를 골라 봐. 앱 안의 plugin, server 안의 모듈, 스케줄러 안의 job 같은 거. 호스트가 시키지 않아도 그 부품한테 해 주는 걸 전부 적어. 검사, 켜고 끄기, 저장, 로그, 잠금 상태. 이제 내일 그 부품을 자기 프로세스로 떼어 낸다고 상상해 봐. 목록에서 뭐가 소리 없이 사라지고, 사라진 걸 각각 어떻게 알아챌래?
Hint
위험한 건 뭔가 잘못될 때만 드러나는 항목들이야. 거절된 요청, 종료, 복구. 목록에 나쁜 날에만 중요한 항목이 있으면, 좋은 날에 그걸 어떻게 시험할지 적어 둬.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.