두 서버 사이에서 큰 폴더 동기화하기(재개 가능, 백그라운드)
마지막 업데이트: 2026-08-05
한 서버에 큰 폴더(수십 GB)가 있고, 다른 서버에 똑같은 복사본을 원한다고 합시다. 몇 시간 동안 터미널을 지켜볼 수도 없고, 도중에 죽은 전송이 처음부터 다시 시작되어서도 안 됩니다. relayium sync는 바로 이를 위해 만들어졌습니다. 이미 있는 것은 건너뛰고, 절반만 보낸 파일은 멈춘 지점부터 재개하며, 보낸 파일을 끝에서 끝까지 검증하는 단방향 증분 미러입니다.
이 가이드는 무인으로 스스로 복구하는 전송을 구성합니다. 보내는 쪽을 한 번만 승인하고, 리스너를 백그라운드에서 돌리고, tmux 안의 재시도 루프로 relayium sync를 구동해 연결이 끊겨도 계속 진행하여 폴더 전체가 도착할 때까지 실행합니다.
왜 relayium sync가 이 작업에 맞나
sync는 네이티브 프로토콜 위의 단방향 증분 미러입니다(양쪽 끝에 relayium을 설치). 무인으로 반복 실행해도 안전한 이유가 세 가지 있습니다:
- 이미 있는 파일은 건너뜀: 받는 쪽 복사본이 크기와 수정 시각으로 일치하는 파일은 다시 보내지 않습니다.
- 부분 파일 재개: 연결이 끊길 때 절반만 전송된 파일은, 다음 실행에서 디스크에 이미 있는 바이트 오프셋부터 이어가며 다시 시작하지 않습니다.
- 보내는 것을 검증: 전송하는 각 파일은 — 이어받은 파일도 포함해 — 송신 측의 SHA-256과 끝에서 끝까지 대조되고, 어긋나면 실패로 보고됩니다. 건너뛰는 파일은 크기와 mtime으로만 판정하며 해시를 다시 계산하지 않으므로, 보내지 않은 내용에 대해 sync는 아무것도 보장하지 않습니다.
- 이 덕분에 명령은 멱등적입니다. 다시 실행해도 남은 일만 합니다. 이것이 바로 재시도 루프가 거대한 전송을 끝낼 수 있게 하는 핵심입니다.
사전 준비
필요한 것
- 이 가이드는 데몬 다이렉트(relayium://)를 쓰므로 두 서버 사이에 SSH 접근이 필요 없습니다.
- 받는 쪽 방화벽이나 보안 그룹에서 리스너 포트(기본 9031)를 보내는 쪽에 열어 두세요.
- 받는 쪽에 폴더 전체가 들어갈 공간. 몇 시간짜리 전송을 시작하기 전에 보내는 쪽의 du -sh /root/workspace 와 받는 쪽의 df -h /root 를 맞춰 보세요.
양쪽 서버에 relayium을 설치하세요(sync는 네이티브 프로토콜을 쓰므로 각 끝에 필요합니다):
# 양쪽 서버 모두에서
curl -fsSL https://relayium.com/install.sh | sh
보내는 쪽을 한 번만 승인하기(받는 쪽)
받는 쪽은 보내는 기기를 한 번만 승인합니다. 승인은 디스크에 기록되어 재시작을 넘어 유효하므로 반복할 필요가 없습니다. 먼저 터미널에서 리스너를 포그라운드로 시작하고, --dir을 부모 디렉터리로 향하게 하세요. relayium sync /root/workspace는 받는 쪽에서 workspace/...를 재현하므로, --dir /root이면 파일이 /root/workspace/에 안착합니다.
보내는 쪽의 첫 연결 시(다음 절), serve가 그 주소와 핑거프린트를 보여주며 승인을 요청합니다. y라고 답하면 영구적으로 기억됩니다:
# 받는 쪽에서(포그라운드, 대화식으로 승인하기 위해)
relayium serve --dir /root --port 9031
# 받는 쪽에서, 첫 연결 시:
Incoming push from 203.0.113.9:52140
fingerprint: 9f2c41ab…
Accept and remember this peer? [y/N] y
- 완전 무인 구성이라면 프롬프트를 건너뛰세요: 보내는 쪽에서 relayium id로 핑거프린트를 출력하고, 받는 쪽에서 relayium authorize <핑거프린트>를 실행합니다.
- --dir은 동기화하는 폴더의 부모이지 폴더 자체가 아닙니다. 그렇지 않으면 파일이 한 단계 더 깊이 안착합니다(예: /root/workspace/workspace).
리스너를 백그라운드로 돌리기(받는 쪽)
핑거프린트를 승인했으면 포그라운드 serve를 멈추고(Ctrl-C), 로그아웃 후에도 살아남도록 분리해서 다시 실행하세요. 저장된 핑거프린트를 불러와 보내는 쪽을 조용히 받아들입니다. 이번엔 프롬프트가 없습니다. 같은 줄이 새 프로세스의 PID를 ~/relayium-serve.pid에 기록하며, 이 가이드의 마지막 단계는 그것으로 자기가 띄운 리스너를 멈춥니다. relayium 이 들어간 명령을 문자열로 훑어 멈추는 방식이 아닙니다:
# 받는 쪽에서
nohup relayium serve --dir /root --port 9031 > ~/relayium-serve.log 2>&1 & echo $! > ~/relayium-serve.pid
- serve는 연결을 하나씩 처리하며 계속 실행되므로, 아래 재시도 루프의 매 재연결마다 받을 준비가 되어 있습니다.
- 항상 켜져 있는 수신함을 원하면 대신 systemd로 돌리세요(Restart=always, --config-dir /etc/relayium).
tmux 안의 재시도 루프로 sync 돌리기(보내는 쪽)
긴 전송은 중단됩니다. 세션 끊김, 불안정한 네트워크, 재부팅. 해결책은 멋진 도구가 아니라 sync를 성공할 때까지 다시 실행하는 루프와, 로그아웃 후에도 살아남게 하는 터미널 멀티플렉서입니다. 여기서는 tmux가 nohup보다 깔끔합니다: 틀리기 쉬운 출력 리다이렉트가 없고, 다시 붙어 진행 상황을 볼 수 있습니다.
tmux 세션을 시작한 뒤 until 루프로 미러를 실행하세요. 10초마다 재시도하고 sync가 성공을 반환하면 스스로 종료합니다:
루프가 그것을 시작한 ssh 세션보다 오래 살도록, 보내는 쪽에서 tmux 세션을 엽니다.
# 보내는 쪽에서 tmux new -s xfer # tmux가 없으면 apt install -y tmuxuntil 루프 안에서 미러를 돌립니다. 10초마다 재시도하다가 sync가 성공을 반환하면 스스로 끝납니다.
until relayium sync /root/workspace relayium://203.0.113.43:9031; do echo "$(date) retrying"; sleep 10; doneCtrl-b 다음 d로 분리합니다. 루프는 계속 돌고, 보고 싶을 때 언제든 다시 붙으면 됩니다.
tmux attach -t xfer
한 번의 성공적인 패스는 어떻게 보이나
한 번 돌 때마다 실제로 보낸 파일마다 한 줄이 찍히고, 이어서 이번에 보낸 수와 받는 쪽이 이미 갖고 있던 수를 세는 요약이 나옵니다. 루프는 sync가 처음으로 성공을 반환할 때 끝나며, 그 요약이 곧 미러의 상태입니다.
relayium sync /root/workspace relayium://203.0.113.43:9031
workspace/data/part-004.bin (1073741824 bytes)
synced: 1 sent, 812 unchanged- 재시도할 때마다 일이 줄어듭니다: 이미 전송된 파일은 건너뛰고, 절반 보낸 파일은 재개됩니다. 그래서 루프는 수렴하고 끝납니다.
- 진행 상황은 파일 하나가 완료될 때마다 한 줄씩 출력되므로, 큰 파일은 끝날 때까지 조용히 전송됩니다. 침묵은 멈춤이 아닙니다(문제 해결 참고).
검증과 마무리
until 루프가 끝나고 평범한 셸 프롬프트로 돌아오면 전송이 완료된 것입니다. 양쪽이 일치하는지 확인한 뒤 리스너를 멈추세요:
until 루프가 스스로 끝날 때까지 기다리세요. 평범한 셸 프롬프트로 돌아와 있다는 것은 전송이 끝났다는 뜻이지, 여러분이 중단했다는 뜻이 아닙니다.
양쪽 서버에서 합계를 비교합니다.
# 양쪽 서버에서 합계 비교 du -sh /root/workspace합계가 맞으면 받는 쪽 리스너를 멈춥니다. 기기의 relayium 명령을 문자열로 훑는 대신 실행할 때 기록해 둔 PID에 시그널을 보내며, PID 파일은 그 시그널이 성공했을 때만 지워집니다. 이전 실행이 남긴 PID 파일은 시스템이 이미 재사용한 PID를 가리킬 수 있으니, 그 파일이 아직 내 것인지 확신이 없으면 먼저 해당 PID의 명령줄을 출력해 보고 serve 가 나올 때만 죽이세요.
# 받는 쪽에서, 확인이 끝나면 ps -p "$(cat ~/relayium-serve.pid)" -o command= kill "$(cat ~/relayium-serve.pid)" && rm ~/relayium-serve.pid
미러가 끝났을 때 보이는 것
두 서버에서 du -sh 가 같은 총량을 보고하고, until 루프는 다시 재시도하는 대신 평범한 셸 프롬프트로 돌아와 있습니다. 총량이 같다는 것은 대략적인 완료 확인일 뿐, 내용이 같다는 증명은 아닙니다. sync는 크기와 mtime으로 어떤 파일을 보내지 않을지 결정하므로, 건너뛴 파일의 내용에 대해서는 총량이 아무것도 말해 주지 않습니다.
# the same total, on BOTH servers
46G /root/workspace- sync가 보낸 것은 검증되었습니다. 전송한 각 파일은 도착 시 SHA-256으로 검사되었고, 깔끔한 종료는 그 검사 중 실패가 하나도 없었다는 뜻입니다. 건너뛴 쪽은 크기와 mtime만 대조했으므로, du -sh 총량이 같다는 것은 완료 여부에 대한 대략적인 확인일 뿐 건너뛴 파일의 내용이 여전히 같다는 증명은 아닙니다. 그 증명이 필요하면 두 서버에서 파일별 체크섬을 직접 비교하세요.
문제 해결
몇 시간짜리 미러링에서는 여섯 가지가 나타납니다. 그중 셋은 실패처럼 보이지만 아니고, 나머지 셋은 진짜 문제입니다. 각각에는 지금 보고 있는 것이 어느 쪽인지 알려 주는 명령이 있습니다.
증상, 확인, 해결
- 한참 아무것도 출력되지 않아 전송이 멈춘 것처럼 보입니다.
# 보내는 쪽에서, 몇 초 간격으로 두 번 ss -tinp dst :9031 # ESTAB 리스너에 닿았다는 뜻일 뿐, 바이트가 움직인다는 증거는 아님 # SYN-SENT 리스너에 닿지 못함진행 상황은 파일 하나가 끝날 때만 출력되므로, 큰 파일 하나는 완전한 침묵 속에 전송됩니다. ESTAB은 닿았다는 것만 증명합니다. established 소켓도 놀고 있거나 멈춰 있을 수 있으니, 그것만으로는 결코 전진의 증거가 되지 않습니다. 이 확인을 몇 초 간격으로 두 번 실행해, -i 가 그 소켓에 대해 출력하는 bytes_acked 카운터를 비교하세요. 숫자가 오르면 실제로 전송 중이고, 그대로면 진짜 멈춘 것입니다.
- 소켓이 SYN-SENT에 머물고 전송이 아예 시작되지 않습니다.
# 받는 쪽에서 sudo ufw allow from 203.0.113.9 to any port 9031 proto tcp ss -tlnp | grep 9031포트가 막혀 있습니다. 9031/TCP는 보내는 쪽에만 여세요. 203.0.113.9 는 보내는 쪽 자신의 주소, 공인 IP나 두 서버가 같은 네트워크에 있다면 사설 IP로 바꾸고, 클라우드 보안 그룹도 같은 출발지로 좁힌 다음, serve가 정말 대기 중인지 확인하세요. 전송이 시작조차 되지 않는 가장 흔한 원인입니다.
- 백그라운드용 명령을 붙여넣으면 셸이 > 계속 프롬프트에 머뭅니다.
tmux new -s xfer until relayium sync /root/workspace relayium://203.0.113.43:9031; do echo "$(date) retrying"; sleep 10; done따옴표와 > 리다이렉트가 섞인 여러 줄짜리 nohup 명령은 붙여넣을 때 리다이렉트에서 깨지기 쉽습니다. 대신 tmux와 이 한 줄 루프를 쓰세요. 틀릴 리다이렉트가 없고, 다시 붙어서 지켜볼 수도 있습니다.
- 정리하다가 죽일 생각이 없던 것까지 죽였습니다.
pgrep -af relayium ps -p "$(cat ~/relayium-serve.pid)" -o command= tmux kill-session -t xfer kill "$(cat ~/relayium-serve.pid)" && rm ~/relayium-serve.pid명령줄 패턴으로 죽이면 그 문자열이 들어간 모든 프로세스에 시그널이 갑니다. 전송이 둘 이상 도는 기기에서는 노린 그것이 아닐 수 있습니다. 미러도 멈추지 않습니다. sync를 소유한 것은 until 루프라서, 자식을 죽여도 10초 뒤에 다시 뜹니다. 먼저 pgrep -af 로 확인한 뒤, 이 가이드가 실제로 소유한 것을 끝내세요. tmux kill-session -t xfer 로 루프를 끝내고, ~/relayium-serve.pid 에 있는 PID에 kill 을 보내 직접 띄운 리스너를 멈춥니다. 그 PID 파일이 이전 실행부터 남아 있었다면 시그널을 보내기 전에 명령줄부터 출력해 보세요. 시스템이 재사용한 PID는 전혀 다른 프로세스의 것입니다.
- 하위 디렉터리 하나를 빼고 싶은데 exclude 플래그가 없습니다.
relayium sync /root/workspace/src /root/workspace/data relayium://203.0.113.43:9031sync가 받는 것은 -i와 -p, --delete, --watch, --config-dir 뿐이고, 트리 중간에서 경로를 걸러 내는 기능은 없습니다. 대신 원하는 하위 디렉터리를 직접 나열하세요. 각 소스는 받는 쪽 --dir 아래에 자기 이름으로 도착하므로, serve --dir /root/workspace 를 상대로 이 명령은 /root/workspace/src 와 /root/workspace/data 를 재현하고 다시 만들 수 있는 venv는 아예 훑지 않습니다.
- 미러될 줄 알았던 소스가 텅 빈 채로 도착합니다.
relayium sync ./links relayium://203.0.113.43:9031 # warning: no regular files to send (symlinks and special files are skipped)sync는 일반 파일만 전송하며, 저 경고가 바로 심볼릭 링크뿐인 트리의 모습입니다. 링크가 가리키는 실제 디렉터리를 sync에 주고, 받는 쪽에 필요한 심볼릭 링크는 따로 처리하세요.
자주 묻는 질문
전송이 도중에 중단되면 어떻게 되나요?
아무것도 잃지 않습니다. relayium sync를 다시 실행하세요. 받는 쪽에 이미 있는 파일은 건너뛰고, 절반 보낸 파일은 디스크에 이미 있는 바이트 오프셋부터 재개합니다. 이 가이드의 until 루프가 폴더 전체가 미러될 때까지 이를 자동으로 합니다.
이것은 rsync와 무엇이 다른가요?
둘 다 증분 단방향 미러링을 하지만, relayium sync는 인증서 고정 TLS 연결로 동작하고 SSH 계정이 필요 없으며(데몬 다이렉트), 인증서 핑거프린트로 두 기기를 인증하고, 전송하는 각 파일을 SHA-256으로 검증합니다. rsync의 기본 동작과 마찬가지로, 수신 측에서 크기와 mtime이 이미 일치하는 파일은 해시를 다시 계산하지 않고 건너뜁니다. relayium의 다른 모드와 같은 전송 엔진입니다.
sync는 소스에서 지운 파일을 받는 쪽에서도 지우나요?
요청할 때만요. 기본적으로 sync는 추가와 갱신만 합니다. 삭제를 미러링하려면 --delete를 넘기고, 그것이 적용되려면 받는 쪽이 --allow-delete로 serve를 실행해야 합니다. 그렇지 않으면 삭제는 무시되고 보고됩니다.
두 폴더를 계속 동기화 상태로 유지할 수 있나요?
네. --watch를 붙이면 sync가 계속 실행되며 소스 아래의 어떤 변경에도 다시 미러링합니다. 큰 폴더를 한 번 옮기는 데는 필요 없습니다. 재시도 루프와 평범한 sync면 충분합니다.
포트를 열어야 하나요?
데몬 다이렉트라면 네. 리스너 포트(기본 9031)가 보내는 쪽에서 도달 가능해야 합니다. 포트를 열고 싶지 않고 서버 사이에 이미 SSH가 있다면 sync는 SSH로도 동작합니다: relayium sync /path user@host:/path(원격에 relayium이 필요).
내 소유의 두 서버 사이에서 폴더를 미러링하세요. 증분, 재개 가능, 지켜볼 필요 없이.
CLI 받기