3.19TB 이관 ETA가 76일이라고 나왔습니다 — 범인은 도쿄를 거치는 릴레이 경로
검사실 쪽 Windows 워크스테이션 하나가 아팠습니다. 원격 데스크탑 접속이 어느 날부터 안 열려서, 결국 SSH로만 상태를 확인해야 했어요. SSH는 열리니 기계가 완전히 죽지는 않았지만, 짐을 옮길 문은 계속 닫혀 있었어요. 그 컴퓨터가 아픈 데에는 뒤에 또 다른 사정이 있었지만, 그날의 진짜 본업은 따로 있었습니다. 영상 데이터 이관이요. 현장 폐쇄망 안의 이미징 스토리지에 폴더 11개, 합계 3.19TB가 담겨 있었고, 연구실 워크스테이션 드라이브로 통째로 옮겨야 했습니다.
예전에는 원격 데스크탑으로 접속해서 탐색기를 끌어다 복사하는 방식으로 이런 일을 처리했습니다. 그런데 대용량 폴더를 옮기면 부하가 심하게 걸려서 컴퓨터가 다운되는 일이 반복됐어요. 그래서 죽어도 이어지는 구조가 필요했습니다. 스크립트로 돌리고, 끊긴 자리에서 다시 붙는 방식이요.
경로에는 또 다른 제약이 있었습니다. 출발지 스토리지는 현장의 이미징 노드에서만 접근할 수 있고, 그 노드는 연구실 워크스테이션과 직결돼 있지 않았어요. 두 현장을 잇는 연결은 Tailscale 하나뿐이었습니다. Tailscale은 네트워크 설정을 복잡하게 건드리지 않아도 기기 사이에 VPN 터널을 뚫어주는 도구입니다. 터널이 열린다는 사실만 믿고 있었고, 그 터널이 어떤 경로를 타는지는 그때까지 아무도 안 보고 있었습니다.
이사 전에 짐 목록을 세는 것과 같은 일이었습니다. 복사를 붙이기 전에 재고부터 확정했습니다. robocopy에는 실제로 옮기지 않고 무엇을 옮길지 미리 세어주는 목록 모드(/L)가 있는데요. 이걸로 재보니 11개 폴더 중 단일 폴더 하나가 2.67TB로, 전체의 84%를 차지하고 있었습니다. 이 재고 조사 덕분에 나중에 "가장 무거운 건 맨 뒤로"라는 순서 전략이 가능했어요.
첫 가동, 그리고 76일이라는 숫자
현장 노드에서 Tailscale을 거쳐 연구실 워크스테이션으로 내리는 경로로 복사를 시작했습니다. 실측 속도는 0.5~0.6MB/s였어요.
Tailscale의 터널은 보통 두 기기 사이에 직결로 뜨는 게 정상입니다. 그런데 직결이 안 열릴 때, Tailscale은 DERP라는 공개 릴레이 서버로 트래픽을 우회시켜요. 직항편이 없을 때 제3의 도시를 거쳐 돌아가는 셈이죠. 문제는 이 노드만이 그 경유편을 타고 있었습니다. 같은 계층의 다른 워크스테이션들은 직결인데, 유독 이 노드와 도착지 사이의 터널만 도쿄 DERP 릴레이 경유로 붙어 있었어요. 두 기기가 직접 마주 보지 못하고 한복판 서버를 거치는 방식이니, 속도가 떨어지는 건 경로의 물리로서는 당연한 일이었습니다. 원인도 결국 터널의 경로 타입을 들여다보고 나서야 드러났어요. 그 전까지는 회선이 막혔거니 하고 내려다본 것은 오직 처참한 복사 속도뿐이었습니다.
76일은 여기서 나온 숫자입니다. 무슨 공식 보증 값이 아니라, 이 속도라면 3TB 남짓을 다 옮기는 데 76일 걸린다고 에이전트가 그때 보고한 ETA 산출값이었어요. 그냥 둘 수 없는 숫자였습니다. 냉장고 하나 옮기려고 용달을 부르는 수준이 아니라, 리어카 하나 밀어서 동네 이사를 하는 계산이었으니까요. 릴레이가 보험이라면 가입 자체는 나쁘지 않은 선택입니다. 문제는 보험료를 확인하지도 않은 채, 76일이라는 시간을 고스란히 내고 있던 형국이었다는 거죠.

그림의 점들은 전부 실측값입니다. 두 구간 사이에는 아무것도 없는 게 아니라, 릴레이냐 직결이냐 하는 경로 하나가 놓여 있었을 뿐이에요.
Windows가 세션을 가리는 함정
속도를 찾기에 앞서 장애물부터 걷어내려 했는데, 첫 벽은 Windows 쪽에 있었습니다. 스토리지에 접근하려면 토큰이 필요한데, 이 토큰이 원격 데스크탑 대화형 로그온 세션에서만 살아 있었어요. 같은 계정으로 비대화형 SSH 세션을 열면 스토리지 접근이 전부 거부됐습니다. 계정이 다른 게 아니라, 그 계정이 서 있는 자리의 종류가 문제였던 셈이에요.
그래서 파이프라인을 사람의 생활 리듬에 맞춰 설계할 수밖에 없었습니다. RDP를 로그오프하면 전송이 일시정지되고, 다시 로그온하면 자동으로 재개되는 구조요. 잠깐 멈춘다고 데이터가 깨지지는 않으니, 세션의 성질을 거스르며 RDP를 빈 채로 열어둘 이유가 없었습니다. 사람의 자리 이탈에 맞춰 잠시 멈추고, 돌아오면 이어서 가는 쪽을 택한 거예요. 데이터는 깨지지 않습니다.
실행체는 PowerShell 스케줄러에 실었습니다.
robocopy /MT:8 /R:3 /W:60 /XJ
폴더별 로그와 state 파일을 곁에 두고, 매시간 재점화하는 스케줄 태스크로 트리거를 놓쳐도 시간을 안 잃게 짰습니다. 락 가드로 이중실행도 차단했고요. 이 옵션 한 줄이 사실상 보험 약관입니다. /MT:8로 여덟 겹 병렬, /R:3과 /W:60으로 막히면 60초 기다렸다가 세 번 재시도, junction처럼 이어붙인 경로 루프는 /XJ로 차단입니다. robocopy는 원래 증분 복사에 강해서, 이미 온 파일은 스킵하고 나머지만 채워 넣습니다. 도착지에는 지난번 미완 이관의 잔재들이 굴러다녔는데, 증분 복사 덕분에 그 잔재가 그대로 밑천이 됐어요. 이미 와 있는 파일이 많을수록 다음 번 실행이 빨라지는 구조, 그게 이 파이프라인의 핵심이었습니다.
세 가지 선택지, 그리고 고른 두 가지
에이전트가 제시한 회피안은 세 가지였습니다. A는 현장 라우터에 포트포워딩을 뚫어서 직결로 붙이는 안이었습니다. B는 직결되는 다른 워크스테이션을 중계 홉으로 쓰는 안이에요. C는 범위를 조절하는 안으로, 급한 폴더부터 받고 가장 큰 폴더는 큐 맨 뒤에 두는 것이었습니다. 결론은 C와 B였습니다. A가 빠진 것은 실력이 아니라 사정 때문이에요. 현장의 네트워크 설정을 함부로 고칠 수 없는 상황이라, 이 선택지는 검토 대상에서 처음부터 빠져 있었습니다. C는 속도를 해결하는 안이 아니라 피해를 줄이는 안이었습니다. 느릿한 길이라도 먼저 쓸 폴더부터 확보하자는 얘기죠. 어떤 순서로 받을지를 정할 수 있었던 것도 아까 그 재고 조사 덕분이었습니다.
B를 검증하는 도중에 또 하나의 구조적 사실을 만났습니다. 그냥 중계만 되면 되겠지 했던 그 워크스테이션이, 사실 소스 스토리지를 읽을 수 없었어요. 스토리지를 읽을 수 있는 세션은 현장 노드 쪽 대화형 로그온에만 살아 있었기 때문이죠. 우리 집 문을 대신 열어줄 줄 알았던 옆집이 사실 열쇠가 없는 것과 같았습니다. 이 확인을 미루고 바로 적용에 들어갔더라면, 시작조차 못 한 경로 앞에서 시간을 더 흘려보냈을 테니 억울한 검증은 아니었어요.
그런데 스토리지를 직접 읽을 수 있는 계정이 확보되는 길이 열렸고, 즉시 재배선했습니다. 2-홉 구상은 접고, 중계 워크스테이션에서 도착지로 빠지는 direct 단선 경로를 그렸습니다. 증분 재개를 위해 로그와 state 파일을 미리 짜 두었던 터라, 경로를 다시 그리는 데는 잔재 정리도 필요 없었어요. 이미 떠 있던 실행 구조에 선만 갈아끼우는 작업으로 끝났습니다.
9배, 그리고 재배선 뒤에 남은 것
재배선한 뒤의 실측은 5.6~6.2MB/s였습니다. 릴레이 경유 대비 약 9배죠. 전환 시점은 그날 밤 10시경이었고, 그때까지 완료돼 검증된 것을 짚으면 이렇습니다. 311MB짜리 작은 폴더는 바이트 일치가 확인됐고, 3.95GB 폴더는 소스와 타깃이 정확히 일치했어요. 15.9GiB 폴더는 39%까지 진행 중이었습니다. 옮겨붙였다는 것과 내용이 같다고 검증됐다는 것은 다른 상태인데, 여기서 센 것은 전부 후자입니다. 약 516GB짜리 급한 폴더 10개의 ETA는 "내일 이맘때"로 보고됐습니다.
하지만 여기서 매끈하게 끝을 못 맺습니다. 2.67TB짜리 최대 폴더는 아직 큐 맨 뒤에 대기 중이거든요. 직결 속도 6MB/s로 환산해도 5일 남짓 걸리는 규모입니다. 9배가 됐어도 여전히 5일이 걸리는 게, 실제로 일어난 일에 대한 정직한 속도였습니다.
긴 레이스의 감시는 에이전트가 아니라 스크립트 크론에 맡겼습니다. 에이전트 토큰을 쓰지 않는 no_agent 방식인데, 3시간마다 진행률과 속도와 ETA를 채널로 보냅니다. 죽으면 재점화하고, 재부팅을 해도 시간대 트리거로 이어가는 구조입니다. 굳이 no_agent로 돌린 이유는 단순합니다. 살아있는지 확인하는 일에 매번 토큰을 태울 필요는 없으니까요. 판단은 에이전트에게 맡기고, 심장은 부엌 타이머처럼 단순하게 두자고 정했습니다.
DERP는 보험이지 회선이 아닙니다
이 이관에서 건진 것은 속도가 아니라 습관 하나입니다. 전송을 붙이기 전에 tailscale status로 터널의 경로 타입부터 확인하는 습관이에요. direct가 걸려 있는지, relay로 우회되고 있는지 한 줄만 보면 판단이 달라집니다. DERP는 터널이 안 뚫릴 때 죽어라 이어주는 보험이지, 회선 대역폭이 아닙니다. 이걸 모른 채 복사를 붙였다면, 76일짜리 이관을 그냥 모르고 돌리고 있을 뻔했어요. 직결이 안 되면 릴레이로 조용히 대체해주는 것이 이 도구의 친절인데, 그 친절이 느리다는 청구서로 돌아온 격이었습니다. 터널은 열려 있고 복사는 돌고 있으니, 진실을 말하는 유일한 숫자는 속도밖에 없었던 거죠.
아직 남은 것도 분명합니다. 2.67TB 최대 폴더는 큐에 대기 중이고, 도착지 중복 정리는 이관 완료 후 2단계로 예약해 뒀습니다. 같은 원본의 사본들과 과거 미완 이관의 잔재까지 한꺼번에 치울 계획이에요. 같은 공유 폴더에는 새 연구 산출물이 계속 만들어지고 있어서, 본 이관이 끝나는 시점에 그 부분만 받아오는 점검 스케줄도 짜 뒀습니다. 중복 정리를 완료 후 2단계로 미룬 것도 같은 이유예요. 복사판이 아직 돌아가는데 밑천인 잔재를 건드리면 안 되니까, 이관을 끝까지 채운 다음에 정리하기로 순서를 정했습니다. A안인 포트포워딩을 열었다면 6MB/s가 아니라 회선 자체의 속도를 봤겠지만, 현장 사정상 그럴 수가 없었어요. 릴레이가 아니면 직결 홉, 그게 우리가 쓸 수 있는 물리의 상단이었습니다.
76일이라는 숫자를 만난 건 아쉽지 않습니다. 경로를 먼저 보라는 습관을 공짜로 가르쳐줬으니요. 다만 두 번 다시 만나고 싶은 숫자는 아닙니다. 남은 2.67TB가 다 채워지면 완료기를 들고 다시 돌아오겠습니다. 그 사이에도 3시간마다 재점화되는 진행률 그래프를 조용히 지켜보고 있을게요.