본문으로 건너뛰기

Barman 빠른 시작

· 약 8분

어떻게 Barman인가​

1편이 "왜 Barman인가"였다면, 이 글은 "어떻게 Barman인가"예요. 1편에서 Barman의 14년 궤적과 아키텍처를 정리했어요. 이번에는 같은 자리에서 한 발짝 더 들어가 두 대의 Rocky Linux 머신을 준비하고, streaming-only 모드로 PostgreSQL을 설정한 뒤, 5개 명령어로 첫 백업과 PITR 복구까지 끝내 봅니다.

이번에 다룰 시나리오를 한 화면에 펼치면 다음과 같습니다.

단계명령어의미
1barman check pg01PG 연결, streaming, 권한 확인
2barman backup pg01첫 base backup
3barman list-backup pg01카탈로그 조회
4barman recover pg01 latest <dir>최신 백업 복원
5barman recover ... --target-time "..."임의 시점 (PITR) 복원

읽으면서 따라 할 수 있도록 모든 명령어와 설정 파일 내용을 그대로 옮겨 둡니다.

사전 준비​

두 호스트로 lab 환경을 구성합니다.

호스트역할OS
demo-pg01PostgreSQL 17 primaryRocky Linux 9
demo-barman01Barman 3.18 서버Rocky Linux 9

두 호스트가 hostname으로 서로 통신한다고 가정합니다(DNS 또는 /etc/hosts). 단일 머신에서 시험하려면 demo-pg01과 demo-barman01을 모두 localhost로 두고 진행해도 됩니다.

PostgreSQL에서 Barman으로 base backup과 WAL이 전달되는 흐름

설치​

PGDG 저장소에서 설치합니다.

demo-pg01과 demo-barman01 양쪽에 PGDG 저장소를 등록합니다.

sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql

demo-pg01에서 PostgreSQL 17 설치/초기화:

sudo dnf install -y postgresql17-server postgresql17-contrib
sudo /usr/pgsql-17/bin/postgresql-17-setup initdb
sudo systemctl enable --now postgresql-17

demo-barman01에 Barman 3.18 설치:

sudo dnf install -y barman barman-cli

설치 시 barman 시스템 사용자가 자동 생성되며, 백업 카탈로그 기본 경로는 /var/lib/barman입니다. /etc/cron.d/barman도 함께 깔리므로 별도 systemd timer 설정 없이 cron이 매 분 한 번 barman cron을 돌립니다.

설정 1: PostgreSQL 측 (demo-pg01)​

streaming 백업은 PostgreSQL의 replication 프로토콜 위에서 동작하므로, replication user와 replication slot이 필요합니다.

/var/lib/pgsql/17/data/postgresql.conf 핵심 항목:

listen_addresses = '*'
wal_level = replica
max_wal_senders = 10
max_replication_slots = 10

/var/lib/pgsql/17/data/pg_hba.conf에 Barman 측 접속을 열어 둡니다.

# TYPE DATABASE USER ADDRESS METHOD
host replication barman demo-barman01 scram-sha-256
host postgres barman demo-barman01 scram-sha-256

PostgreSQL을 재시작한 후 사용자와 슬롯을 생성합니다.

sudo systemctl restart postgresql-17
sudo -u postgres psql <<'SQL'
CREATE USER barman WITH REPLICATION ENCRYPTED PASSWORD 'changeme';
GRANT EXECUTE ON FUNCTION pg_backup_start(text, boolean) TO barman;
GRANT EXECUTE ON FUNCTION pg_backup_stop(boolean) TO barman;
GRANT EXECUTE ON FUNCTION pg_switch_wal() TO barman;
GRANT EXECUTE ON FUNCTION pg_create_restore_point(text) TO barman;
GRANT pg_read_all_settings, pg_read_all_stats TO barman;
SELECT pg_create_physical_replication_slot('barman');
SQL

실제 운영에서는 changeme을 비밀 관리자(Vault, AWS Secrets Manager 등)로 옮기고 .pgpass 또는 환경변수로 분리해야 합니다.

설정 2: Barman 측 (demo-barman01)​

/etc/barman.conf는 default 값 그대로 두는 편이 무난합니다. 서버별 설정만 추가합니다(default 항목 자체는 별도 글에서 다룹니다).

/etc/barman.d/pg01.conf:

[pg01]
description = "Production PG primary"
conninfo = host=demo-pg01 user=barman dbname=postgres
streaming_conninfo = host=demo-pg01 user=barman
backup_method = postgres
streaming_archiver = on
slot_name = barman
create_slot = manual
retention_policy = RECOVERY WINDOW OF 4 WEEKS

[pg01]은 Barman 서버 라벨이자 서버 식별자입니다. CLI(barman backup pg01), 카탈로그 디렉토리(/var/lib/barman/pg01/...), conf 파일명(pg01.conf)에 같은 값을 씁니다. 실제 호스트네임 demo-pg01과는 별개이며, Barman이 부르는 이름과 네트워크가 부르는 이름을 분리한 것입니다.

conninfo = host=demo-pg01 ...에는 Barman이 PostgreSQL에 접속할 때 사용하는 실제 호스트네임을 지정하므로 라벨과 달라도 자연스럽습니다. backup_method = postgres는 pg_basebackup을 통한 streaming 백업을 뜻하고, streaming_archiver = on은 WAL을 pg_receivewal 방식으로 받도록 설정합니다. slot_name = barman은 앞에서 만든 replication slot을 사용합니다. create_slot = manual로 지정한 이유는 슬롯을 SQL로 이미 만들었으므로 Barman이 자동 생성하지 않게 하기 위해서입니다.

비밀번호는 barman 사용자의 ~/.pgpass로 분리합니다.

sudo -u barman tee ~barman/.pgpass > /dev/null <<'EOF'
demo-pg01:5432:*:barman:changeme
EOF
sudo chmod 600 ~barman/.pgpass
sudo chown barman:barman ~barman/.pgpass

5개 명령어 시나리오​

이제부터는 모든 명령어를 barman 사용자로 실행합니다. sudo -i -u barman으로 barman 셸에 들어가거나, 명령어마다 sudo -u barman을 앞에 붙입니다.

1. barman check pg01​

셋업을 검증합니다.

sudo -u barman barman check pg01

기대 출력 (요약):

Server pg01:
PostgreSQL: OK
wal_level: OK
replication slot: OK
directories: OK
retention policy settings: OK
pg_basebackup: OK
pg_basebackup compatible: OK
systemid coherence: OK
pg_receivexlog: OK
receive-wal running: OK
archiver errors: OK

한 줄이라도 FAILED가 나오면 그 항목에 셋업 단계의 문제가 있습니다. 가장 자주 보이는 receive-wal running: FAILED는 cron이 아직 한 번도 돌지 않았거나 slot 이름이 어긋났다는 뜻입니다. 한 번 강제로 돌려 두는 편이 빠릅니다.

sudo -u barman barman cron

2. barman backup pg01​

첫 base backup을 실행합니다.

sudo -u barman barman backup pg01

기대 출력 (요약):

Starting backup using postgres method for server pg01 in /var/lib/barman/pg01/base/20260504T143012
Backup start at LSN: 0/3000028
Starting backup copy via pg_basebackup for 20260504T143012
Copy done (time: 12 seconds)
Backup size: 41.5 MiB
Backup end at LSN: 0/4000060
Marking backup as DONE
Backup completed (start time: 2026-05-04 14:30:12, elapsed time: 14 seconds)

/var/lib/barman/pg01/base/<timestamp>/ 아래에 base backup이 쌓입니다. WAL은 /var/lib/barman/pg01/streaming/으로 수신된 뒤 /var/lib/barman/pg01/wals/ 아래에 아카이브됩니다.

3. barman list-backup pg01​

카탈로그를 조회합니다.

sudo -u barman barman list-backup pg01

기대 출력:

pg01 20260504T143012 - F - 2026-05-04 14:30:26 - Size: 41.5 MiB - WAL Size: 16 MiB

F는 full backup입니다. backup-id는 timestamp 기반(20260504T143012)이며, latest 키워드를 별칭으로 쓸 수 있습니다.

상세 보기:

sudo -u barman barman show-backup pg01 latest

Begin time / End time / Begin LSN / End LSN 등 PITR 타깃을 결정할 때 필요한 값이 모두 여기에 있습니다.

4. barman recover​

최신 백업으로 복원합니다.

복원 대상은 빈 디렉토리여야 합니다. PostgreSQL이 새로 기동할 자리를 미리 비워 둡니다.

sudo mkdir -p /var/lib/barman/restore
sudo chown barman:barman /var/lib/barman/restore

sudo -u barman barman recover pg01 latest /var/lib/barman/restore

복원이 끝나면 그 디렉토리 안에 base backup이 풀리고, recovery.signal과 postgresql.auto.conf의 restore_command 항목이 자동 생성됩니다. PostgreSQL을 그 데이터 디렉토리로 띄우면 곧장 기동합니다.

다른 호스트로 직접 복원하려면 --remote-ssh-command "ssh postgres@<host>"를 추가합니다. 이때 barman 사용자에서 그 호스트의 postgres로 SSH 키 기반 접속이 미리 설정되어 있어야 합니다.

5. barman recover --target-time​

특정 시점으로 되감는 PITR을 실행합니다.

sudo -u barman barman recover pg01 latest /var/lib/barman/restore \
--target-time "2026-05-04 15:00:00"

복원 시점은 base backup 종료(End time) 이후, 가장 마지막에 받은 WAL 이전 사이여야 합니다. 정확한 경계가 헷갈리면 barman show-backup pg01 latest의 Begin time / End time을 기준으로 잡습니다.

다른 PITR 타깃 옵션도 같은 자리에 지정합니다.

옵션의미
--target-time시점 기준
--target-xid트랜잭션 ID 기준
--target-namepg_create_restore_point()로 만든 명시적 라벨
--target-immediatebase backup 직후 일관성 시점

일상 운영​

cron 한 줄과 보존 정책을 설정합니다.

설치 시 동봉된 /etc/cron.d/barman이 매 분 barman cron을 실행합니다(WAL 수신과 아카이브 정리 담당). 정기 백업은 별도 cron 한 줄로 잡는 방식이 표준입니다.

# /etc/cron.d/barman-backup — 매일 02:00에 모든 등록 서버 백업
0 2 * * * barman /usr/bin/barman backup all

보존 정책은 서버별 conf 한 줄로 끝납니다.

retention_policy = RECOVERY WINDOW OF 4 WEEKS
# 또는 개수 기반:
# retention_policy = REDUNDANCY 5

RECOVERY WINDOW는 "이 시점부터 N 단위(WEEKS/DAYS) 전까지 PITR이 가능하도록 보장"한다는 의미입니다. 지난 4주 동안 임의 시점으로 되감을 수 있도록 base backup과 WAL을 함께 보존합니다. 정책에서 벗어난 backup은 barman cron이 자동으로 정리합니다.

자주 만나는 에러 빠른 가이드​

증상원인한 줄 해결
receive-wal running: FAILEDstreaming WAL receiver 미실행barman cron 수동 실행, slot 이름/권한 재점검
replication slot: FAILEDPG 측 슬롯이 없음 / 이름 불일치pg_create_physical_replication_slot('barman') 다시 실행
pg_basebackup: FAILEDreplication 권한/pg_hba 미흡barman 사용자의 REPLICATION 속성 확인, pg_hba에 host replication 행
Connection refusedlisten_addresses, 방화벽postgresql.conf의 listen_addresses = '*', firewalld에서 5432 허용

barman check는 한 번에 끝내려 들기보다 "FAIL 한 줄씩 잡아 나가는 도구"로 보면 마음이 편합니다. 위 4개를 해결하면 첫 셋업의 90%가 끝납니다.

정리​

항목내용
백업 모델streaming-only (SSH 없이 pg_basebackup + replication slot)
카탈로그 위치/var/lib/barman/pg01/{base,streaming,wals}
일상 명령어barman cron (자동) + barman backup all (cron 1회/일)
검증 명령어barman check pg01 — 셋업 직후/이상 발생 시 첫 진단
PITRbarman recover ... --target-time "..." 한 줄
보존 정책RECOVERY WINDOW OF 4 WEEKS 권장

백업의 가치는 백업이 아니라 복구에서 결정돼요. 셋업 직후 PITR을 한 번은 반드시 돌려봐요.

참고 자료​

Barman 14년의 발자취

· 약 9분

pgBackRest가 멈춘 자리, Barman을 다시 본다​

지난 글에서 pgBackRest의 종료를 정리하면서, 마이그레이션 평가의 가장 유력한 후보로 Barman(바만)을 짚었어요. 표준이 멈췄다는 4월 말의 며칠이 시끄러웠던 동안, Barman은 평소처럼 일하고 있었어요. 3월에 v3.18.0을 조용히 릴리스했고, 깃허브 이슈는 평상시처럼 열리고 닫혔는데 14년째 그러고 있어요.

오픈소스 도구의 가치는 시끄럽지 않게 잘 굴러갔다는 사실에서 가장 잘 드러납니다. 그 14년이 어떻게 흘러왔는지, Barman이라는 도구가 어떤 모양으로 생겼는지를 이 글에서 1편으로 정리하고, 실전 사용법은 2편에서 다룹니다.

토스카나 프라토에서 시작하다 (2001 → 2008 → 2012)​

Barman의 출발점을 이해하려면 먼저 모회사 2ndQuadrant Italia의 시작점으로 가야 합니다.

시점사건
2001영국에서 2ndQuadrant Ltd. 창립 (Simon Riggs(시몬 리그스))
2008-05-212ndQuadrant Italia 설립 (이탈리아 프라토), Gabriele Bartolini(가브리엘레 바르톨리니), Marco Nenciarini(마르코 넨치아리니) 등
2012Barman 첫 공개 (저작권 © 2012-)

토스카나 한가운데의 프라토에서 시작된 이 작은 컨설팅사가 PostgreSQL 생태계에 남긴 자취가 적지 않습니다. Logical Replication 초창기 구현에서 시작해 Barman, repmgr(고가용성 매니저), pglogical까지, 그 진영에서 나온 도구가 운영 도구 라인업의 한 축을 채웠습니다. PostgreSQL 핵심 개발자인 Simon Riggs가 영국에서 비전을 세우고, 이탈리아 팀이 운영 도구의 실무를 채우는 분업이 16년간 이어졌습니다.

Barman은 그 라인업의 중심축이었습니다. 이름은 Backup And Recovery MANager의 머리글자에서 왔습니다.

1.0에서 2.0으로 (2012 → 2016)​

이 시기에 Barman은 SSH 없이도 동작하는 길을 열었습니다.

시점릴리스의미
2012Barman 1.xrsync over SSH 기반 원격 백업, archive_command로 WAL 수집
2016-09-27Barman 2.0streaming-only 백업 도입, SSH 없이 pg_basebackup + replication slot으로 동작
2.x 후반barman-cloud-*S3, Azure, GCS 직결 명령어 도입

Barman 1.x 시대의 모델은 단순했습니다. Barman 서버 한 대가 SSH로 PostgreSQL 서버에 붙어 rsync로 데이터 디렉토리를 가져오고, PostgreSQL의 archive_command가 WAL 파일 하나하나를 Barman 서버로 밀어넣는 구조입니다. 1.x는 PostgreSQL의 백업 절차를 원격에서 자동화했다는 점에서 의미가 있었지만, SSH 의존이라는 단단한 가정이 깔려 있었습니다.

2.0이 그 가정을 풀었습니다. 2016년 9월 27일 발표된 Barman 2.0의 핵심은 다음 한 줄이었습니다.

"Streaming-only backups are now possible through transparent integration with pg_basebackup and full support of replication slots for WAL streaming."

(이제 streaming-only 백업이 가능하다 — pg_basebackup과의 투명한 통합, 그리고 WAL streaming을 위한 replication slot의 전면 지원으로.)

이 변화는 보기보다 컸습니다. SSH 키 분배, 권한 관리, rsync 환경 구성을 매번 짜야 하던 운영자에게, "PostgreSQL의 streaming replication 프로토콜 위에 백업을 얹는다"는 발상은 컨테이너 환경의 PostgreSQL 운영을 비로소 자연스럽게 만들었습니다. 같은 릴리스에서 들어온 synchronous WAL streaming은 RPO=0 백업이라는 개념을 도구 수준에서 처음으로 풀어냈습니다.

이 시점 이후 Barman은 rsync 진영과 streaming 진영을 동시에 지원하는 도구가 됐고, 선택은 운영자에게 맡겨졌습니다. 2.x 후반에 들어온 barman-cloud-* 명령어 계열이 마지막 퍼즐이었습니다. 백업 대상이 로컬 디스크일 수도, S3, Azure, GCS의 오브젝트 스토리지일 수도 있게 됐습니다.

EDB로 향한 2020년 9월의 분기점​

PostgreSQL 생태계에서 2020년 9월 30일은 적지 않은 변곡점입니다.

"EDB has acquired 2ndQuadrant, a global PostgreSQL solutions and tools company based out of the UK." — Crunchbase, 2020-09-30

(EDB가 영국 본사의 글로벌 PostgreSQL 솔루션·도구 회사인 2ndQuadrant를 인수했다.)

EnterpriseDB(EDB, 미국 보스턴)가 2ndQuadrant(영국/이탈리아)를 인수했습니다. 합병 이후 PostgreSQL 코어팀은 별도 성명을 냈습니다. 인수로 코어팀 구성원의 과반이 EDB 소속이 됐고, 한 회사 소속이 코어팀의 50%를 넘지 않는다는 불문율과 충돌했기 때문입니다. 인수가 완료된 2020-10-08의 공식 발표에는 이런 표현이 등장합니다.

"...the largest dedicated provider of PostgreSQL products and solutions worldwide."

(...전 세계 최대 규모의 PostgreSQL 전문 제품·솔루션 제공사)

운영 도구 입장에서 이 사건이 의미하는 바는 분명했습니다. Barman의 메인테이너가 그 시점부터 EDB로 바뀌었습니다. GitHub 저장소도 자연스럽게 EnterpriseDB/barman으로 이관됐습니다. 같은 처지에 놓인 도구로는 BDR(Bi-Directional Replication), repmgr, pglogical 등이 있습니다.

여기서 한 가지 짚어둘 만한 점이 있습니다. "메인테이너가 바뀐다"는 사건의 결과는 동일하지 않습니다. Barman은 EDB 산하에서 활발한 유지를 이어갔습니다. v2.x → v3.x 메이저 전환, 클라우드 백업 강화, 2026년 3월 v3.18.0까지 14년째 정상 궤도에 있습니다.

같은 4월 pgBackRest는 같은 "메인테이너 변경" 변수 앞에서 정반대 결말을 맞았습니다. Crunchy Data의 Snowflake 인수 → 후원 단절 → 종료로 이어졌습니다. 같은 단어가 다른 결말을 낳았습니다. 무엇이 둘을 갈랐는가는 글 끝에서 한 번 다시 짚습니다.

Simon Riggs 이후의 2024년 3월​

이 글에서 한 줄은 따로 두어야 합니다. 2ndQuadrant의 창립자이자 PITR, hot standby, synchronous replication 등을 이끈 PostgreSQL 핵심 개발자였던 Simon Riggs(시몬 리그스)는 2024년 3월 26일 항공 사고로 사망했다.

EDB 인수 이후에도 Riggs는 은퇴할 때까지 EDB에서 Postgres Fellow로 일했습니다. 그가 떠난 자리에 EDB가 — 그리고 Barman이 — 어떤 모양으로 남을지에 대해 적지 않은 사람들이 한 번씩 멈추고 들여다본 시기가 있었습니다. 끝내 도구는 흔들리지 않았습니다.

Bartolini의 추도글이 이 시기를 가장 잘 정리해 둡니다. 16년 전 토스카나의 작은 사무실에 둔 신뢰가, 결국 그가 떠난 뒤에도 도구를 살아 있게 만든 구조였다는 회고입니다.

Barman의 주요 연혁

중앙 카탈로그 모델의 아키텍처​

이제 도구 자체로 들어갑니다. Barman의 구조는 실은 단순합니다.

세 레이어로 쪼개 보면 됩니다.

1. PostgreSQL 서버 (백업 대상)​

하나 또는 여러 대입니다. Barman 입장에서는 등록된 서버마다 별도 카탈로그 항목을 가집니다. 각 서버는 두 채널, base backup 채널과 WAL 채널로 Barman 서버에 연결됩니다.

2. Barman 서버 (중앙 카탈로그)​

Barman의 본체로, 다음을 보관하고 관리합니다.

  • 각 서버의 base backup (rsync 또는 pg_basebackup으로 생성)
  • 각 서버의 WAL archive (streaming 또는 archive_command로 수집)
  • 백업 메타데이터, 보존 정책, 체크 결과
  • 복구 시 사용할 recovery.signal과 설정 파일 자동 생성

설정은 /etc/barman.conf(전역)와 /etc/barman.d/<server>.conf(서버별)로 두 단계로 나뉩니다. 운영 명령어는 단일 진입점 barman 한 줄에 모입니다. barman backup <server>, barman list-backups <server>, barman recover <server> <backup-id> <target>을 사용합니다.

3. Object Storage (선택)​

v2.x 후반부터 들어온 barman-cloud-backup, barman-cloud-wal-archive, barman-cloud-restore 명령어를 통해 S3, Azure Blob, GCS에 직접 업로드, 복원할 수 있습니다. PostgreSQL 서버가 Barman 서버를 거치지 않고 곧바로 클라우드로 백업할 수도, Barman 서버를 거쳐 로컬 + 클라우드 2단 보관을 할 수도 있습니다.

rsync와 streaming, 두 갈래 백업 방식​

Barman의 첫 인상이 "옵션이 많다"라면, 그 인상의 절반은 이 분기 때문입니다.

항목rsync 모델streaming 모델
도입 시기1.x (2012-)2.0 (2016-)
전송 채널SSHPG streaming replication
의존SSH 키, rsync, sudoreplication slot, replication user
증분하드링크 dedup(PG17+ 블록 레벨로 점진 이행)
WAL 수집archive_commandreceive_wal (streaming)
적합 환경베어메탈/VM, SSH 통제 가능컨테이너/K8s, SSH 미허용

어느 쪽이 더 좋다고 단언하긴 어렵습니다. 베어메탈/VM에서 SSH 통제가 명확한 환경이라면 rsync 진영의 하드링크 dedup이 디스크를 덜 먹습니다. 반대로 K8s에서 PostgreSQL을 운영 중이라면 SSH 키 관리 자체가 부담이라 streaming 진영이 자연스럽습니다. CNPG가 Barman Cloud Plugin을 채택한 이유도 거기에 있습니다. PostgreSQL의 streaming 프로토콜 위에 백업을 얹는다는 발상이 K8s의 역할 분리에 잘 맞물립니다.

PostgreSQL에 위임한다는 철학​

pgBackRest 종료 글에서도 인용한 Bartolini의 한 줄이 Barman을 이해하는 가장 짧은 길입니다.

"Barman delegates data copy mechanisms to PostgreSQL primitives and rsync, focusing instead on orchestration, retention, and recovery management."

(Barman은 데이터 복사 메커니즘 자체는 PostgreSQL의 기본 기능과 rsync에 위임하고, 자기는 오케스트레이션·보존·복구 관리에 집중한다.)

이 한 문장을 풀면 이렇습니다. Barman은 "파일을 어떻게 빠르게 복사할 것인가"를 직접 풀지 않습니다. 그건 PostgreSQL의 pg_basebackup과 archive_command, 그리고 rsync가 이미 잘 풀어둔 문제입니다. Barman은 "여러 PostgreSQL 서버의 백업을 어떻게 한 곳에서 카탈로그화하고, 보존 정책을 적용하고, 복구 시점을 결정하고, 명령어 한 줄로 PITR을 실행시킬 것인가"에 집중합니다.

pgBackRest는 정확히 반대 결정을 한 도구였습니다. 복사, 압축, 암호화, 전송을 자체 C 구현체로 끝까지 통제했습니다. 그래서 빨랐고, 그래서 깔끔했고, 그래서 한 사람의 13년이 필요했습니다. Barman은 PostgreSQL 자체의 진화에 올라타는 길을 골랐고, 그 덕에 EDB라는 모회사 변경을 견뎌낸 도구가 됐습니다.

둘 중 어느 쪽이 옳다고 단정할 일은 아닙니다. 다만 "외부 의존을 줄이는 도구"와 "외부 의존에 올라타는 도구"가 메인테이너 변경이라는 변수 앞에서 어떻게 다르게 반응하는지를, 같은 4월의 두 사건이 한 화면에 보여주었습니다.

정리​

항목내용
첫 릴리스2012, 이탈리아 프라토 (2ndQuadrant Italia)
메인테이너 전환2020-10-08 EDB의 2ndQuadrant 인수 완료
백업 방식rsync (1.x~) / streaming (2.0~) / cloud (2.x 후반~), 3가지 모드
최신 릴리스v3.18.0 (2026-03-12)
라이선스GPL-3.0
정체성PG에 복사를 위임하고 오케스트레이션에 집중하는 백업 매니저

Barman이 14년을 조용히 굴러왔다는 사실은, 백업 도구에 기대하는 안정성을 그대로 보여줍니다.

2편에서는 실제로 한 대의 Barman 서버를 세우고, 한 PostgreSQL 인스턴스를 등록해 첫 백업을 떠 보고, 임의의 시점으로 복구하는 5개 명령어 시나리오를 다뤄요. 이 글이 왜 Barman인가에 대한 답이라면, 다음 글은 어떻게 Barman인가에 대한 답이에요.

참고 자료​

Dia와 Arc 브라우저

· 약 5분

직전에 Arc 이후 세로탭을 찾아 글을 쓰면서 Dia(디아)는 한 줄로만 다루고 넘어갔어요. "의도적으로 더 보수적이고 채팅 인터페이스 중심"이라고요. 그다음 며칠 마음이 동해서 직접 깔아봤고, 이 글에 그 첫인상을 적었어요.

스포일러는 단순합니다. 디자인은 마음에 들었지만, 메인으로 옮기진 못했습니다.

첫 화면​

의외로 조용합니다. Dia를 처음 켰을 때 인상은 "고요함"이었습니다. URL바가 가운데 떠 있고, 좌측에 사이드바가 붙어 있고, 그 외에는 거의 비어 있습니다. Arc를 처음 켰을 때 "이게 뭐지?" 하던 충격은 없었지만, 그다음 단계의 절제 같은 게 있습니다.

이름은 라틴어/스페인어 día(낮)에서 왔습니다. 그래서 "디아"라고 읽습니다. 다이아몬드 쪽이 아닙니다.

며칠 가볍게 써볼 마음으로 켰는데, 일단 디자인이 거슬리지 않는다는 점이 가장 좋았습니다. 익숙하지도 낯설지도 않은, 그저 조용한 첫 화면입니다.

마음에 든 것 네 가지​

가장 먼저 눈에 들어온 것은 세로탭과 사이드바입니다. 좌측 정렬 탭에 사이드바 토글까지, Arc를 쓰던 손이 거의 그대로 적응합니다. 단축키로 사이드바를 접으면 본문이 넓어지는 그 감각이 살아 있습니다. URL바가 검색, 질문, AI의 단일 입구라는 점도 좋았습니다. Cmd+T 한 번이면 끝이라, 검색하고 싶으면 검색하고 물어보고 싶으면 같은 자리에서 묻습니다. Arc Command Bar의 진화형이라고 봐도 됩니다.

타이포와 여백, 애니메이션의 정돈도 눈에 띕니다. 폰트, 트랜지션, 인터랙션 디테일이 차분해서 광고와 PR이 없는 Chrome 같습니다. 그중 가장 의외였던 것은 Arc보다 더 단순하다는 점입니다. Arc는 처음 며칠 학습 비용이 있었는데 Dia는 그게 없습니다. 장식이 적고 일상에서 쓰기 편합니다.

며칠 쓰면서 거슬린 것들​

유료의 벽​

가장 크게 걸린 지점은 가격입니다.

Dia의 AI를 본격적으로 쓰려면 Dia Pro $20/월이 필요합니다. 무료 티어가 있지만 사용량 한도가 있습니다. 가벼운 탭 요약, 짧은 질문 몇 번에는 충분한데, AI를 일상적으로 끼고 쓸 만큼은 아닙니다.

진짜 문제는 가격이 ChatGPT Plus, Claude Pro와 정확히 겹친다는 점입니다. 이미 그쪽 중 하나를 쓰고 있다면 추가 $20는 부담입니다. 저처럼 "브라우저는 무료로 쓰고 싶다"는 사람은 AI 기능을 본격적으로 쓰는 순간 결제창부터 만납니다.

브라우저에 월 구독료를 낸다는 모델 자체에도 마음이 잘 안 갑니다. 브라우저는 도구지, 어시스턴트가 아닙니다.

AI 응답 품질이 한 단계 묽다​

무료 한도 안에서 몇 번 써본 인상으로는 Dia에 묶여 있는 모델이 ChatGPT나 Claude를 직접 쓸 때보다 한 단계 묽어요. 탭 컨텍스트를 자동으로 넣어주는 강점은 있는데, 모델 자체의 추론력이 그만큼 따라오지 못합니다.

결국 진지한 질문은 ChatGPT/Claude 탭으로 가게 되고, Dia는 가벼운 요약 정도에만 쓰게 됩니다. 그러면 또 "굳이 Dia를 켤 이유가 뭐지" 싶어집니다.

정체성이 흔들린다​

며칠 쓰는 내내 가장 모호했던 부분입니다. 시간순으로 짚으면 이렇습니다. 2025년 6월 베타 출시 때는 Arc의 Spaces, Command Bar, 세로탭을 다 빼고 "AI 우선"을 명분으로 시작했습니다. 그해 9월 Atlassian이 The Browser Company를 $610M에 인수한다고 발표했고, 10월에 인수를 마무리했습니다. 11월에는 Arc 사용자 이탈이 길어지자 사이드바, 세로탭, 핀 탭 같은 Arc 기능을 다시 넣기 시작했습니다. 사실상 역수입입니다. 이어 2026년 3월부터 Slack, Notion, Gmail, Google Calendar, Jira, Linear 같은 SaaS 도구 통합이 본격화됐습니다.

그래서 지금 Dia는 Arc 후속 + AI 우선 + 엔터프라이즈 SaaS 통합 도구 셋이 어색하게 동거 중입니다. 며칠 써본 제 입장에서 가장 모호한 것은 "이 브라우저는 누굴 위한 거지?"였어요. 개인 사용자인 저를 위한 것인지, Atlassian 워크플로우를 쓰는 팀을 위한 것인지, AI를 적극 쓰는 사람을 위한 것인지 하나로 모이지 않습니다.

AI 우선 출시부터 Arc 기능 복귀와 SaaS 통합까지 이어진 Dia의 변화

이건 사용성 단점이라기보다 방향성의 단점에 가깝습니다. 1년 후의 Dia가 지금의 Dia와 같은 도구일 거라는 보장이 없습니다.

macOS 14+ Apple Silicon 전용​

Intel Mac, Windows 사용자는 아직 진입 자체가 안 됩니다. Windows 베타는 가입 페이지만 받는 중입니다. 회사, 집, 외부 환경이 섞여 있는 사람에게 메인으로 가져가기에는 조건이 너무 좁습니다.

확장/자동화의 부재​

Chromium 기반인데 확장 호환이 제한적이라 익숙하던 확장을 다 못 가져옵니다. 그리고 Perplexity Comet이나 ChatGPT Atlas가 보여주는 "에이전트가 폼 채우고 메일 보내주는" 자동화도 Dia에는 없습니다.

지금의 Dia는 "채팅하는 브라우저"이고, 경쟁사들은 "일하는 브라우저" 쪽으로 가고 있습니다. 카테고리는 같은데 답하는 질문이 다릅니다.

AI 브라우저 시장에서의 자리​

브라우저만든 곳방향플랫폼
CometPerplexity에이전트가 일을 처리Win/Mac/iOS/Android
ChatGPT AtlasOpenAIAgent Mode 멀티탭 자동화macOS Apple Silicon
DiaAtlassian / Browser Co탭과 대화 + SaaS 통합macOS Apple Silicon

같은 카테고리지만 풀려는 문제가 다릅니다. Comet과 Atlas가 "제 대신 일을 처리해 주세요"라면, Dia는 "이 탭에 대해 같이 얘기하자"입니다. Atlassian 인수 이후로는 거기에 "팀 SaaS 워크플로우"가 얹히는 중입니다. 어느 쪽이 정답인지는 아직 시장이 결정하지 않았습니다.

결론​

관찰 모드로 남았습니다. 며칠 써본 결론은 단순합니다. 디자인은 좋습니다. URL바 통합, 조용한 절제, 익숙한 세로탭까지, 며칠 쓰면서 거슬리지 않는다는 것 자체가 이미 점수입니다. 다만 메인으로 옮기기에는 벽이 두 가지 있습니다. 하나는 비용입니다. 무료로는 한계가 뚜렷하고, 유료는 ChatGPT/Claude 구독과 가격이 겹칩니다. 다른 하나는 방향입니다. 정체성이 1년 안에 어디로 갈지 불확실합니다. Arc UX의 향수, AI 어시스턴트, Atlassian 엔터프라이즈 도구 중 어느 쪽으로 정리될지 아직 모릅니다.

그래서 지금 결론을 내리지 않는 편이 정직한 후기입니다. 계속 관찰만 하기로 했습니다.

직전 글에서 정리한 결론(현실적 1순위 Edge, 호기심 1순위 Zen)은 여전히 유효합니다. Dia는 그 옆에 한 자리 비워두고 두기로 했습니다. 정체성이 정리되거나 무료 티어가 너그러워지면, 그때 다시 며칠 깊게 써볼 것입니다.

조용한 디자인은 마음에 들었지만, 그것만으로 메인 브라우저를 바꾸기에는 아직 일러요.

참고​

Copy Fail 취약점

· 약 8분

732바이트짜리 파이썬 스크립트 하나로 2017년 이후 빌드된 거의 모든 주요 리눅스 배포판에서 root를 따낼 수 있습니다. 이번에 공개된 커널 논리 버그 Copy Fail 이야기예요.

표준 라이브러리만 쓰고, race condition도 없고, 배포판별 오프셋도, 컴파일된 셸코드 페이로드도 필요 없습니다. Ubuntu, RHEL, Amazon Linux, SUSE에서 같은 스크립트가 그대로 돕니다. 어제(2026-04-29) 공개됐고, 발견자는 한국 보안업체 Theori의 Taeyang Lee, 도구는 자체 AI 코드 감사기 Xint Code입니다.

Dirty COW/Dirty Pipe와 무엇이 다른가​

이 자리는 익숙합니다. 2016년의 Dirty COW, 2022년의 Dirty Pipe도 모두 비특권 사용자가 페이지 캐시를 변조해 setuid 바이너리를 갈아치우는 LPE였습니다. 그런데 그 둘과 Copy Fail의 결정적인 차이는 race가 없다는 점입니다.

Dirty COW (2016)Dirty Pipe (2022)Copy Fail (2026)
클래스race conditionflag 초기화 누락직선형 논리 버그
성공률확률적 (수십~수천 회 시도)거의 결정론적첫 시도 결정론적
배포판별 오프셋필요할 때 있음거의 불필요불필요
컴파일된 페이로드보통 필요종종 필요불필요 (Python 표준 라이브러리만)
영향 기간~2007–2016~2020–20222017–패치 적용일

"직선형 논리 버그"라는 표현이 이 결함을 잘 짚습니다. 흐름이 분기 없이 일자로 흐르고, 각 단계가 의도대로 떨어지면 페이지 캐시의 정해진 위치에 정해진 바이트가 쓰입니다. 운영자 입장에서는 가장 무서운 형태의 LPE인데, 패치하기 전까지는 누구나 한 번에 성공하기 때문입니다.

세 가지 변경의 우연한 교차점​

Copy Fail은 단일 버그가 아닙니다. 시기가 다른 세 가지 커널 변경이 한 자리에서 만나면서 만들어진 함정입니다.

시기변경원래 의도
2011authencesn 템플릿 추가IPsec ESP의 64비트 Extended Sequence Number 지원. 처음부터 호출자의 destination scatterlist를 임시 저장 공간으로 재사용
~2014AF_ALG 소켓에 AEAD 지원(algif_aead.c) + splice() 경로사용자 공간이 커널 crypto API를 소켓처럼 쓰도록 하기 위함
2017algif_aead에 in-place AEAD 최적화 (commit 72548b093ee3)메모리 절약 목적. 복호화 시 req->src = req->dst로 묶고, tag 페이지는 sg_chain()으로 참조만 연결

각각만 보면 그럴듯한 변경입니다. 문제는 셋이 만나는 지점입니다.

  1. splice()로 읽기 전용 파일의 페이지 캐시 페이지를 AF_ALG의 TX scatterlist에 그대로 참조로 넣습니다.
  2. 2017년 인-플레이스 최적화가 그 TX scatterlist를 그대로 쓰기 가능한 dst scatterlist로 재사용합니다.
  3. authencesn은 dst[assoclen + cryptlen] 위치에 ESN 임시값(4바이트)을 씁니다. 그 자리가 1번에서 체인된 페이지 캐시 페이지입니다.

그 결과 비특권 사용자가 임의 파일의 페이지 캐시에 결정론적인 4바이트 쓰기를 하게 됩니다. 거의 10년 동안 조용히 익스플로잇 가능한 상태로 있었습니다.

익스플로잇 흐름​

PoC는 copy.fail에 공개돼 있고 본문에는 옮기지 않습니다. 동작 원리는 다음과 같습니다.

순서를 풀면 이렇습니다.

  1. AF_ALG 소켓을 열어 authencesn(...) 알고리즘에 bind합니다.
  2. 타깃 setuid 바이너리(/usr/bin/su 등)를 열어 그 페이지 캐시 페이지를 splice()로 소켓의 TX scatterlist에 주입합니다.
  3. 쉘코드를 4바이트 단위로 잘라, 매번 위치 계산을 맞춰 sendmsg() + splice() 페어를 반복합니다.
  4. recv()를 호출하면 복호화 동작이 이뤄지며 authencesn이 ESN 임시값을 dst(즉 공격자가 확보한 페이지 캐시 페이지)에 4바이트씩 기록합니다.
  5. 마지막으로 execve("/usr/bin/su"). 손상된 페이지 캐시에서 로드된 setuid 바이너리가 쉘코드를 root 권한으로 실행합니다.

PoC가 이렇게 짧은 이유는 모든 단계가 표준 syscall과 표준 라이브러리만 쓰기 때문입니다. 컴파일러도, 외부 페이로드도 필요 없습니다.

왜 이게 무서운가​

세 가지가 겹칩니다.

먼저 디스크를 건드리지 않습니다. 손상은 페이지 캐시에서만 일어나기 때문에 디스크 파일의 해시는 그대로입니다. AIDE/Tripwire 같은 무결성 모니터링이 잡지 못합니다. 재부팅하거나 페이지가 evict되면 캐시는 정상 복원되지만, 그 사이에 익스플로잇은 끝납니다.

다음으로 페이지 캐시는 호스트 전체가 공유합니다. 컨테이너는 같은 호스트 커널의 같은 페이지 캐시를 봅니다. 즉 비특권 컨테이너 안에서 일으킨 4바이트 쓰기가 호스트의 setuid 바이너리, 또는 옆 컨테이너의 read-only mount 파일을 손상시킬 수 있습니다. K8s 노드, 멀티테넌트 SaaS, 공유 CI 러너가 1순위 위험군입니다. namespace 격리만 쓰는 컨테이너 모델이 이 한 줄짜리 버그 앞에서 그대로 뚫립니다.

마지막으로 CVSS는 7.8(High)로 매겨졌습니다. Critical이 아닌 이유는 로컬 액세스가 필요해서일 뿐이고, 한번 로컬이 잡히면 결정론적으로 root까지 직행합니다. 실무 영향은 Critical과 다를 게 없습니다.

AI가 한 시간 만에 찾았다는 것의 의미​

이 발견의 또 다른 화제는 도구입니다. 발견자는 Theori의 Taeyang Lee, 도구는 자체 개발한 AI 기반 보안 스캐너 Xint Code입니다. 단일 오퍼레이터 프롬프트로 Linux 커널의 crypto/ 서브시스템 전체를 약 한 시간 동안 감사했고, 이 버그가 가장 심각한 항목으로 보고됐습니다.

AI가 사람보다 똑똑해서 찾은 것은 아닙니다. "AF_ALG와 splice가 만나는 자리가 실은 거대한 비특권 공격 표면"이라는 가설을 사람이 정해주면, AI는 그 가설을 전체 코드 경로에 결정론적으로 적용해 보는 일을 사람과 비교가 안 되게 잘합니다. 그렇게 10년 동안 사람 손에 안 잡힌 코드 경로 하나를 끄집어냈습니다.

The Register는 Trend Micro Zero Day Initiative의 Dustin Childs를 인용해 "최근 취약점 제보 폭증의 가장 큰 변수는 AI 기반 버그 발견"이라고 보도했습니다. 이 흐름이 운영자 입장에서 의미하는 건 단순합니다. 패치 사이클이 더 짧아져야 하고, 오늘 멀쩡해 보이는 코드가 내일도 멀쩡하리라 가정하지 말아야 한다는 것입니다.

공개 타임라인​

날짜이벤트
2026-03-23Linux 커널 보안팀에 보고
2026-03-25패치 제안
2026-04-01메인라인 커널 커밋 (a664bf3d603d)
2026-04-22CVE-2026-31431 할당
2026-04-29공개 공시

보고에서 메인라인 패치까지 9일이 걸렸습니다. 빠른 편입니다. 그러나 메인라인 패치와 각 배포판의 안정 커널이 사용자 손에 들어오는 시점 사이에는 늘 며칠에서 몇 주의 갭이 있고, 그 사이가 운영자에게 가장 위험한 구간입니다.

운영자 대응 가이드​

즉시 완화, 정식 패치, 검증 세 단계로 나눠 봅니다.

1) 즉시 완화: algif_aead 모듈 차단 (재부팅 불필요)​

LUKS, kTLS, IPsec은 AF_ALG 사용자 공간 인터페이스가 아닌 다른 경로를 쓰기 때문에 영향이 없습니다. 영향을 받는 건 OpenSSL의 afalg 엔진처럼 AF_ALG를 명시적으로 호출하는 경우뿐입니다. 즉 일반 서버에서는 모듈 차단으로 인한 부작용이 거의 없습니다.

단, 차단이 가능한지부터 확인해야 합니다. RHEL 9/10 일부 빌드는 algif_aead가 LKM이 아니라 커널 빌트인이고, 그러면 modprobe로 막을 수 없습니다.

# 모듈로 로드돼 있는지
lsmod | grep algif_aead

# 빌트인인지 (filename: (builtin) 이면 차단 불가)
modinfo algif_aead 2>/dev/null | head -3

LKM인 경우, modprobe로 차단합니다.

echo 'install algif_aead /bin/true' | sudo tee /etc/modprobe.d/disable-algif_aead.conf
sudo rmmod algif_aead 2>/dev/null || true

/bin/false 대신 /bin/true를 쓰는 이유는 다른 모듈이 의존성으로 algif_aead를 끌어올 때 modprobe가 에러를 뱉지 않도록 하기 위해서입니다. 어느 쪽이든 모듈은 올라오지 않습니다.

빌트인인 경우, modprobe 차단은 효과가 없습니다. 두 가지 우회 중 하나를 씁니다.

  • SELinux/AppArmor로 AF_ALG 사용을 제한합니다. 일반 사용자 도메인에서 socket(AF_ALG, ...) 호출 자체를 막는 정책입니다. 정책 변경이라 운영 검증이 필요합니다.
  • 정식 패치 일정을 앞당깁니다. 빌트인 환경에서는 사실상 이 옵션이 가장 현실적입니다.

2) 정식 패치 (재부팅 필요, kpatch 가능 시 무중단)​

RHEL/Rocky/Alma:

sudo dnf updateinfo list cves | grep CVE-2026-31431 # RHSA 발행 확인
sudo dnf update kernel kernel-core kernel-modules
sudo reboot

Ubuntu/Debian:

sudo apt update
apt list --upgradable 2>/dev/null | grep -E '^linux-(image|generic|headers)'
sudo apt install --only-upgrade linux-image-$(uname -r | sed 's/-generic//')-generic
sudo reboot
# Ubuntu Pro의 경우
sudo pro status
canonical-livepatch status # 라이브 패치 발행 여부

라이브 패치(kpatch/canonical-livepatch)가 발행돼 있다면 재부팅 없이 적용 가능합니다. 프로덕션 노드는 우선 라이브 패치로 막은 뒤 다음 정기 점검 시간에 재부팅하는 편이 안전합니다.

3) 검증​

uname -r

# RHEL 계열
rpm -q --changelog kernel-core | grep -i 31431 | head

# Debian/Ubuntu 계열
apt changelog linux-image-$(uname -r) 2>/dev/null | grep -i 31431 | head

CHANGELOG에 CVE 번호가 보이면 패치된 빌드입니다. 라이브 패치를 적용한 경우에는 kpatch list 또는 canonical-livepatch status로 적재된 패치 목록을 함께 확인합니다.

배포판 대응 현황​

배포판초기 대응비고
Debian즉시 패치안정 채널에 빠르게 반영
Ubuntu즉시 패치Pro 사용자에겐 라이브 패치 동시 발행
SUSE즉시 패치SLES/openSUSE 모두
Red Hat처음 보류 → 입장 변경 후 신속 대응일부 빌드에서 algif_aead builtin이라 즉시 완화 옵션이 제한됨
Amazon Linux즉시 패치AL2023 우선
Arch / Fedora즉시 패치롤링 특성상 가장 빠름

여러 배포판이 초기에 "Moderate"로 분류했다가 PoC 공개와 함께 "High"로 재분류한 점도 이번 사례의 특징입니다. race 없는 LPE의 운영 영향이 통상 분류보다 크다는 걸 확인한 결과입니다.

정리​

  • Copy Fail은 race 없는 결정론적 LPE입니다. 패치 전까지는 누구나 한 번에 root를 따냅니다.
  • 단일 버그가 아니라 2011, 2014, 2017년에 들어간 세 변경의 우연한 교차점에서 만들어졌습니다.
  • 페이지 캐시 쓰기 프리미티브라 디스크 무결성 검사로는 잡히지 않고, 컨테이너 격리만으로는 막지 못합니다.
  • 즉시 완화는 algif_aead 모듈 차단이지만 빌트인 환경에서는 무효입니다. 환경부터 확인해야 합니다.
  • 정식 패치는 재부팅이 필요합니다. kpatch나 canonical-livepatch가 가능한 환경이면 무중단 적용을 우선 검토합니다.

다음 글에서는 이 글에서 다루지 않은 두 가지를 따로 봐요. 컨테이너 탈출(user namespace 경계에서의 동작 검증), 그리고 AI 기반 보안 스캐너를 운영 파이프라인에 도입할 때의 트레이드오프예요.

참고​

pgBackRest 종료와 대안

· 약 11분

2026-05-19 업데이트: 6사 컨소시엄으로 정식 부활했습니다. 자세한 내용은 pgBackRest 컨소시엄 부활에 정리했습니다.

2026-05-04 업데이트: pgBackRest는 사실상 부활 쪽으로 가닥이 잡혔습니다.

이 글을 올리고 며칠 지나지 않아 분위기가 뒤집혔습니다. 저장소 archive가 해제됐고("archived": false), 기본 브랜치가 eol로 바뀐 README 상단에 MAINTENANCE UPDATE 섹션이 새로 추가됐습니다. 그 안에서 David Steele(데이비드 스틸)이 직접 부활 의사를 밝혔습니다.

"It is clear that many pgBackRest users... would prefer the project to continue with me as the primary maintainer. ... This time pgBackRest will be funded by a coalition of sponsors so that a single acquisition will no longer affect my ability to continue work on the project."

(많은 pgBackRest 사용자가 내가 계속 메인테이너로 남아 주기를 바란다는 게 분명해졌다. ... 이번에는 한 회사 매각 한 번으로 프로젝트가 흔들리지 않도록 여러 후원사가 함께 자금을 댄다.)

골자는 세 가지입니다. (1) Steele의 메인테이너 복귀, (2) 단독 후원사(과거 Crunchy Data) 의존 모델을 깨고 sponsor coalition 모델로 전환, (3) 추가 메인테이너 영입 예정입니다. README에 명시된 현재 sponsor는 Supabase입니다. 정식 발표는 같은 주 안에 나올 예정으로 잡혀 있습니다.

본문은 2026-04-27 시점의 충격과 해석을 사료로 남기기 위해 그대로 둡니다. 운영자 입장에서 결론은 단순해졌습니다. 즉각 마이그레이션 압력은 약해졌고, 다음 PostgreSQL 메이저 업그레이드까지의 시간은 그대로입니다. 정식 발표가 나온 뒤 한 번 더 들여다보면 됩니다.

여담 한 줄입니다. 후배가 그러더군요. "이래서 파업들을 하는 거라고." 농담이지만 묘하게 정곡을 찔렀습니다. 13년 일한 사람이 손을 놓고 나서야 비로소 자본이 모였기 때문입니다.

한 사람이 13년 지킨 백업 도구​

PostgreSQL 운영을 좀 해본 사람이라면 pgBackRest라는 이름은 거의 반사적으로 익숙할 텐데요, 전체 / 차등 / 증분 백업, PITR, 병렬 압축/전송, S3, GCS, Azure 직결, 무결성 검증까지 PostgreSQL 백업에 필요한 거의 모든 항목이 한 도구로 깔끔하게 풀리는 사실상의 표준이었어요.

그 표준이 멈췄습니다. 2026년 4월 27일, 단독 메인테이너 David Steele(데이비드 스틸)이 GitHub 저장소를 archive 처리하면서 다음과 같이 공식 안내했습니다.

"After a lot of thought, I have decided to stop working on pgBackRest. I did not come to this decision lightly." — David Steele, pgbackrest GitHub README

13년입니다. 한 사람이 13년 동안 사실상 혼자 유지해 온 도구가 멈췄습니다.

개인적으로는 슬프면서도 "이게 그렇게 단순한 슬픔으로 정리될 일인가" 하는 생각이 들어요. 이 글에서는 사실관계, 다른 사람들의 해석, 대안 도구 비교, CNPG에서 Barman이 어떤 자리에 있는지, 그리고 운영자 입장에서 지금 무엇을 할지 정리합니다.

무슨 일이 있었는가​

먼저 타임라인을 살펴보겠습니다.

시점사건
2013David Steele이 pgBackRest 시작 (Crunchy Data(크런치 데이터) 후원)
2024–2025Crunchy Data 매각, Steele이 후속 직장 / 독립 스폰서십 모색
2026-01-19마지막 릴리스 v2.58.0 ("Object Storage Improvements")
2026-04-27"no longer maintained" 공식 선언, 저장소 archive
2026-04-28Percona 공식 입장: 계속 사용 권장
2026-05-04저장소 archive 해제, README에 MAINTENANCE UPDATE 추가. David Steele 부활 의사 공식화, sponsor coalition 모델로 재구성 중

마지막 릴리스 v2.58.0은 PostgreSQL 18까지 공식 지원합니다. 즉 지금 운영 중인 클러스터의 백업이 당장 멈추는 건 아닙니다. 다만 다음 PostgreSQL 메이저(PostgreSQL 19, 2026-09 예정) 출시 이후 호환성/보안 패치 공급원이 사라진다는 점이 분기점입니다.

왜 이렇게 됐는가​

단독 메인테이너 구조에 함정이 있었습니다.

13년간 코드 리뷰, 릴리스, 이슈 트리아지 거의 전부를 한 사람이 처리한 프로젝트였습니다. Crunchy Data가 Steele의 인건비를 받쳐주는 동안에는 그 모델이 굴러갔습니다. 매각 이후 후속 직장과 독립 후원 시도가 이어졌지만, 어느 쪽도 프로젝트를 제대로 유지할 만큼은 모이지 않았습니다.

pgBackRest 후원 모델 전환

"The person who makes sure your data survives a disaster did not make the cut." — Lætitia Avrot(레티시아 아브로), pgBackRest is dead. Now what?

(데이터 재해 복구를 책임지는 사람의 자리는 결국 잘려나갔다.)

이 한 문장이 사실 사건의 전부입니다. AI 붐을 따라 자본은 GPU와 추론 인프라로 흘러가고, "당신의 데이터가 재해 후에도 살아남게 해주는 도구"의 메인테이너 한 명을 살리는 것은 어느 회사의 우선순위에도 들어가지 못했습니다. Steele 본인은 절제된 톤으로 정리했습니다.

"Rather than do the work poorly and/or sporadically, I think it makes more sense to have a hard stop."

대충 유지하면서 사용자 신뢰만 갉아먹느니 깨끗하게 멈추겠다는 결정입니다. 비난할 자리는 아닙니다. 13년치 코드를 그렇게 마감할 수 있는 사람이 흔하지 않습니다.

같은 사건을 바라보는 네 가지 시선​

며칠 사이에 정리된 PostgreSQL 생태계의 주요 입장을 표로 묶으면 다음과 같습니다.

출처입장 요약권장
Lætitia Avrot(레티시아 아브로) (MyDBA Notebook)"pgBackRest is dead." 신규 평가는 Barman, 기존 사용자는 시간 지날수록 위험 증가Barman 평가 시작
Christophe Pettus(크리스토프 페투스) (thebuild.com)"올바른 결정." OSS 인프라의 자본 부족이 본질적 문제WAL-G가 가장 유망한 대체
Gabriele Bartolini(가브리엘레 바르톨리니) (EDB / CNPG 창립자)"선순환의 단절." Barman과 pgBackRest는 철학적 의견차였다고 평가OSS 지속가능성 모델 자체를 재설계
Percona(페르코나) (공식 블로그)"현 상황은 우리 권장사항에 영향이 없다""Keep on using pgBackRest as you did!"

각 입장이 가리키는 곳이 조금씩 다른데, 그 차이가 오히려 흥미롭습니다. Avrot는 "지금 평가 단계라면 굳이 archived 도구를 신규 도입할 이유가 없다"는 운영자 시점에서 봅니다. Pettus는 "오픈소스 인프라의 본질은 자본 문제이지 기술 문제가 아니다"라고 한 단계 위에서 진단합니다. Bartolini는 자기 진영(Barman/CNPG)의 철학적 정당성을 곱씹으면서도 "선순환이 끊긴 결과"라며 더 큰 그림을 봅니다. Percona는 가장 보수적으로, 자기 고객에게 즉각 마이그레이션을 권하지 않습니다.

네 입장이 모순되지는 않습니다. 시간 축이 다를 뿐입니다. "오늘 당장의 운영"에는 Percona, "다음 분기의 신규 도입"에는 Avrot, "내년의 기술 선택"에는 Pettus, "5년 뒤 OSS가 어떻게 살아남을까"에는 Bartolini가 답합니다.

대안 도구 비교​

운영자 입장에서 결정에 필요한 정보만 압축하면 다음과 같습니다.

도구메인테이너백업 모델PITR증분오브젝트 스토리지비고
Barman(바만)EnterpriseDB (활발)rsync / pg_basebackup 위임지원지원지원 (S3, Azure, GCS)CNPG가 채택. "복사는 PG에 위임, 오케스트레이션에 집중" 철학
WAL-GAiven 등 (활발)스트리밍 + 압축지원지원 (delta)지원클라우드 네이티브, MySQL, SQL Server, MongoDB도 지원
pg_basebackup + pg_combinebackupPG 코어PG 내장수동지원 (PG17+)해당 없음외부 의존 회피, 중소 규모
pgmoneta커뮤니티 (GSoC 활발)내장 + 인크리멘털지원지원부분신생, 검증 데이터 부족
pg_dump / pg_dumpallPG 코어논리 export해당 없음해당 없음해당 없음백업 도구 아님. 마이그레이션/스키마 덤프용

마지막 두 행이 좀 어색해 보이지만, Avrot가 원문에서 한 번 더 짚어준 부분이라 굳이 표에 남겼습니다.

"pg_basebackup is a clone tool, not a backup tool. pg_dump is an export tool."

(pg_basebackup은 클론 도구이지 백업 도구가 아니다. pg_dump는 export 도구다.)

PostgreSQL 입문자가 "PostgreSQL에 기본으로 들어 있는 거 쓰면 되지 않나"라고 생각하기 쉽지만, WAL 관리, 복구 명령, 무결성 검증 중 어느 것도 자체적으로 제공하지 않습니다. pg_basebackup으로 만든 base에 자체 WAL 보존/복구 스크립트를 얹는 방식은 곧 직접 만든 mini-Barman이 되며, 그러느니 진짜 Barman을 쓰는 게 낫습니다.

pg_basebackup + pg_combinebackup (PostgreSQL 17+) 조합은 블록 레벨 증분이 들어왔으므로 외부 도구를 도입하기 싫은 작은 클러스터에는 의미 있는 선택지입니다. 다만 오브젝트 스토리지 직결, 보존 정책, 병렬 복원 같은 운영 편의는 직접 짜야 합니다.

선택은 대체로 둘로 좁혀집니다.

베어메탈/VM 운영, EDB 중심 생태계, K8s 환경(CNPG)에는 Barman이 어울립니다. 멀티 클라우드 / 멀티 DB 엔진을 한 도구로 통일하거나 스트리밍 친화적인 구성을 원한다면 WAL-G가 어울립니다.

CNPG와 Barman​

쿠버네티스에서는 이미 답이 나와 있습니다.

쿠버네티스에서 PostgreSQL을 운영 중이라면 이번 일에 가장 마음 편한 쪽입니다. CloudNativePG(CNPG)가 처음부터 Barman 진영에 서 있었기 때문입니다.

CNPG 공식 문서는 현재 백업 메서드를 세 가지로 명시합니다.

  1. plugin: CNPG-I 플러그인 기반 백업. 공식 권장 경로
  2. volumeSnapshot: Kubernetes CSI 볼륨 스냅샷
  3. barmanObjectStore: Barman Cloud 네이티브 통합. v1.26부터 deprecated

핵심은 1번입니다. 네이티브로 들어 있던 Barman Cloud 통합이 v1.26부터 deprecated 처리됐고, 공식 권장 경로는 Barman Cloud Plugin으로 옮겨갔습니다. 즉 코어와 백업 도구가 분리된 플러그인 구조로 진화 중입니다. pgBackRest는 CNPG 공식 문서에 등장하지 않습니다.

흐름을 그림으로 정리하면 다음과 같습니다.

Bartolini의 글에 이 분리의 철학이 잘 정리돼 있습니다.

"Barman delegates data copy mechanisms to PostgreSQL primitives and rsync, focusing instead on orchestration, retention, and recovery management."

(Barman은 데이터 복사 자체는 PostgreSQL의 기본 기능과 rsync에 위임하고, 자기는 오케스트레이션·보존·복구 관리에 집중한다.)

pgBackRest는 정반대 결정을 한 도구였습니다. 복사 메커니즘을 자기 안에 두고 끝까지 통제한다는 쪽입니다. 둘 중 어느 쪽이 옳다고 단정하긴 어렵습니다. 다만 K8s 오퍼레이터의 역할 분담 모델에는 역할을 잘게 쪼개고 위임하는 Barman 쪽이 잘 맞는다는 점이 결국 드러났습니다.

요약하면 이렇습니다.

  • 베어메탈/VM에서 pgBackRest를 쓰고 있었다면 다음 메이저 업그레이드 사이클에 Barman 또는 WAL-G로의 마이그레이션을 평가합니다.
  • K8s + CNPG 환경이라면 이미 Barman Cloud Plugin이 표준이므로 별도 의사결정이 거의 필요하지 않습니다.

운영자 입장에서 지금 무엇을 할 것인가​

0. 호흡 한 번. 즉각 위험은 없다.
1. 인벤토리 — 어느 클러스터가 pgBackRest를 쓰는지
2. 다음 PostgreSQL 메이저 업그레이드 일정 = 분기점
3. 신규 클러스터 / 평가 단계는 Barman 또는 WAL-G로
4. K8s라면 CNPG + Barman Cloud Plugin
5. 단독 메인테이너 의존도가 높은 도구를 식별
6. 백업이 아니라 복구 — 정기 복구 리허설

조금 풀어 쓰면 다음과 같습니다.

  1. 즉시 교체할 필요는 없습니다. v2.58.0은 PostgreSQL 18까지 공식 지원하고, Percona가 "계속 쓰라"는 공식 입장을 냈습니다. 패닉 마이그레이션은 추가 사고를 만듭니다.
  2. 분기점은 다음 PostgreSQL 메이저 업그레이드 시점. PostgreSQL 19 이후 호환성/보안 이슈가 생겼을 때 아무도 책임지지 않습니다. 그 시점까지 마이그레이션 계획만 잡아두면 충분합니다.
  3. 신규로 도입한다면 archived 도구를 새로 들이는 건 합리적이지 않습니다. Avrot의 권고대로 Barman부터 평가합니다.
  4. K8s + CNPG 환경에서는 사실상 의사결정이 끝났습니다. Barman Cloud Plugin으로 가면 됩니다.
  5. 이번 사건의 일반화된 교훈. 운영 critical path에 단독 메인테이너 의존 도구가 또 어디에 있는지 한 번 점검해 볼 가치가 있습니다. 같은 일이 다른 도구에서 다시 일어나지 말란 법이 없습니다.
  6. 백업이 아니라 복구가 본질입니다. 어느 도구로 옮기든, 옮긴 직후 복구 리허설을 한 번 돌려보지 않으면 실제로는 백업이 없는 것과 같습니다.

오픈소스 인프라의 자본 문제에 대한 단상​

Bartolini는 글에서 "선순환(virtuous cycle)"이라는 표현을 썼습니다.

"Companies invest in engineers who build open-source software. That software creates production value. Those organizations purchase commercial support. Companies reinvest profits into engineering."

(회사가 엔지니어에 투자해 OSS를 만든다 → OSS가 운영 가치를 만든다 → 그 운영 가치가 상용 지원으로 환원된다 → 회사가 다시 엔지니어에 재투자한다.)

이 사이클은 어느 한 고리만 끊겨도 무너집니다. pgBackRest는 Crunchy Data의 후원이라는 한 고리에 매달려 있다가, 그 회사의 매각이라는 외생 변수 한 번에 끊겼습니다. 그 단계에서 커뮤니티 후원이 빈자리를 못 메웠다는 게 사건의 본질입니다.

오픈소스가 공짜라는 건 사용자 입장에서의 이야기입니다. 누군가는 그 비용을 치르고 있고, 그 비용 청구서가 어디에도 도착하지 않으면 결국 한 사람이 13년을 혼자 지키다가 손을 놓게 됩니다. 한 사람이 13년을 지킨 코드는 그 자체로 이미 자본 부족을 증명합니다.

Pettus의 한 줄이 이 단상을 닫기에 가장 정확합니다.

"This is not a problem with a technical solution. In the long run, it is the only important problem."

(이건 기술적 해법이 있는 문제가 아니다. 장기적으로 보면, 사실 그것만이 유일하게 중요한 문제다.)

정리​

어제까지오늘부터
pgBackRest 상태사실상 표준archived (v2.58.0 동결)
단독 메인테이너David Steele 13년(미정, 포크 가능성 있음)
후원 모델Crunchy Data 단독 (~2024)후원 부재 → (2026-05-04) sponsor coalition 재구성 중 (현재: Supabase)
신규 평가 권장pgBackRestBarman (또는 WAL-G), 정식 발표 후 재평가
K8s 환경다양한 옵션 검토 가능CNPG + Barman Cloud Plugin
즉각 위험해당 없음없음 (다음 메이저 업그레이드까지)
장기 위험해당 없음PG 신버전 호환성/보안 패치 공급 단절

같은 일이 다음에 일어날 도구는 어디일까요. 그 질문이 이번 사건이 우리에게 남긴 진짜 숙제예요.

참고 자료​

PostgreSQL 18 비동기 I/O

· 약 10분

30년 가까이 동기 I/O로 버텨온 데이터베이스​

PostgreSQL은 1996년에 첫 릴리스가 나온 이래로 process-per-connection 모델 + 동기 I/O라는 단순한 조합을 30년 가까이 유지해왔어요. 클라이언트 하나가 붙으면 백엔드 프로세스 하나가 fork되고, 그 프로세스는 디스크에서 페이지를 읽을 때 read() 시스템 콜을 직접 호출해서 결과가 돌아올 때까지 그냥 멈춰서 기다려요.

이 모델은 단순함이 가장 큰 무기였습니다. fork 한 번이면 격리되고 신호 처리도 직관적이며, 잠금이나 컨텍스트 스위치 같은 까다로운 동시성 이슈도 적게 신경 써도 됩니다. MySQL이나 SQL Server가 thread-per-connection 모델로 가면서 동시성 버그와 평생을 싸우는 동안, PostgreSQL은 다른 길을 갔습니다.

문제는 2020년대의 NVMe와 클라우드 스토리지가 그 단순함의 대가를 점점 더 비싸게 만들고 있다는 점입니다.

PostgreSQL 18(2025-09-25 GA)은 약 30년 만에 처음으로 비동기 I/O 서브시스템을 들고 왔습니다. 이 글에서는 그 구조와 트레이드오프를 살펴봅니다.

동기 I/O의 정확한 병목​

PostgreSQL이 디스크에서 데이터를 읽을 때의 기본 단위는 8KB 페이지 한 장입니다. Sequential Scan 한 번에 100만 페이지를 읽어야 한다면, 옛 모델은 이렇게 동작합니다.

Sequential Scan (cold cache):

read(page 1) → 디스크 응답 대기 (lat) → 처리
read(page 2) → 디스크 응답 대기 (lat) → 처리
read(page 3) → 디스크 응답 대기 (lat) → 처리
...

총 시간 ≈ 페이지 수 × 디스크 latency (직렬)

CPU는 매 페이지마다 디스크를 기다리며 놀고, NVMe는 큐가 대부분 비어 있는 채로 능력의 일부만 씁니다. 클라우드 EBS처럼 한 IOP의 latency가 수백 마이크로초인 환경에서는 이 직렬 누적이 잔인하게 드러납니다.

PostgreSQL도 손 놓고 있던 건 아닙니다. 17 버전까지는 OS의 readahead와 posix_fadvise(POSIX_FADV_WILLNEED) 정도로 커널에 "앞으로 이 영역을 읽을 테니 미리 좀 가져와"라고 힌트만 줬습니다. 동작은 하지만, PostgreSQL이 직접 I/O 깊이를 통제하지 못하기 때문에 어떤 페이지가 언제 도착할지를 알 수 없었습니다. 통계 기반 readahead는 패턴이 깨지면 무력해집니다.

"This feature allows backends to queue multiple read requests, which allows for more efficient sequential scans, bitmap heap scans, vacuums, etc." — PostgreSQL 18 Release Notes, E.4.3.1.3

PostgreSQL 18의 한 줄 요지는 이렇습니다. "여러 read를 큐에 넣고 동시에 보냅니다." 페이지마다 멈추지 않고 100개를 한꺼번에 던져놓은 뒤, NVMe가 알아서 병렬로 처리하게 두자는 이야기입니다.

세 가지 io_method​

PostgreSQL 18은 새 GUC 파라미터 io_method로 비동기 I/O 동작을 고릅니다. 세 가지 옵션이 있습니다.

모드어디서 동작환경 요건장점단점
sync메인 백엔드모든 OSPG17과 동일, 호환성 100%, 추가 프로세스 없음사실상 비동기 X, NVMe/EBS 큐 활용 못 함
workerI/O 워커 프로세스모든 OS (default)어디서든 돌고 sync 대비 1.6배, 운영 안정성 검증된 디폴트워커 프로세스 비용, 컨텍스트 스위치/메모리 카피 오버헤드
io_uring커널과 ring buffer 공유Linux 5.1+sync 대비 2.7배, 워커 없이 syscall 거의 0일부 배포판/컨테이너에서 seccomp으로 차단, Linux 전용(macOS/BSD 불가)

기본값은 worker입니다. 모든 운영체제에서 동작하면서도 동기보다는 확연히 빠르기 때문에 안전한 선택입니다. macOS, BSD 또는 io_uring을 못 쓰는 환경에서는 그대로 default로 두면 됩니다.

함께 추가된 GUC들도 알아둘 만합니다(공식 release notes).

io_workers는 worker 모드의 워커 프로세스 수이며 기본값은 3입니다. io_combine_limit와 io_max_combine_limit는 인접 read를 한 요청으로 합치는 한도입니다. effective_io_concurrency의 기본값은 1 → 16으로 올랐으며, 이 값은 사실상 "동시에 던질 read 수"입니다. maintenance_io_concurrency는 VACUUM 같은 유지보수 작업의 동시 I/O를 조절합니다.

fadvise()가 없는 OS에서도 effective_io_concurrency > 0이 의미를 갖게 됐다는 점도 조용히 큰 변화입니다. 이전에는 Linux 외 환경에서 이 파라미터가 사실상 장식이었습니다.

worker 모드​

누군가 대신 디스크를 읽는 방식입니다.

worker의 구조는 의외로 깔끔합니다.

백엔드는 read 요청을 큐에 넣고 다른 일을 합니다. 별도의 I/O 워커 프로세스 풀이 큐에서 꺼내 실제 read() syscall을 호출하고, 결과를 공유 버퍼에 채워줍니다. 백엔드는 자기가 요청한 페이지가 필요한 시점에 공유 버퍼만 들여다보면 됩니다.

장점은 호환성입니다. POSIX read만 있으면 어디서든 동작합니다. 단점은 워커 프로세스 자체가 일종의 디스패처라서 컨텍스트 스위치와 메모리 카피 비용이 발생한다는 점입니다.

io_uring 모드​

커널에 ring을 심는 방식입니다. io_uring은 Linux 5.1(2019년)에 들어온 커널 비동기 I/O 인터페이스입니다. PostgreSQL과 커널 사이에 공유 메모리 ring 두 개를 두고, 시스템 콜 없이 요청을 주고받습니다.

PostgreSQL 18 구현의 흥미로운 결정 하나는 ring 인스턴스를 backend마다 따로 둔다는 점입니다. 한 인스턴스를 여러 백엔드가 공유하면 lock contention이 생기므로 아예 격리했습니다. 다만 ring 자체는 fork 전에 postmaster가 미리 생성해서 shared memory에 올려둡니다(credativ deep-dive).

워커가 빠지므로 컨텍스트 스위치가 사라지고, syscall도 제출과 대기를 잘 묶으면 거의 0에 수렴합니다. 다만 Linux 5.1+ 전용이며, 일부 배포판은 보안 정책상 io_uring을 비활성화해두기도 합니다(예: 특정 컨테이너 런타임의 seccomp 프로파일).

pg_aios​

PostgreSQL의 I/O 큐를 처음으로 들여다볼 수 있게 됐습니다. 운영자 입장에서 더 반가운 변화는 새 시스템 뷰 하나입니다. pg_aios는 진행 중인 비동기 I/O 요청을 그대로 보여줍니다.

SELECT pid, io_id, state, operation, target, off, length
FROM pg_aios
ORDER BY pid, off
LIMIT 20;

지금까지 PostgreSQL의 I/O는 거의 블랙박스였습니다. pg_stat_io(PostgreSQL 16에서 들어옴)는 누적 통계를, pg_stat_activity는 wait event 정도를 보여줬습니다. "바로 지금 어떤 read가 큐에 떠 있는가"는 이제야 들여다볼 수 있게 됐습니다.

여기서 잡히는 정보로 특정 파일에 I/O가 몰리는 hot relation을 식별하거나, io_uring이 큐를 정말 깊게 쓰고 있는지를 확인할 수 있습니다.

벤치마크​

숫자로 성능 차이를 살펴봅니다.

pganalyze 벤치마크는 AWS c7i.8xlarge에서 3.5GB 테이블의 cold scan을 측정했습니다.

버전 / 모드실행 시간대 PG17대 PG18 sync
PostgreSQL 17 (sync)15,830 ms기준해당 없음
PostgreSQL 18 sync15,071 ms-5%기준
PostgreSQL 18 worker10,051 ms-37%-33%
PostgreSQL 18 io_uring5,723 ms-64%-62%

PostgreSQL 17과 18 I/O 모드별 cold scan 실행 시간 비교

cold cache Sequential Scan에서 io_uring은 PostgreSQL 17 대비 2.7배 빠릅니다. worker도 1.6배 빠릅니다. 같은 PostgreSQL 18을 sync로만 켜두면 거의 차이가 없다는 점도 중요한 신호입니다. 모드 선택이 곧 성능입니다.

이 향상이 어디서 오는지 한 줄로 정리하면, 디스크 latency를 여러 번 직렬로 치르던 것을 한 번 병렬로 치르게 된 것뿐입니다. NVMe는 원래 그렇게 쓰는 물건이었는데, PostgreSQL이 약 30년 만에 그 사용법을 익혔습니다.

한계​

아직 비동기가 아닌 작업도 있습니다.

PostgreSQL 18 AIO는 읽기 작업 일부에만 적용됩니다.

작업PG18 AIO 적용?
Sequential Scan적용
Bitmap Heap Scan적용
VACUUM적용
ANALYZE (일부)적용
Index Scan random read아직 동기
WAL write미적용
Checkpoint write미적용

쓰기는 전부 동기 그대로입니다. 인덱스 random read도 들어가지 않았습니다. 즉 OLTP 점 쿼리 워크로드는 이번 변화로 직접 빨라지지는 않고, 이득은 분석 워크로드/대량 스캔/VACUUM에 몰려 있습니다.

PostgreSQL 19(2026-09-24 Beta 4 공지 기준 GA는 10월 예상) 개발 트리에서는 인덱스 prefetch와 일부 쓰기 경로의 비동기화가 논의되고 있습니다. AIO는 PostgreSQL 18에서 끝난 게 아니라 시작된 것에 가깝습니다.

운영 관점​

무엇을 켤지 선택하는 기준은 사실 단순합니다.

환경추천
Linux 5.1+ 직접 운영, io_uring 사용 가능io_uring
Linux지만 io_uring 비활성 배포판/컨테이너worker (default)
macOS / BSD / 구형 Linuxworker (default)
호환성 이슈 발생 시 임시 폴백sync

추가로 함께 조정할 만한 설정은 다음과 같습니다.

# postgresql.conf 예시 (분석 워크로드 기준)
io_method = io_uring
effective_io_concurrency = 32 # 기본 16에서 NVMe 깊이에 맞게 조정
maintenance_io_concurrency = 32 # VACUUM 가속
io_combine_limit = 256kB

effective_io_concurrency는 "동시에 던질 read 수"라고 생각하면 됩니다. NVMe 큐 깊이 / 동시 사용자 수에 맞춰 늘립니다. 너무 키우면 다른 백엔드와 디스크 대역을 놓고 다투게 되니, 분석 전용 인스턴스가 아니라면 기본 16에서 천천히 올립니다.

배포 전에는 다음 항목을 확인합니다.

  1. 커널 버전(uname -r): 5.1 미만이면 io_uring 불가
  2. seccomp/AppArmor 프로파일이 io_uring 시스템 콜을 막는지 확인
  3. pg_aios 뷰가 보이는지로 AIO 활성 검증
  4. cold scan 워크로드에서 EXPLAIN (ANALYZE, BUFFERS) 비교 측정

한국 커뮤니티 반응​

GeekNews에는 이미 작년에 Postgres 18을 기다리며: 비동기 I/O로 디스크 읽기 속도 향상이 올라왔습니다. 댓글은 많지 않지만 톤은 거의 "드디어"에 가깝고, 관심사는 io_uring 보안 우려와 클라우드 NVMe에서의 측정 향상에 집중돼 있었습니다. 1년이 지난 지금 PostgreSQL 18이 GA된 상태에서 그 기대가 어느 정도 채워졌다고 봐도 됩니다.

정리​

PG17 이하PG18
I/O 모델동기 (메인 백엔드 직접 syscall)동기 + worker + io_uring
모드 선택없음io_method GUC
가시성pg_stat_io (누적 통계)+ pg_aios (실시간 큐)
effective_io_concurrencyLinux fadvise 한정모든 OS, 기본 16
Cold scan 성능 (3.5GB)15.8초5.7초 (io_uring)
적용 범위—Sequential/Bitmap Scan, VACUUM
미적용—Index random read, WAL/Checkpoint write

PostgreSQL이 process-per-connection이라는 30년 가까이 이어온 아키텍처 결정을 바꾸지 않고서도 비동기 I/O를 들였다는 점이 이번 변화의 진짜 핵심입니다. 백엔드 프로세스 모델은 그대로 두고 syscall 경계만 다시 그었습니다. 그래서 운영자 입장에서 마이그레이션이 거의 무료이며, io_method 한 줄만 바꾸면 됩니다.

다음 PostgreSQL 18 시리즈 글에서는 UUIDv7과 B-tree 인덱스의 관계를 다룰 예정이에요. 같은 "스토리지 효율"이라는 축에서, 이번에는 인덱스 페이지 분할 쪽 이야기를 해보려고 해요.

참고 자료​

Chrome 146 세로탭 탐험

· 약 8분

Arc를 처음 만난 날​

Arc를 처음 켰을 때의 인상은 지금도 또렷해요. 새 탭 페이지는 없고 URL바는 화면 한복판이 아니라 옆에 붙어 있었으며, Cmd+T를 누르면 빈 새 탭 대신 "다음에 뭐 할 거야?"라고 묻는 듯한 Command Bar가 떴어요. 5분도 안 돼 "아, 이게 2020년대 브라우저구나" 싶었고, 그날부터 메인 브라우저가 됐어요.

좋아한 부분을 꼽자면 끝이 없었습니다.

Sidebar는 Cmd+S 한 번이면 접히고 콘텐츠가 화면 전체를 덮습니다. 단순한 토글이 아니라 "작업 모드 자체가 전환되는 감각"이었습니다. 글을 읽을 때는 접고 작업할 때는 펴면서, 키 한 번으로 흐름을 바꿉니다.

Spaces는 회사, 개인, 사이드 프로젝트를 색깔과 아이콘으로 분리합니다. 컨텍스트 스위칭이 "탭 닫고 새로 열기"보다 한 단계 위로 올라갔고, Space마다 북마크, 핀, 테마가 따로 존재합니다. Command Bar에는 Cmd+T 한 키로 북마크 점프, URL 입력, 탭 검색, 새 탭 생성이 모두 모입니다. 키보드만으로 거의 모든 이동이 가능해서 어느 순간부터 마우스로 탭을 클릭하지 않게 됐습니다.

Pinned Tabs / Today Tabs에서는 자주 가는 사이트를 사이드바 위쪽에 고정하고, 일회성 탭은 일정 시간이 지나면 자동으로 사라지게 합니다. "탭 정리"라는 행위 자체가 사라졌습니다. 한 번 익숙해지면 다른 브라우저의 탭 50개 무덤이 답답하게 느껴집니다.

Boost는 사이트 CSS와 JS를 직접 오버라이드합니다. 광고 영역을 가리고 폰트를 바꾸거나, 거슬리는 섹션을 통째로 숨기기도 합니다. Twitter의 "For You" 탭을 display: none으로 지우고 썼습니다. Little Arc는 외부 앱에서 클릭한 링크를 작은 팝업 윈도우로 엽니다. 메인 브라우저의 흐름을 깨지 않고 그 자리에서 닫히므로, Slack에서 링크 하나를 확인하려고 메인 창에 탭을 쌓는 일이 사라집니다.

쓸수록 "브라우저는 그냥 탭 컨테이너 아니냐"는 통념이 깨졌습니다. Arc는 브라우저를 단순한 도구가 아니라 작업 공간 그 자체로 다시 정의한 첫 제품이었습니다. 그렇게 한참을 메인으로 썼습니다.

그러다 어쩔 수 없이 Chrome으로​

문제는 한국에서 매일 쓰기에 가끔 까다로웠다는 점입니다. 디자인이 살짝 깨지는 페이지와 결제창이 제대로 안 뜨는 사이트가 종종 있었습니다. 평소에는 "뭐 그러려니" 하고 넘겼는데, 작년 가을 춘천마라톤 등록일에 결제가 안 돼서 선착순 마감을 놓쳤습니다. 그날 이후 호환성이 우선이라는 판단이 섰고, 메인을 Chrome으로 옮겼습니다.

Chrome으로 넘어와서 가장 그리웠던 건 결국 세로탭이었습니다. Sidebar 토글의 흐름, 컨텍스트가 바뀌는 감각, 좌측에 정렬된 깔끔한 탭 목록 — 여기에 익숙해지면 가로 탭으로 돌아가기 어렵습니다. 그렇게 어색한 1년이 흘렀습니다.

그러다 2026년 4월, Chrome 146 stable에 세로탭(vertical tabs)이 정식으로 추가됐다는 뉴스를 봤습니다. Arc의 흐름을 Chrome에서 다시 만날 수 있을까 싶었습니다. 이왕 둘러보는 김에 시중의 다른 브라우저도 한꺼번에 설치해 비교했습니다. 이 글은 그 탐험기입니다.

Chrome의 세로탭​

Chrome 146은 우클릭 메뉴에 "Show Tabs Vertically" 항목을 추가했습니다. 한 번 켜면 다음에도 그대로 유지됩니다. 활성화 자체는 간단합니다.

문제는 이게 전부라는 점입니다.

  • 토글 단축키가 없어 세로탭을 잠깐 접고 가로로 돌리는 흐름이 없습니다
  • 사이드바 너비 조절이 거칩니다
  • 그룹 탭과 세로탭 UI가 어딘가 어색하게 섞입니다
  • Arc의 "공간이 바뀌는 감각" 없이 탭 영역만 옆으로 옮겨갑니다

비교 대상은 분명합니다. Edge는 Cmd+Shift+, 한 번으로 세로/가로 탭을 토글합니다(Windows에서는 Ctrl+Shift+,). 2021년부터 그랬습니다. Chrome이 5년 늦게 따라잡으면서도 단축키를 넣지 않은 건 의도적이라고밖에 보기 어렵습니다.

추측건대 Chrome은 "세로탭 사용자 = 소수 파워유저"라는 데이터를 갖고 있을 것입니다. 우클릭 메뉴에만 둔 건 일반 사용자에게 노출하지 않겠다는 결정으로 보입니다. 합리적이긴 하지만, Arc의 사용성을 기대하고 켠 사람에게는 첫인상부터 부족합니다.

이게 끝이라면 다른 브라우저를 다시 봐야겠다고 생각했습니다.

다시 둘러본 브라우저들​

세로탭이 정식 기능으로 있고, 토글 단축키가 있는지를 기준으로 정리해봤습니다.

브라우저세로탭토글 단축키엔진
Edge정식Cmd+Shift+, (Mac) / Ctrl+Shift+, (Win)Chromium
Vivaldi정식무한 커스텀Chromium
Zen강제매핑 가능Gecko (Firefox 포크)
Arc기본Cmd+SChromium
Dia기본Arc 후속 유사Chromium
Chrome지원 (146~)없음Chromium
Firefox정식F1 사이드바Gecko
Brave정식미확인Chromium
Safari사이드바만—WebKit

각각 며칠씩 써보고 느낀 점을 짧게 정리합니다.

Edge​

단축키의 유무가 이렇게 큰 차이를 만듭니다.

Microsoft 브라우저라 윈도우 전용이라고 생각하기 쉬운데, macOS 버전도 정식으로 있습니다. Apple Silicon 네이티브 빌드까지 지원해서 M1 이상에선 매우 가볍게 돌아갑니다.

Chromium 기반이라 Chrome 확장을 그대로 쓸 수 있고 동기화도 매끄럽습니다. Cmd+Shift+,로 세로/가로를 토글하며(Windows는 Ctrl+Shift+,), 이 차이가 꽤 큽니다. 회의 중 화면을 공유할 때는 가로로, 평소 작업할 때는 세로로 바꿔도 흐름이 끊기지 않습니다.

거슬리는 건 Microsoft 자체 PR입니다. 첫 실행할 때마다 Bing/Copilot/Reading list 같은 걸 꽂으려 듭니다. 한 번 정리해두면 그 뒤론 조용해집니다. 세로탭을 진지하게 쓰고 싶다면 Mac/Win 가리지 않고 가장 안전한 선택입니다.

Vivaldi​

무엇이든 되지만 그만큼 직접 설정해야 합니다.

세로탭은 기본이며 탭 타일링(여러 탭을 한 화면에 분할), 세션 저장, 마우스 제스처, 이메일, 캘린더, RSS 내장까지 갖췄습니다. 모든 명령에 단축키를 매핑할 수도 있습니다. 할 수 있는 게 너무 많아서 첫 실행 후 30분 동안 설정만 하게 됩니다.

파워유저용 도구가 맞고, 거기서 본인이 원하는 워크플로우가 명확하다면 최고의 선택입니다. 다만 "그냥 좋은 디폴트가 있는 브라우저"를 원하는 사람과는 안 맞습니다.

Zen​

Arc UX가 그리운 사람들에게는 새 집 같은 브라우저입니다.

Firefox 148 기반 포크입니다. 세로탭이 기본이고 가로탭은 아예 만들지 않습니다. Arc의 Spaces에 해당하는 워크스페이스, 화면 분할 타일링(최대 4개), 탭 폴더, Glance 미리보기, Zen Mods를 통한 사이트 커스텀까지 갖췄습니다. Arc UX의 향수를 가장 진지하게 계승하는 프로젝트입니다.

오픈소스이고 무료이며 텔레메트리를 최소화합니다. 2026년 4월 기준 1.19.x 베타지만 일상에서 쓰기에 큰 문제는 없습니다. 단점은 Firefox 기반이라 Chrome 확장을 쓰지 못하고, 한국 사이트 호환성도 결국 Chrome보다 떨어진다는 점입니다 — Arc에서 겪었던 패턴이 어느 정도 반복됩니다.

그래도 "무료 Arc 대안"으로는 단연 가장 흥미로운 후보입니다.

Brave​

빠르고 가볍지만 세로탭은 평범합니다.

광고/트래커 차단을 엔진 레벨에서 처리해서 체감 속도가 빠릅니다. 메모리 사용량이 Chrome 대비 60% 이상 적다는 벤치마크가 여러 곳에서 나옵니다. 세로탭도 정식 지원이지만 사용성 자체는 평범합니다. 프라이버시/속도가 1순위이고 세로탭은 보너스 정도라면 좋은 선택입니다.

Firefox​

의외로 가장 견고했습니다.

Firefox 136부터 사이드바와 함께 세로탭이 정식 기능으로 들어왔습니다. 안정성과 표준 준수라는 측면에서 가장 견고합니다. Chromium 계열이 아닌 엔진(Gecko)을 쓰는 메이저 브라우저라는 가치도 다시 보입니다.

세로탭 UX 자체는 Edge보다 살짝 투박하지만, 추적 방지, 확장 생태계, 메모리 효율의 균형은 여전히 좋습니다. 며칠 써본 후보 가운데 가장 의외였습니다.

Safari​

사이드바는 있지만 본격적인 세로탭은 아닙니다.

탭 그룹과 사이드바는 좋지만, 사이드바를 켜도 진정한 세로탭이라기보다는 "탭 그룹 네비게이션"에 가깝습니다. macOS와의 통합(Handoff, iCloud 키체인, 배터리 효율)은 여전히 압도적이지만, 세로탭 자체를 우선순위로 본다면 Safari는 답이 아닙니다.

AI 브라우저​

같은 시기에 등장했지만 다른 질문에 답하는 브라우저입니다.

세로탭 이슈와 별개로, 2025년 말부터 2026년 사이에 AI 네이티브 브라우저 라는 새 카테고리가 본격적으로 등장했습니다. 같이 둘러본 김에 짧게 정리합니다.

브라우저만든 곳플랫폼핵심
DiaThe Browser Company (Arc 팀)macOS Apple Silicon 전용URL바가 AI 어시스턴트, 무료 + Pro $20/월
CometPerplexityWin/Mac/iOS/Android컨텍스트 인식 어시스턴트, MAU 10M (2025 Q3)
ChatGPT AtlasOpenAImacOS Apple Silicon 전용Agent Mode로 멀티탭 자동화, $20~$200/월

흥미롭지만 이들은 "세로탭의 답"과는 다른 질문을 다룹니다. 검색, 리서치, 반복 작업의 자동화를 풀어주는 도구이며, Arc의 "작업 공간으로서의 브라우저" 감각을 채워주는 도구는 아닙니다. Dia가 Arc 팀의 후속이라 가장 가까울 법하지만, 이쪽은 의도적으로 더 보수적이고 채팅 인터페이스 중심입니다.

윈도우 환경에서 AI 브라우저를 진지하게 쓰고 싶다면 현재로서는 Comet이 사실상 유일한 선택지입니다. macOS라면 Dia를 무료로 시작해보는 게 부담 없습니다.

시장 지형, 한 문단​

Statcounter 기준 2026년 글로벌 점유율은 Chrome 71%, Safari 14.7%, Edge 4.6%, Firefox 2.2% 정도입니다. 한국 데스크탑은 Chrome이 72%로 더 압도적입니다. 성능 벤치마크(JetStream 3, Speedometer 3.1)에서는 Chrome이 처음으로 Safari와 공동 1위에 올라왔고, 에너지 효율은 Edge가 1위입니다. 점유율이든 속도든 적어도 데이터상으로는 Chrome을 쓰지 않을 이유가 점점 줄어들고 있습니다. 그럴수록 사용성의 아쉬움이 도드라집니다.

결론​

아직 Arc의 자리는 비어 있습니다.

며칠씩 돌려가며 써보고 정리한 결론은 단순합니다. 현실적인 1순위는 Edge입니다. 단축키와 호환성을 갖췄고 Chrome 확장을 그대로 쓸 수 있어 Windows 환경이라면 거의 무난합니다. 호기심으로 고른 1순위는 Zen입니다. Arc UX의 정신적 후계로서 베타지만 매일 발전 중이고, 무료 오픈소스라는 가치가 큽니다. Chrome의 세로탭은 시작점일 뿐 아직 완성품은 아닙니다. 구글이 단축키를 넣고 사이드바 UX를 다듬어줄 때까지는 차선책에 머뭅니다.

그리고 인정할 건 인정해야 합니다. 한국에서 결제와 본인인증, 회사 사내 시스템 같은 결정적인 순간엔 결국 Chrome이 가장 안전합니다. 춘마 같은 사건은 한 번이면 충분합니다. 그래서 일상 작업은 Edge나 Zen으로 옮겨도, Chrome 한 자리는 여전히 비워두게 됩니다.

Arc가 만들어준 그 감각을 다시 만날 날이 언젠가는 올 거라고 믿습니다. 다만 그게 Chrome 146의 세로탭은 아니었습니다.

그러니 올해 춘천은 Chrome으로 등록할 거예요. 하지만 일상의 브라우저는 아직 찾는 중이에요.

참고​

Claude Code Switcher

· 약 15분

왜 "스위처"가 필요해졌나​

Claude Code가 2025년 2월 공개된 직후만 해도 사용량 캡은 거의 의식되지 않았어요. 그게 바뀐 건 2025-08-28이에요. Anthropic이 Pro / Max 플랜의 기존 5시간 한도에 7일 weekly cap을 더한 날이에요. Max 5x ($100/mo)와 Max 20x ($200/mo)도 weekly 캡에서 자유롭지 않습니다.

cap에 걸리면 두 가지 길이 있습니다.

  1. 그 주가 끝날 때까지 기다립니다
  2. 다른 프로바이더로 옮깁니다. Z.AI의 GLM Coding Plan, Moonshot의 Kimi, OpenRouter의 300개 모델 중 하나, 또는 로컬 Ollama를 선택합니다

옮기는 일 자체는 어렵지 않습니다. Claude Code가 환경변수 두 개(ANTHROPIC_BASE_URL과 ANTHROPIC_AUTH_TOKEN)만 보고 동작하기 때문입니다. 예를 들어 GLM으로 옮기는 방법은 Z.AI 공식 가이드 기준으로 두 줄이면 됩니다.

export ANTHROPIC_BASE_URL="https://api.z.ai/api/anthropic"
export ANTHROPIC_AUTH_TOKEN="..."

다른 프로바이더(Kimi, DeepSeek 등)도 같은 패턴이지만 정확한 Anthropic-호환 엔드포인트 URL은 각 프로바이더 공식 문서에서 직접 확인해야 합니다. 같은 회사라도 OpenAI-호환 엔드포인트(/v1)와 Anthropic-호환 엔드포인트가 다르고, 후자가 잘못 표기된 글이 인터넷에 흔합니다.

문제는 하루에 셋넷씩 갈아타게 되는 순간입니다. 회사 계정, 개인 계정, GLM, 로컬 모델, 임시 OpenRouter를 오가며 매번 셸 변수를 바꾸다 보면, 헷갈려서 회사 계정으로 사이드 프로젝트를 돌리기도 하고 캡이 어디서 깎이는지도 까먹습니다. 이 지점에서 등장한 도구가 CCS(Claude Code Switcher)입니다.

CCS 한 줄 요약​

@kaitranntt/ccs는 Kai Tran(카이 트란)이 만든 Claude Code용 멀티 프로바이더/멀티 계정 프로필 매니저입니다. MIT 라이선스의 npm 패키지이며, GitHub ★2.2k, 2026-04-29 기준 v7.75.0입니다. 721개 릴리즈와 3,687개 커밋이 쌓일 만큼 개발이 매우 활발합니다.

홈페이지: https://ccs.kaitran.ca/ 저장소: https://github.com/kaitranntt/ccs

자기 소개는 한 문장입니다.

The multi-provider profile and runtime manager for Claude Code and compatible CLIs.

홈페이지 설명을 풀면 설정 파일을 고치지 않고 활성 세션을 유지한 채 프로바이더를 옮기자는 도구입니다.

무엇을 지원하나​

설치는 한 줄이면 됩니다.

npm install -g @kaitranntt/ccs
# 또는
bun add -g @kaitranntt/ccs

ccs config 한 번 돌려 초기 설정 후, 프로필 단위로 사용합니다.

명령라우팅
ccs기본값: Claude Sonnet 4.6
ccs glmZ.AI GLM-5.1 (API 또는 Coding Plan)
ccs kimiMoonshot Kimi (1M context)
ccs geminiGemini 3 Pro/Flash (OAuth)
ccs codexGPT-5.4 / Codex (OAuth)
ccs ollama로컬 Ollama 모델
ccs work / ccs personal같은 Claude라도 계정 격리
ccs --target droid glmruntime 자체를 Factory Droid로 바꾸고 모델은 GLM

지원 프로바이더는 점점 늘어 현재 다음을 포함합니다.

  • Claude (Sonnet 4.6, Opus 4.6): 공식
  • GLM-5.1, GLM-5-Turbo, GLM-4.7: Z.AI
  • Kimi K2.5: Moonshot
  • Gemini 3 Pro/Flash: Google (OAuth)
  • GPT-5.4 / Codex: OpenAI (OAuth)
  • Antigravity Pro/Turbo: ccs agy (OAuth)
  • OpenRouter: 300+ 모델 한 번에
  • Ollama, llama.cpp, Novita, Alibaba Coding Plan 등 로컬/OpenAI-호환 모두

어떻게 동작하나​

CCS의 핵심은 두 가지 컴포넌트입니다.

첫 번째는 로컬 Anthropic-호환 프록시입니다. GLM/Kimi처럼 이미 Anthropic-호환 엔드포인트를 제공하는 프로바이더는 단순 패스스루로 지나가고, 그렇지 않은 OpenAI-호환 프로바이더(OpenRouter, DeepSeek 등)는 로컬 프록시가 요청과 응답을 변환해 Claude Code가 자기 형식대로 받게 만듭니다.

두 번째는 CLIProxyAPI라는 OAuth 프록시 백엔드입니다. Gemini, Codex, Antigravity처럼 공식 API 키가 없는 OAuth 기반 프로바이더는 이 별도 컴포넌트가 처리하고, 라운드로빈이나 fill-first 같은 토큰 관리 정책도 여기서 돕습니다. 커뮤니티 fork(CLIProxyAPIPlus)도 옵트인으로 지원합니다.

CCS가 프로필에 따라 호환 형식과 인증 방식별 경로를 선택하는 구조

활성화는 eval "$(ccs proxy activate)" 같이 셸에 평가시켜 ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN을 그 세션 한정으로 주입하는 방식입니다. 다음 셸에서는 다시 깨끗해집니다.

# CCS가 한 번에 처리하는 것
$ ccs glm "이 PR 리뷰해줘"
# 내부적으로:
# 1. ~/.ccs/profiles/glm.settings.json 읽기
# 2. ANTHROPIC_BASE_URL=https://api.z.ai/api/anthropic 주입
# 3. ANTHROPIC_AUTH_TOKEN=$GLM_KEY 주입
# 4. claude code 호출

겉으로는 한 줄이지만 안쪽에서 하는 일은 수동 env 셋과 다르지 않고, 그걸 프로필이라는 단위로 캡슐화했을 뿐입니다.

장점은 어디서 나오나​

가장 눈에 띄는 건 명령 하나가 프로파일 하나에 대응한다는 점입니다. 회사 Claude, 개인 Claude, GLM, Kimi, 로컬까지 다섯 개를 셸 변수 갈아끼우지 않고 명령 하나로 분리합니다. 여기서 한 발 더 들어가면 멀티 계정 격리가 있습니다. ccs work와 ccs personal이 같은 머신에서 동시에 다른 Claude 세션을 돌릴 수 있고 weekly cap도 따로 깎이므로, 회사 계정으로 사이드 프로젝트를 돌리다 cap을 축내는 사고가 구조적으로 사라집니다.

비용 이야기도 빼놓기 어렵습니다. 공식 페이지는 "Save $500–1000/month with strategic delegation"이라 적는데, 검증은 본인 워크로드에 따라 다릅니다. 다만 무거운 보일러플레이트는 GLM($10–30/mo)으로 돌리고 설계만 Claude Max로 가는 패턴은 실제로 cap 부담을 크게 줄여줍니다.

나머지 장점은 결이 비슷합니다. Z.AI처럼 API 키를 발급해 쓰는 곳, Gemini처럼 OAuth로 가는 곳, 로컬 Ollama까지 같은 명령 표면 위로 올라오고, proxy.routing 설정으로 "긴 컨텍스트는 Kimi, 추론은 Claude, 백그라운드는 GLM" 식의 자동 라우팅도 지원합니다. config 파일을 손으로 만지지 않고 비주얼 대시보드로 프로필을 추가/수정할 수 있어, 한국에서 GLM Coding Plan처럼 결제/키 발급이 번거로운 프로바이더를 쓸 때 편합니다. 721 릴리즈에 거의 매일 업데이트될 만큼 개발이 활발해 새 프로바이더가 나오면 빠르게 흡수되는 것도 덤입니다.

단점과 주의사항​

가장 실질적인 부담은 추상화가 두껍다는 점입니다. 프록시가 두 단계(로컬 + CLIProxyAPI)로 끼어들고 프로바이더마다 라우팅 룰이 따로 사는 탓에, "내 요청이 어디로 갔지?"가 헷갈리는 순간이 생깁니다. 로그(~/.ccs/logs/)를 켜둘 가치가 여기 있습니다. 핵심 기능 일부가 CLIProxyAPI라는 별도 프로젝트에 묶여 있다는 점도 같은 맥락인데, 그쪽 이슈가 CCS 동작에 그대로 옮겨붙을 수 있습니다. 버전 업이 매우 빠른 것(v7.75.0, 721 릴리즈)도 새 프로바이더 흡수에는 좋지만 "내 환경에서 안정적인 v7.x"를 고수하려는 보수적 운영에는 부담이라, lockfile로 고정하는 편이 낫습니다.

쓸모의 경계도 분명합니다. 다른 프로바이더를 안 쓰고 회사/개인 두 계정만 격리하는 경우라면 direnv나 .envrc 같은 더 가벼운 도구로 충분하고, CCS는 옮겨 다닐 곳이 셋 이상일 때부터 의미가 생깁니다. 그리고 Codex, Antigravity 같은 비공개 OAuth 프로바이더를 우회로 쓰는 것은 각 서비스 이용약관 위반 소지가 있으니, 회사 환경에서는 한 번 확인하는 편이 안전합니다.

한국에서 쓸 때 걸리는 현실적 지점도 둘 있습니다. Z.AI Coding Plan은 분기 결제(분기당 $30–80), Moonshot Kimi는 위안화 결제라 CCS가 결제까지 해결해주지는 않습니다. 또 이 도구가 weekly cap 회피 수단으로 광고되지만, Anthropic 입장에서 "한 사람이 멀티 계정으로 cap을 돌파"하는 패턴이 쌓이면 정책이 다시 조여질 수 있습니다. 도구의 책임은 아니어도 시장 동학은 의식해 둘 만합니다.

한국 커뮤니티 반응​

CCS 자체에 대한 긱뉴스(news.hada.io) 단일 글은 (작성 시점 기준) 아직 없지만, CCS 같은 도구가 왜 등장했는지를 보여주는 신호는 긱뉴스에 풍부합니다.

스레드(Threads) 쪽에서는 한국어 사용자들이 비슷한 도구를 다양하게 시도 중이라는 점이 눈에 띕니다. vibe proxy로 Claude Code에 AntiGravity와 GLM을 라이드 매핑하거나, oh-my-opencode(★9K)가 Kimi K2.5와 GLM 5를 메인 오케스트레이터로 튜닝하거나, free-claude-code가 NVIDIA NIM 무료 API로 Claude Code를 무료 운영하는 식입니다. 한국 개발자들이 프록시와 멀티 프로바이더라는 패턴 자체에 적극적으로 베팅하고 있다는 의미이고, CCS는 그 흐름에서 가장 잘 정리된 도구 중 하나입니다.

대안 비교​

CCS만 있는 게 아닙니다. 같은 영역에 여러 도구가 경쟁 중입니다.

도구별강점약점
CCS (kaitranntt)★2.2kOAuth 프록시 + GUI 대시보드 + 활발한 업데이트추상화 두꺼움, 의존성 복잡
claude-code-router (musistudio)★33.2k시나리오 라우팅(background/think/longContext), 토큰 임계값 자동 전환, JS 플러그인OAuth 미지원
cc-switch (farion1231)(데스크톱)데스크톱 GUI, Claude Code/Codex/OpenCode/Gemini 통합CLI 친화도 낮음
claude-code-switch (foreveryh)(소규모)미니멀, 한 명령으로 Anthropic 모델만 전환Anthropic 외 미지원
OpenCode (opencode.ai)(별도 트리)75+ 프로바이더, 완전 오픈, Claude Code 호환 인터페이스자체 CLI라 Claude Code 그대로는 아님
DIY (.zshrc 두 줄)—의존성 없음, 가장 투명함프로필 3개 넘어가면 관리 불가

API 키 기반 프로바이더가 2개 이내이고 라우팅이 필요하다면 claude-code-router가 어울립니다. 별 수도 가장 많고 시나리오 라우팅이 가장 강합니다. OAuth 프로바이더를 포함한 멀티 계정이라면 이 영역에서 거의 유일한 CCS가 맞습니다. 데스크톱 친화성과 멀티 툴 통합 GUI를 원한다면 cc-switch, Anthropic만 쓰면서 단순함을 우선한다면 DIY 또는 claude-code-switch를 고르면 됩니다.

시나리오: 회사 Team + Enterprise 두 계정만 쓴다면​

CCS가 풀려는 문제가 프로바이더 다양화와 OAuth 우회라면, Anthropic만 쓰면서 회사 계정 두 개(Team + Enterprise)만 격리하는 시나리오에는 정확히 그 두 가치가 빠져 있습니다. 이때는 도구 선택이 달라집니다.

이 시나리오의 본질은 두 ANTHROPIC 계정 사이를 빠르고 정확하게 전환하는 한 가지로 줄어듭니다. 다른 프로바이더도, 시나리오 라우팅도, 로컬 모델도 무관합니다.

이 경우라면 direnv가 첫 손에 꼽힙니다. .envrc에 export ANTHROPIC_API_KEY=... 한 줄을 두 회사 디렉토리에 각각 두면 cd만으로 키가 자동 로드됩니다. 이 시나리오에서 가장 큰 리스크는 한 회사 코드에 다른 회사 키가 실수로 흘러가는 사고인데, 디렉토리 경계로 그걸 막아주는 게 direnv의 장점입니다.

더 단순하게 가려면 ~/.zshrc에 alias 두 줄이면 됩니다.

alias cct='ANTHROPIC_AUTH_TOKEN=$TEAM_TOKEN claude'
alias cce='ANTHROPIC_AUTH_TOKEN=$ENT_TOKEN claude'

디렉토리에 묶이지 않아 빠른 대신, 지금 어느 회사 컨텍스트인지는 사람이 기억해야 합니다. Anthropic 전용 미니멀 스위처인 claude-code-switch도 CCS보다 가볍고 이 시나리오에 정확히 맞습니다. CCS를 굳이 쓴다면 멀티 계정 모드만(ccs auth create team / ccs auth create ent) 떼어 쓰는 것도 동작은 하지만, 프로바이더 다양화, 프록시, OAuth 같은 95% 기능을 놀리면서 의존성과 복잡도는 그대로 짊어지게 됩니다. 나중에 GLM 같은 외부 프로바이더로 확장할 가능성이 있을 때만 미리 깔아두는 의미가 있습니다.

Enterprise가 SSO를 강제하면 단순 API 키 토글로는 안 되고 OAuth 로그인 사이클이 필요합니다. 이때는 direnv만으로 부족해 CCS의 OAuth 프로필 분리를 쓰거나 전환할 때마다 다시 로그인해야 하는데, SSO 강제에 잦은 전환까지 겹친 경우가 사실상 CCS를 정당화하는 거의 유일한 회사 시나리오입니다. 정리하면 API 키 기반 두 계정에는 direnv로 충분하고, SSO 강제에 잦은 전환이 겹칠 때만 CCS가 값을 하며, 그 밖에는 대체로 과한 도구입니다.

설정/대화는 공유하면서 계정만 분리하고 싶다면​

같은 시나리오의 변종입니다. 두 회사 계정을 전환은 하되 슬래시 커맨드, MCP, hooks, CLAUDE.md/대화 히스토리는 한 군데에서 공유하고 싶은 경우인데, 이때는 정답이 다릅니다.

먼저 Claude Code가 무엇을 어디 두는지 알면 답이 나옵니다.

위치무엇계정 종속?
~/.claude/settings.json사용자 설정 (theme, env, hooks 트리거 등)무관
~/.claude/commands/, agents/, skills/커스텀 슬래시 커맨드, 에이전트, 스킬무관
~/.claude/CLAUDE.md글로벌 메모리무관
~/.claude/projects/<path-hash>/*.jsonl대화 히스토리 (프로젝트 경로 기준)무관
OS 키체인 / ANTHROPIC_AUTH_TOKEN인증 토큰종속

핵심은 계정 종속이 토큰 한 가지뿐이라는 점입니다. 토큰만 갈아끼우면 나머지는 자동으로 공유되므로, 답은 direnv로 토큰만 스위칭하는 것입니다. .envrc에 export ANTHROPIC_AUTH_TOKEN=... 한 줄만 두고 ~/.claude/는 건드리지 않으면, 슬래시 커맨드, hooks, MCP, 대화가 전부 자동으로 공유됩니다. 반대로 ccs auth create work 같은 CCS의 isolated profile 모드는 정확히 이 공유를 분리하는 기능이라 피해야 합니다. CCS 자체는 써도 되지만 isolation 옵션은 끄고 토큰만 바꾸는 모드로만 씁니다. 여러 머신 사이에서도 공유하고 싶으면 ~/.claude/를 git으로 관리하는 dotfiles가 답인데, settings.json에 토큰이 평문으로 들어가지 않도록 .gitignore에 settings.local.json을 등록해 두는 정도만 주의하면 됩니다.

다만 Enterprise 데이터 격리는 짚어둘 필요가 있습니다. Enterprise 플랜 약관에 대화 데이터를 다른 organization 환경과 섞지 못하게 하는 조항이 있을 수 있어서, PII, 고객 데이터, 비공개 코드를 다루는 프로젝트라면 ~/.claude/projects/를 공유하는 것이 회사 보안 정책 위반이 될 수 있습니다. 사내 보안팀에 한 번 확인하는 게 안전하고, 우려가 있으면 프로젝트 디렉토리 자체를 회사별로 분리해 자연히 다른 <path-hash>로 떨어뜨리는 패턴(예: ~/work/team/, ~/work/ent/ 두 루트)이 가장 깔끔합니다.

SSO 강제 환경, 한국 회사 현실에 더 가까운 케이스​

위 두 서브섹션은 API 키를 사용자가 직접 발급받을 수 있다는 전제 위에 있습니다. 그런데 국내 대기업, 금융권, 보안 민감 조직 대부분은 그 전제 자체가 깨져 있습니다. Team/Enterprise 플랜에서 SSO를 강제하면 사용자 API 키 발급이 비활성화되고, 인증은 OAuth + IdP MFA 사이클로만 가능합니다. 이 환경에서는 도구 선택이 다시 한 번 달라집니다.

먼저 구조를 짚으면 다음과 같습니다.

  1. claude login → 브라우저 → 회사 IdP 로그인 (SSO + MFA) → Anthropic Console → 토큰 발급
  2. 토큰은 OS 키체인 또는 ~/.claude/.credentials.json에 저장
  3. 토큰은 보통 시간 제한이 있고 (몇 시간~며칠), 만료되면 다시 SSO 사이클
  4. 한 머신의 한 OS 유저는 동시에 한 organization의 세션만 보유 가능

SSO 강제 환경의 Claude 로그인과 토큰 만료 흐름

즉 SSO 강제 환경에서 두 회사 organization 사이를 전환하려면 결국 logout, 다른 org로 login, 다시 작업 사이클을 돌아야 합니다. IdP가 매번 MFA를 요구하니 도구가 우회할 수 있는 영역이 아닙니다.

실제로 동작하는 패턴은 현실적 운영 순으로 정리하면 다음과 같습니다. 가장 흔한 사례이자 가장 깔끔한 결론은 organization 하나만 쓰는 것입니다. 회사가 Enterprise 하나를 표준화했고 Team이 레거시/POC였다면 둘 다 유지할 이유가 없으니 회사 IT에 정리를 요청하는 편이 낫습니다.

두 organization이 진짜 별개라면 방법이 무거워집니다. 같은 노트북에 OS 계정 두 개를 만들어 각각 SSO 로그인 상태를 독립으로 두면 키체인과 ~/.claude/가 자연히 분리되지만, 설정/대화 공유가 깨져서 dotfiles를 두 OS 유저 사이에 공유하는 추가 작업이 필요합니다. CCS의 OAuth 프로필 분리(ccs auth create work-team / ccs auth create work-ent)는 이론상 이 시나리오에 정확히 맞지만, 회사 IdP가 OAuth 흐름에 거는 추가 검증(IP 화이트리스트, 디바이스 트러스트, MFA 빈도)에 따라 CCS의 토큰 캐시 방식이 IdP 정책과 충돌할 수 있어 시도 전에는 알기 어렵습니다. 사내 보안팀과 사전 협의를 권합니다. 가장 보수적인 선택은 두 회사용 Dev Container나 VM을 따로 띄워 각각 한 organization으로 로그인하는 것인데, 무겁고 호스트 키체인을 못 쓴다는 대가가 따릅니다.

반대로 SSO 환경에서 동작하지 않는 패턴은 다음과 같습니다.

  • direnv + ANTHROPIC_AUTH_TOKEN 토글: 사용자 API 키 자체가 없으니 토글할 대상이 없습니다.
  • 셸 alias로 토큰 swap: 같은 이유로 동작하지 않습니다.
  • "session 토큰을 추출해서 재사용": 만료가 짧고 IdP가 디바이스 핑거프린팅으로 거부할 가능성이 있습니다. ToS의 회색지대이기도 합니다.
  • 두 organization의 ~/.claude/.credentials.json을 수동으로 백업/복구: 동작은 할 수 있지만 MFA 빈도 정책을 우회하는 모양새가 되어 정책 위반 우려가 있습니다.

한국 회사 현실에 맞는 결론은, SSO 강제면 organization 전환 자체가 비용이 크다는 사실을 인정하고 두 organization을 동시에 자주 오가지 않는 워크플로우를 설계하는 쪽입니다. 오전엔 Team org에서 한 덩어리, 오후엔 Enterprise org에서 다른 덩어리를 처리하는 식의 시간 분할이, 전환을 도구로 매끄럽게 만드는 것보다 운영 부담이 훨씬 적습니다. 도구는 그 다음 문제입니다.

실전 팁​

블로그, GitHub 이슈, 도구 README의 패턴을 종합해 한국 환경에서 바로 쓸 만한 것만 모았습니다.

가장 큰 가치는 라우팅 자동화보다 자기 워크플로우에 맞는 모델 분리에서 나옵니다. 설계/아키텍처는 ccs(Claude Sonnet), 보일러플레이트/테스트 정리는 ccs glm, 1M 컨텍스트가 필요한 대규모 리뷰는 ccs kimi, 민감 코드는 로컬 ccs ollama 정도가 무난한 시작점입니다. Claude Sonnet 4.6도 1M 컨텍스트를 지원하지만 토큰 값이 비싸서, 큰 monorepo 전체 리뷰는 ccs kimi 한 번이 훨씬 싸게 먹힙니다. 새 prompt가 안정적인지 확인하는 단계라면 첫 5~10번은 무료인 ccs ollama로 돌려보다 만족스러우면 Claude로 옮기면 됩니다.

회사/개인 계정은 무조건 격리하는 편이 좋습니다(ccs auth create work / ccs auth create personal). weekly cap이 섞이지 않아 둘 다 마음 편히 씁니다. 여기에 더해 Claude Max의 weekly cap이 60% 이상 깎였으면 남은 작업의 70% 정도는 GLM으로 돌리는 룰을 정해두면, cap이 갑자기 끊기는 사고가 사라집니다.

라우팅은 단순하게 시작하는 편이 낫습니다. proxy.routing 시나리오 라우팅을 처음부터 복잡하게 짜면 요청이 어디로 갔는지 못 따라가니, 모델을 직접 지정해 일주일 운용하고 패턴이 보이면 그때 룰을 추가합니다. 기본 CLIProxyAPI가 새 프로바이더를 못 잡으면 옵트인 fork(CLIProxyAPIPlus) 활성화부터 시도해 볼 만합니다.

eval을 매번 실행하기 싫으면 ~/.zshrc에 한 줄을 넣어 영구화합니다.

[[ -s "$HOME/.ccs/init.zsh" ]] && source "$HOME/.ccs/init.zsh"

멀티 계정 사고를 디버깅할 때는 CCS_LOG=debug ccs glm "..." 식으로 한 번 돌려보면 어느 base URL로 갔는지, 어느 토큰을 썼는지 정확히 보이니 로그를 켜두는 게 필수입니다.

한 줄로​

CCS는 Claude Code가 환경변수 두 개로 정의되는 도구라는 사실을 그대로 받아들인 도구입니다. 그 두 변수를 프로파일이라는 단위로 정리하고, OAuth 우회와 다중 계정까지 한 명령 표면 위로 올려놓았습니다. weekly cap 시대에 한 프로바이더에만 의지하는 게 부담스러워진 사용자에게 기본기에 가까운 도구가 됐고, 같은 영역에 claude-code-router 같은 더 큰 별 수의 경쟁자도 있습니다. 자기 워크플로우가 옮겨 다닐 곳이 셋 이상인지부터 보고, 그다음에 도구를 고르는 순서가 맞아요.


→ 다음 편: Claude Code Switcher (CCS) 2편: 디렉토리, 설정, 라우팅의 실제 — 사용자 머신의 ~/.ccs/를 직접 들여다보며 instance / shared 격리 모델, config.yaml의 9개 영역, 로컬 프록시의 5단계 변환을 풀어 봅니다.

참고​

CCS 설정과 라우팅

· 약 14분

1편의 잔상​

Claude Code Switcher(1편)가 표면의 글이었어요. CCS가 왜 등장했는지, 무엇을 풀고 있는지, 누구에게 언제 맞고 안 맞는지를 다뤘어요. 그 글의 한 단락을 그대로 옮기면 이렇습니다.

CCS는 Claude Code가 환경변수 두 개로 정의되는 도구라는 사실을 그대로 받아들인 도구입니다. 그 두 변수를 프로파일이라는 단위로 정리하고, OAuth 우회와 다중 계정까지 한 명령 표면 위로 올려놓았습니다.

2편은 그 표면 아래를 다룹니다. ~/.ccs/ 한 디렉토리에 무엇이 어떻게 사는지, config.yaml의 어느 키가 무엇을 결정하는지, instance와 shared가 어떻게 갈리는지, 로컬 프록시는 정확히 어떤 변환을 거치는지 살펴봅니다. 1편에서 표 한 줄로만 끝낸 부분을 시스템의 그림으로 풀어 봅니다.

이 글의 모든 디렉토리/키 이름은 사용자 머신의 실제 CCS 설치를 직접 들여다본 결과입니다. 토큰/세션 같은 민감한 값은 보지 않고 구조만 인용합니다.

~/.ccs/ 한 장 지도​

CCS가 머신에 자리 잡으면 홈 디렉토리에 ~/.ccs/ 한 폴더가 생깁니다. 그 안의 트리가 사실상 CCS의 운영 모델 전부인데, 진짜 그림은 심볼릭 링크 체인입니다.

~/.claude/ ← 사용자의 기존 Claude Code 글로벌 (canonical)
├── settings.json
├── commands/, skills/, agents/, plugins/
└── projects/

~/.ccs/ ← CCS 영역
├── config.yaml ← 메인 설정 (YAML, 11K 정도)
├── .session-secret ← 세션 비밀 (64B, 절대 공유 금지)
├── .claude/ ← CCS *번들* (ccs.md, ccs-delegation 스킬 등 자체 주입)
├── cliproxy/bin/ ← CLIProxyAPI 바이너리 (자동 다운로드)
├── cache/, completions/, logs/
│
├── shared/ ← 사용자 정의 공유 영역
│ ├── settings.json → ~/.claude/settings.json (symlink)
│ ├── commands → ~/.claude/commands (symlink)
│ ├── skills → ~/.claude/skills (symlink)
│ ├── agents → ~/.claude/agents (symlink)
│ ├── plugins → ~/.claude/plugins (symlink)
│ └── context-groups/default/projects/ (실 디렉토리)
│
└── instances/ ← per-account 격리 영역
├── team/ ← 회사 Team account
│ ├── settings.json → ~/.ccs/shared/settings.json (symlink)
│ ├── commands → ~/.ccs/shared/commands (symlink)
│ ├── skills → ~/.ccs/shared/skills (symlink)
│ ├── agents → ~/.ccs/shared/agents (symlink)
│ ├── projects → ~/.ccs/shared/context-groups/default/projects/ (symlink)
│ ├── plugins/, .anthropic/, sessions/, session-env/ ← 실 디렉토리
│ ├── todos/, file-history/, shell-snapshots/, logs/ ← 실
│ ├── backups/, cache/, debug/, image-cache/, paste-cache/, plans/ ← 실
│ └── .claude.json, history.jsonl, .session-stats.json,
│ mcp-needs-auth-cache.json, policy-limits.json, remote-settings.json
└── enterprise/ ← 회사 Enterprise account (구조 동일)

여기서 눈여겨볼 것은 두 가지입니다. 하나는 2단계 symlink 체인입니다. instances/team/settings.json을 따라가면 ~/.ccs/shared/settings.json으로, 다시 ~/.claude/settings.json으로 이어집니다. 사용자의 기존 Claude Code 글로벌 설정이 모든 인스턴스에 자동 반영된다는 뜻이며, 한 번 만든 슬래시 커맨드, 스킬, MCP가 team과 enterprise 양쪽에서 살아 있는 이유도 여기에 있습니다.

다른 하나는 projects/만 체인이 한 단계라는 점입니다. 대화 히스토리는 instances/team/projects에서 ~/.ccs/shared/context-groups/default/projects/(실 디렉토리)까지만 가고 ~/.claude/projects/까지는 이어지지 않습니다. CCS가 자기 context-group 단위로 대화를 모아 사용자의 평소 Claude Code 대화와 자연히 분리해 두는 것으로, 회사 대화가 개인용 Claude Code에 섞이지 않도록 의도한 끊음입니다.

이 2단계 체인이 이중 구조라고 표현했던 것의 실제 메커니즘이며, 1편의 "공유 vs 분리"가 단일 축이 아니라 어느 단위까지 체인을 잇느냐의 문제였다는 뜻입니다. 5~6절에서 이 단위들을 풀어 봅니다.

CCS 인스턴스가 설정을 공유하고 인증과 세션을 격리하는 경계

config.yaml의 9개 영역​

설정의 거의 전부는 ~/.ccs/config.yaml 한 파일에 모입니다. JSON이 아니라 YAML인 점이 의외인데, 사람이 손으로 읽고 고치기 좋은 쪽을 택한 것으로 보입니다.

최상위 키는 15개이고, 역할이 같은 키를 묶으면 9개 영역으로 나뉩니다. 메타(version, default), 계정(accounts), 프로바이더 프로파일(profiles), OAuth 프록시(cliproxy), 로컬 프록시(proxy), CLIProxy 서버(cliproxy_server), 로그와 환경설정(logging, preferences), 웹 검색(websearch), 외부 도구 통합(copilot부터 global_env까지)입니다.

version: # 메타
default: # 기본 프로파일

accounts: # *instance 단위* 메타
team:
context_mode: # isolated | shared
context_group: # shared 일 때 그룹 이름
continuity_mode: # 추가 공유 정책
enterprise:
...

profiles: # 프로바이더 프로파일 (glm, kimi, ollama, ...)

cliproxy: # OAuth 프록시 백엔드 설정
backend:
oauth_accounts:
providers:
routing:
strategy: # round-robin / fill-first
session_affinity:
session_affinity_ttl:

proxy: # 로컬 OpenAI-호환 프록시
profile_ports: # 프로파일별 포트 매핑
routing:
longContextThreshold:

cliproxy_server: # 원격/로컬 CLIProxy 서버
remote: { enabled, host, protocol, auth_token }
fallback: { enabled, auto_start }
local: { port, auto_start }

logging: # 로그 회전·보관
preferences: # theme, telemetry, auto_update

websearch: # ★ 8개 fallback 프로바이더
providers:
exa, tavily, brave, searxng, duckduckgo, gemini, opencode, grok

# --- 통합 (Claude Code 외부 도구들) ---
copilot: # GitHub Copilot 라우팅
cursor: # Cursor 통합 (ghost_mode 포함)
channels: # Telegram/Discord/iMessage
thinking: # opus/sonnet/haiku 별 thinking 기본값
global_env: # DISABLE_TELEMETRY 등 전역 env 자동 주입

이 9개 영역을 한 번 살펴보면 CCS가 단순 스위처가 아니라 Claude Code 주변의 운영 표면 전체를 흡수해 가고 있다는 인상이 분명해집니다. 12절에서 이 중 안 알려진 부분을 따로 추립니다.

Instance 시스템, symlink와 실 상태의 갈림​

instances/<name>/ 디렉토리는 언뜻 보면 Claude Code의 모든 상태를 담은 통 같지만, ls -la로 열어 보면 절반은 shared/로의 symlink이고 나머지 절반만 인스턴스별 실 상태입니다. 둘을 갈라 두면 멘탈 모델이 명확해집니다.

먼저 모든 인스턴스가 공유하는 symlink 항목입니다.

항목가는 곳
settings.json~/.ccs/shared/settings.json → ~/.claude/settings.json
commands/~/.ccs/shared/commands → ~/.claude/commands/
skills/~/.ccs/shared/skills → ~/.claude/skills/
agents/~/.ccs/shared/agents → ~/.claude/agents/
projects/~/.ccs/shared/context-groups/default/projects/ (한 단계만)

settings.json의 최상위 키 9개는 체인을 따라가 본 결과 다음과 같습니다. 사용자의 기존 ~/.claude/settings.json 스키마 그대로입니다.

alwaysThinkingEnabled
enabledPlugins
env
extraKnownMarketplaces
hooks
permissions
skipAutoPermissionPrompt
skipDangerousModePermissionPrompt
statusLine

다음은 인스턴스별로 격리된 실 상태입니다.

분류항목
인증 (절대 안 섞임).anthropic/ (OAuth), .claude.json (사용자 메타)
세션/실행sessions/, session-env/, shell-snapshots/, history.jsonl
작업 상태todos/, file-history/, plans/ (team만), backups/
로깅/캐시logs/, cache/, debug/, image-cache/, paste-cache/
플러그인plugins/ — 인스턴스별 (claude-hud 같은 설치)
메타/정책policy-limits.json, remote-settings.json, mcp-needs-auth-cache.json, .session-stats.json
외부 통합.omc/ (oh-my-claudecode 상태)

CCS는 보이지 않게 CLAUDE_CONFIG_DIR 같은 환경변수로 인스턴스 컨텍스트를 끼워 넣습니다.

enterprise 인스턴스는 team보다 가볍게 비어 있는 상태였는데, 활성 사용 중인지 아닌지가 디렉토리 충실도에 그대로 드러납니다. CCS는 인스턴스를 만들 때 symlink와 빈 디렉토리만 만들어 두고, 실제로 사용해야 실 데이터가 채워지는 lazy 모델입니다.

shared/와 context-groups로 1편의 빈칸 메우기​

1편의 "설정/대화는 공유하면서 계정만 분리하고 싶다면" 단락의 진짜 답이 여기 있습니다. CCS는 이 시나리오를 shared/라는 별도 영역과 context_mode/context_group 설정으로 정식 지원합니다.

config.yaml의 accounts: 섹션:

accounts:
team:
context_mode: isolated # 또는 shared
context_group: default # shared 일 때만 의미
continuity_mode: deeper # 또는 default
enterprise:
context_mode: shared
context_group: default
continuity_mode: deeper

세 키의 역할:

context_mode: isolated | shared는 인스턴스가 자기 디렉토리에만 사는지, 같은 그룹의 다른 인스턴스와 공유하는지를 정합니다. context_group은 shared일 때 쓸 그룹 이름이며, ~/.ccs/shared/context-groups/<group>/가 그 그룹의 공유 데이터 자리입니다. continuity_mode: deeper는 공유의 깊이를 정하며, deeper는 더 많은 디렉토리를 공유 대상에 포함합니다.

이 셋을 조합해 보면 1편에서 "공유 vs 분리"라고 단순화한 구도가 사실 세 축의 조합임이 드러납니다.

무엇이 공유 가능하고 무엇이 항상 격리되는가​

README는 이 부분을 강조하지 않지만, 저장소의 docs/session-sharing-technical-analysis.md에는 이렇게 적혀 있습니다.

CCS only shares workspace context paths (project/session context files). It does not merge or copy authentication credentials between accounts.

— docs/session-sharing-technical-analysis.md

요약 표:

분류대상동작
shared (조건부)session-env/shared + deeper 일 때 그룹 안에서 공유
shared (조건부)file-history/동일
shared (조건부)shell-snapshots/동일
shared (조건부)todos/동일
항상 격리.anthropic/ (OAuth)인증은 어떤 모드에서도 인스턴스별
항상 격리인증 토큰 / 자격증명동일
수동 공유commands/, skills/, agents/, plugins/shared/<dir>/에 두면 모든 인스턴스가 사용

인증은 어떤 경우에도 섞이지 않습니다. 한 인스턴스의 OAuth 토큰이 실수로 다른 인스턴스의 요청에 흘러갈 일이 구조적으로 없으므로, 회사 환경에서 가장 큰 사고 가능성을 시스템 자체가 막아 둡니다.

ccs -r (resume)는 현재 활성 lane만 이어가고, ccs <account> -r은 그 인스턴스의 lane만 이어갑니다. 두 인스턴스 모두 다른 continuity 인벤토리를 가질 수 있다는 점을 의식하고 운영해야 합니다.

4가지 진입점: Target Adapter System​

ccs가 Claude Code만 호출하는 게 아닙니다. CCS는 runtime 자체를 바꾸는 4가지 바이너리를 노출합니다.

바이너리runtime
ccsClaude Code (기본)
ccsd / ccs-droidFactory Droid
ccsx / ccs-codexCodex CLI (네이티브)
ccsxpCodex CLI + CLIProxy 프로바이더 오버라이드

내부적으로는 각 바이너리가 CCS_INTERNAL_ENTRY_TARGET 환경변수를 세팅한 후 target resolver에 위임합니다. resolver의 우선순위:

  1. CLI 플래그 (--target)
  2. 진입 바이너리 자체
  3. argv[0] 이름 검출
  4. 프로파일별 config
  5. 기본값

이 추상화 덕분에 "Claude Code로 GLM 돌리기"와 "Droid로 GLM 돌리기"가 같은 명령 표면 위에서 가능합니다(ccs glm vs ccs --target droid glm). Codex의 경우 자기 ~/.codex/ 상태를 별도 보존하려고 환경변수만 임시로 덮어쓰는 식으로 신경을 더 씁니다.

로컬 프록시 (127.0.0.1:포트)의 5단계 흐름​

OpenRouter, DeepSeek 같은 OpenAI-호환 프로바이더 프로필로 요청이 들어오면 다음 5단계를 거칩니다(docs/openai-compatible-providers.md 정리).

  1. 127.0.0.1에서 그 프로파일의 로컬 포트 바인딩 (포트는 proxy.profile_ports에서 할당)
  2. Claude Code가 보내는 Anthropic 형식 /v1/messages 요청 수신
  3. OpenAI chat-completions 형식으로 변환
  4. 업스트림 프로바이더로 포워드
  5. 스트리밍 응답을 다시 Anthropic SSE로 역변환해 Claude Code에 돌려줌

정리하면 Claude Code는 자기가 Anthropic과 이야기하는 줄 알고, 변환은 프록시 한 점에서만 일어나므로 디버깅 지점도 한 곳으로 모입니다.

Anthropic-호환 엔드포인트는 프록시를 우회합니다. https://api.anthropic.com이나 Z.AI의 /api/anthropic으로 가는 요청은 변환이 필요 없어 그냥 직통하고, 이 분기 덕분에 GLM 사용은 추상화 비용이 거의 0입니다.

CCS 로컬 프록시가 요청과 스트리밍 응답을 변환하는 5단계 흐름

CLIProxyAPI 서버 자체는 기본적으로 port 8317에서 동작하는데, cliproxy_server.local.port로 바꿀 수 있고 remote.host를 켜면 다른 머신의 CLIProxy를 쓸 수도 있습니다.

시나리오 라우팅 4종​

CCS가 수동으로 프로바이더를 명시하지 않아도 자동으로 라우팅하는 4가지 시나리오가 있습니다.

시나리오트리거
background요청이 Haiku를 포함 (가벼운 백그라운드 작업)
thinkAnthropic extended thinking 활성화
longContext토큰 추정치가 임계값 초과 (proxy.routing.longContextThreshold)
webSearchweb_search 툴 호출

config.yaml에 proxy.routing.longContextThreshold 키가 실제로 있어 임계값을 손으로 조절합니다. 자동화의 깊이는 워크로드에 따라 잘 맞기도 하고 헷갈리기도 하므로, 처음부터 라우팅 룰을 복잡하게 짜기보다 한 주 동안 단순하게 운영한 뒤 패턴을 보고 추가하는 편이 낫습니다(1편 실전 팁 참조).

CLIProxy 서브시스템, OAuth 프로바이더의 자리​

API 키가 없는 OAuth 기반 프로바이더(Gemini, GitHub Copilot, AWS Kiro 등)는 별도 서브시스템이 책임집니다. ~/.ccs/cliproxy/bin/에 자동 다운로드되는 CLIProxyAPI 바이너리가 그 핵심입니다.

이 서브시스템의 Account manager는 OAuth 토큰의 lifecycle(발급, 갱신, 만료)을 관리하고, Quota manager와 Quota fetcher는 각각 프로바이더별 quota 추적과 자동 failover, 사용량 실시간 동기화를 맡습니다. Auth handler는 Anthropic, Gemini, Copilot, Kiro 등 각 프로바이더의 OAuth 흐름을 처리합니다. Model catalogs에는 프로바이더별 모델 목록과 compatibility 가드가 있으며, codex-plan-compatibility.ts 같은 파일이 plan별로 맞지 않는 모델 조합을 막아 줍니다. Hybrid quota strategy는 round-robin 또는 fill-first(cliproxy.routing.strategy) 방식으로 같은 프로바이더의 여러 OAuth 계정을 어떻게 분산할지 정합니다.

config.yaml의 cliproxy.oauth_accounts가 등록된 OAuth 계정 목록을 담는데, 이 부분은 토큰을 포함하므로 git에 올리면 안 되는 영역입니다. 다음 11절에서 정리합니다.

파일 관리 실전: symlink가 dotfiles의 답입니다​

2절의 symlink 체인을 보고 나면 자연스레 이런 질문이 나옵니다. 내 CCS 환경을 다른 머신에 어떻게 옮기는지, ~/.ccs/를 통째로 git에 넣어도 되는지 궁금해집니다. ~/.ccs/를 versioning하지 않고 ~/.claude/를 versioning해 CCS가 그쪽을 끌어들이게 하면 됩니다.

멘탈 모델: ~/.claude/가 source of truth​

체인의 시작점은 ~/.claude/입니다. CCS의 shared/와 instance들은 그쪽으로 가는 포인터일 뿐입니다. 따라서 dotfiles의 운용 원칙은 하나로 압축됩니다. ~/.claude/를 git으로 관리해 두면, 새 머신에 CCS를 설치하더라도 shared/만 같은 symlink로 다시 걸어 모든 인스턴스가 같은 환경을 보게 됩니다.

git에 올리기 좋은 자산 (~/.claude/ 쪽)​

~/.claude/commands/, ~/.claude/skills/, ~/.claude/agents/에는 각각 슬래시 커맨드, 스킬, 에이전트 정의가 들어가므로 git에 올리기 좋습니다. 글로벌 메모리인 ~/.claude/CLAUDE.md도 관리 대상입니다. ~/.claude/settings.json은 토큰을 직접 적어 두지 않은 경우에만 포함해야 하며, env 필드에 비밀이 있다면 settings.local.json 패턴으로 분리합니다. 실 디렉토리인 ~/.ccs/shared/context-groups/default/에서는 projects/를 빼고 CCS의 그룹 메타만 versioning하는 식으로 관리합니다.

~/.ccs/.claude/ (CCS 번들)은 npm 패키지 일부라 install 시 자동으로 따라옵니다. 직접 versioning 할 필요 없습니다.

절대 git 금지 (자격증명, 세션, 트랜스크립트)​

~/.ccs/config.yaml에는 OAuth 토큰/refresh_token이 있으므로 머신별로 보관하고, 세션 비밀인 ~/.ccs/.session-secret도 제외합니다. ~/.ccs/instances/*/.anthropic/의 Anthropic OAuth와 계정 식별자를 포함한 ~/.ccs/instances/*/.claude.json도 올리면 안 됩니다.

~/.ccs/instances/*/sessions/, session-env/에는 세션과 세션별 env가 있어 토큰 환경변수가 들어갈 가능성이 있습니다. ~/.ccs/instances/*/history.jsonl, file-history/에는 명령과 파일 history가 남아 회사 코드의 흔적이 섞일 수 있습니다. 대화 히스토리 본체인 ~/.ccs/shared/context-groups/*/projects/도 PII나 회사 코드를 포함할 가능성이 있으므로 제외합니다. 바이너리와 휘발성 데이터가 담긴 ~/.ccs/cliproxy/, cache/, logs/, 평소 Claude Code 대화 히스토리인 ~/.claude/projects/도 git에 올리지 않습니다.

새 머신 부트스트랩 4단계​

dotfiles가 ~/.claude/만 들고 있다고 할 때 새 머신에서 같은 환경을 만들려면:

  1. dotfiles를 clone해 ~/.claude/를 채웁니다.
  2. npm install -g @kaitranntt/ccs로 CCS를 설치합니다.
  3. ccs config로 초기 ~/.ccs/ 골격을 생성합니다.
  4. symlink를 재구성합니다. ~/.ccs/shared/{commands,skills,agents,plugins,settings.json}을 ~/.claude/의 동명 항목으로 link하며, 한 줄 스크립트로 자동화하기를 권장합니다.
ln -sf ~/.claude/commands ~/.ccs/shared/commands
ln -sf ~/.claude/skills ~/.ccs/shared/skills
ln -sf ~/.claude/agents ~/.ccs/shared/agents
ln -sf ~/.claude/plugins ~/.ccs/shared/plugins
ln -sf ~/.claude/settings.json ~/.ccs/shared/settings.json

이 다섯 줄로 새 머신에서도 1편의 instance들이 평소 환경을 그대로 봅니다. OAuth 인증만 각 인스턴스에서 새로 하면 끝입니다.

격리의 운영 이점​

한 인스턴스가 망가져도(plugins/ 깨짐, policy-limits.json 누락 등) 다른 인스턴스는 그대로이므로 단일 장애점이 줄어드는 부수 효과가 있습니다. 인스턴스 단위 백업에는 tar czf team-backup.tgz -C ~/.ccs/instances team을 씁니다. symlink는 그대로 보존하며, 옵션 -h를 추가하면 링크가 가리키는 파일까지 따라가 archive합니다. 복원할 때 같은 dotfiles 환경을 가정하므로 보통은 symlink를 그대로 두는 편이 의미 있습니다.

config.yaml의 비공식 영역들​

CCS의 README가 강조하지 않지만 config.yaml 키만 봐도 드러나는, 단순 스위처라기엔 풍부한 통합이 여럿입니다.

가장 눈에 띄는 것은 WebSearch fallback입니다. websearch.providers 아래에 exa, tavily, brave, searxng, duckduckgo, gemini, opencode, grok 여덟 곳이 등록돼 있어, Claude Code의 WebSearch가 Anthropic 밖 8개 프로바이더로 넘어갈 수 있습니다. docs/websearch.md가 별도 문서로 있을 만큼 일급 통합입니다. 외부 에디터/툴 통합도 넓어서, copilot.account_type, rate_limit, model로 GitHub Copilot을 프로바이더 한 슬롯처럼 다루고 cursor.ghost_mode/cursor.port로 Cursor의 ghost-mode까지 건드립니다. channels.selected, channels.unattended를 통해 Telegram, Discord, iMessage로 Claude에게 일을 시키는 통합도 있습니다(1편의 free-claude-code 텔레그램 봇 사례와 같은 결입니다).

세밀한 정책 키도 있습니다. thinking.tier_defaults는 opus, sonnet, haiku별로 thinking 기본 모드를 다르게 잡게 해주고(Opus는 항상 deep thinking, Haiku는 끄는 식), global_env.env의 DISABLE_BUG_COMMAND, DISABLE_ERROR_REPORTING, DISABLE_TELEMETRY는 보안, 프라이버시에 민감한 회사 환경에서 텔레메트리를 자동으로 꺼줍니다. cliproxy.routing.session_affinity와 session_affinity_ttl은 같은 세션을 같은 OAuth 계정으로 sticky하게 묶는 정책입니다. 이런 영역들은 CCS가 단순 스위처에서 운영 플랫폼으로 진화 중이라는 신호입니다.

한 줄로​

1편이 CCS의 표면이었다면 2편은 안쪽입니다. 안쪽을 보고 나면 "프로파일로 옮겨 다닌다"는 1편의 단순한 설명이 사실은 instance 격리, shared 공유, OAuth 분리, OpenAI와 Anthropic 사이의 변환, 시나리오 라우팅, 여러 통합 지점이라는 축들로 짜여 있다는 게 보입니다. 추상화가 두껍다는 1편의 단점 평가는 그래서 정확하지만, 그 두께가 풀고 있는 문제도 같이 두껍다는 사실이 디렉토리 한 통에 적나라하게 드러납니다.

다음 편이 있다면 두 갈래 중 하나일 가능성이 커요. proxy.routing 시나리오를 실제 워크로드로 맞춰 보는 측정 기반 운영 글이거나, CCS와 dotfiles, Anthropic Workspaces를 엮는 멀티-머신 계정 운영 글일 거예요. 어느 쪽이 먼저 나올지는 다음 글에서 정할게요.

참고​

systemd 유닛 11종

· 약 10분

"유닛"이라는 단어가 묶고 있는 것​

SysVinit → Upstart → systemd: 리눅스 init 세대 연표의 마지막 단락은 이렇게 끝났어요.

systemd는 init만이 아니라 logind, journald, networkd, resolved, timedated 등 시스템 영역의 여러 컴포넌트를 흡수해 갔다.

이 글에서는 그 한 줄을 펼쳐 봅니다. 시스템 영역에서 systemd가 흡수해 간 것 중 가장 가시적인 흔적은 흩어진 옛 도구를 유닛(unit)이라는 단일 언어로 묶은 모습입니다. cron, fstab, inetd, autofs, inotify, runlevel, cgroup은 저마다 다른 시대에 다른 사람이 다른 이유로 만든 도구지만, *.service, *.timer, *.mount처럼 같은 모양의 파일 안으로 모여들었습니다.

서버에 들어가서 systemctl --type=help 한 번 실행해 보면 출력이 짧습니다. 11줄.

service
socket
target
device
mount
automount
timer
swap
path
slice
scope

이 11종이 무엇을 흡수했는지 먼저 표로 보여준 다음, 한 종류씩 풀어봅니다.

유닛흡수한 옛 도구
.service/etc/init.d/* 셸 스크립트
.socketinetd / xinetd / launchd
.timercron / anacron / at
.mountfstab
.automountautofs
.pathinotify 사용처 (디렉토리 감시 데몬)
.targetrunlevel
.slicecgroup 트리의 그룹 이름
.scope외부에서 만든 프로세스를 cgroup 으로 묶는 래퍼
.deviceudev 와의 다리
.swapswapon

systemd의 트리거 유닛이 service와 mount 유닛을 깨우는 관계

.service: 옛 init.d 스크립트의 자리​

가장 익숙한 유닛입니다. 1편의 hello-web.service 9줄 예제를 다시 떠올리면 충분합니다.

[Unit]
Description=hello web server
After=network.target

[Service]
ExecStart=/usr/local/bin/hello-web --port 8080
Restart=on-failure

[Install]
WantedBy=multi-user.target

중요한 부분은 Type=의 주요 7종입니다(man systemd.service, v253+는 notify-reload 추가).

Type언제 쓰나
simple기본값. ExecStart 가 즉시 메인 프로세스
execsimple + 자식 exec 까지 끝나야 활성화 처리
forking옛 데몬 스타일. fork 후 부모는 종료하고 자식이 데몬으로 동작
oneshot일회성 작업 (마이그레이션, 셸 스크립트 등)
notify자식이 sd_notify(3) 로 "준비됨" 통지
dbusDBus 이름 등록 시점에 활성화 처리
idlesimple 변형. 다른 잡이 콘솔 출력을 끝낼 때까지 대기

옛 init.d 시절의 가장 큰 골칫거리였던 "데몬이 fork한 자식을 init이 추적하지 못한다"는 문제는 cgroup으로 깔끔하게 해결됐습니다(1편의 3절 참조). PID 파일 위조도 더블 fork도 cgroup 트리에서 빠져나가지 못합니다. Restart=on-failure 한 줄이 monit/supervisord의 자리를 흡수한 것도 이 추적 덕분입니다.

.service 한 종류만 따로 깊게 다룬 글을 한 편 더 쓸 만큼 옵션이 많습니다. 이 글에서는 이 정도만 살펴보겠습니다.

.socket: inetd → xinetd → launchd → systemd의 계보​

.socket이 가져온 모델을 흔히 "socket activation"이라 부릅니다. 처음 들으면 systemd의 발명 같지만 사실 40년짜리 계보의 마지막 단계입니다.

연도도구한 줄
1980년대 초inetd (4.3BSD)슈퍼 서버. 소켓을 listen하다가 연결이 들어오면 데몬을 fork
1990년대 후반xinetdinetd 를 보안성 강화로 대체 (Panagiotis Tsirigotis(파나기오티스 치리고티스))
2005-04-29launchdMac OS X 10.4 Tiger 도입. Dave Zarzycki(데이브 자지키) 설계. 데몬을 미리 안 띄우고 첫 연결로 깨운다
2010systemd socket activationlaunchd에서 영감을 받아 Linux로 도입

1편에서 "macOS launchd에서 영감을 받았다"는 한 줄로 끝낸 부분의 21년짜리 사연입니다. 공교롭게도 launchd의 데뷔일은 정확히 21년 전 오늘입니다.

systemd가 자식에게 listen 소켓을 어떻게 넘기는지는 sd_listen_fds(3) man page에 정확히 적혀 있습니다.

The first file descriptor may be found at file descriptor number 3 (i.e. SD_LISTEN_FDS_START), the remaining descriptors follow at 4, 5, 6, ...

세 개의 환경변수가 따라옵니다. $LISTEN_PID는 이 fd들이 자기 것인지 PID 일치 여부를 검사하고, $LISTEN_FDS는 넘어온 소켓 개수를 나타냅니다. $LISTEN_FDNAMES에는 각 소켓의 라벨(FileDescriptorName=)이 담깁니다.

자식은 accept()만 하면 되고, listen은 systemd가 부팅 직후 미리 해 둔 상태입니다. 그 결과는 다음과 같습니다.

  • 부팅 시 데몬을 미리 띄우지 않아도 됩니다(첫 연결로 깨움)
  • 데몬을 재시작해도 listen 소켓이 살아 있어 연결이 잘리지 않습니다
  • 의존성도 자연스럽게 연결됩니다. A가 B의 소켓을 두드리면 systemd가 B를 알아서 깨웁니다

간단한 예로 sshd.socket을 보겠습니다.

[Unit]
Description=OpenSSH Server Socket

[Socket]
ListenStream=22
Accept=no

[Install]
WantedBy=sockets.target

Accept=yes면 연결마다 인스턴스화된 sshd@.service가 깨어납니다. 이것이 inetd의 원래 모델입니다.

.timer: cron / anacron / at의 후계​

cron 표현식은 "분 시 일 월 요일" 5컬럼입니다. systemd의 OnCalendar=는 같은 일을 하지만 표현법이 다릅니다.

[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=300

트리거 키워드는 6종입니다(man systemd.timer).

OnActiveSec=는 타이머가 활성화된 시점을 기준으로 삼고, OnBootSec=는 부팅 후 N초, OnStartupSec=는 systemd가 뜬 후 N초를 가리킵니다. 반복 잡에는 짝꿍 유닛이 마지막으로 활성화된 시점을 기준으로 하는 OnUnitActiveSec=나 마지막으로 멈춘 시점을 기준으로 하는 OnUnitInactiveSec=를 씁니다. OnCalendar=는 cron의 자리를 맡은 달력식입니다.

보조 키워드도 그대로 정확히 적어둡니다.

Persistent=true는 시스템이 꺼져 있던 시간을 보정해 부팅 직후 한 번 실행하며 anacron의 자리를 맡습니다. RandomizedDelaySec=는 N초 범위에서 무작위로 지연해 cron으로 풀기 어려운 thundering herd를 피합니다. AccuracySec=는 1분 단위로 묶어 깨워서 노트북 배터리와 디스크 사용을 줄이고, OnClockChange=와 OnTimezoneChange=는 시계가 점프했을 때 트리거됩니다.

타이머는 자기가 일을 하지 않습니다. .timer 는 짝꿍 .service 를 한 번씩 깨울 뿐입니다. 잡 본체는 .service 에 적습니다.

# /etc/systemd/system/certbot-renew.timer
[Timer]
OnCalendar=daily
Persistent=true
RandomizedDelaySec=1h

[Install]
WantedBy=timers.target
# /etc/systemd/system/certbot-renew.service
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet

cron과 비교하면 journald에 자동으로 기록되고, Wants= / After=로 의존성을 표현하며, 실패할 때 OnFailure=로 알림 잡을 트리거한다는 이점이 분명합니다.

.mount / .automount: fstab의 그림자와 autofs 흡수​

리눅스를 오래 다룬 사람도 잘 모르는 사실이 있습니다. fstab은 부팅 시점에 그대로 쓰이지 않습니다. systemd-fstab-generator(8)가 끼어들어 fstab의 각 줄을 .mount와 .swap 유닛으로 변환한 다음 systemd의 의존성 그래프에 넣습니다.

systemd-fstab-generator is a generator that translates /etc/fstab into native systemd units... instantiating mount and swap units as necessary.

이름 규칙도 정해져 있습니다(man systemd.mount의 예시).

Mount units must be named after the mount point directories they control. Example: the mount point /home/lennart must be configured in a unit file home-lennart.mount.

따라서 /var/log는 var-log.mount가 됩니다. 슬래시는 하이픈으로 바꾸고 첫 슬래시는 떼어냅니다. 이 변환 로직은 systemd-escape 명령으로 직접 확인할 수 있습니다.

$ systemd-escape -p --suffix=mount /var/log
var-log.mount

.automount는 autofs의 자리를 흡수했습니다. 마운트 지점에 처음 접근할 때 비로소 mount하고, 일정 시간 idle 상태가 이어지면 unmount합니다. NFS 같은 큰 볼륨을 lazy mount할 때 유용합니다.

# /etc/systemd/system/mnt-bigdata.automount
[Unit]
Description=Lazy mount for /mnt/bigdata

[Automount]
Where=/mnt/bigdata
TimeoutIdleSec=600

[Install]
WantedBy=multi-user.target

짝꿍 .mount가 함께 있어야 합니다(mnt-bigdata.mount). .automount는 트리거를 담당하고 .mount는 실제로 마운트합니다.

.path: inotify를 유닛 언어로​

"파일이 생기거나 바뀌거나 사라지면 잡을 깨운다"는 패턴은 오래전부터 있었습니다. cron 폴링, inotifywait 스크립트, 별도의 파일 감시 데몬이 그 역할을 했습니다. .path는 이를 유닛 언어로 흡수했습니다.

키워드트리거 조건
PathExists=경로가 존재하면
PathExistsGlob=글롭 패턴이 매치되면
PathChanged=파일/디렉토리가 변경되면 (close-after-write)
PathModified=매 write 마다
DirectoryNotEmpty=디렉토리에 항목이 있으면
# /etc/systemd/system/upload-watch.path
[Path]
PathChanged=/srv/upload
Unit=upload-process.service

[Install]
WantedBy=multi-user.target

cron으로 1분마다 폴링하던 잡을 즉시 반응형으로 바꿀 수 있습니다. .timer와 마찬가지로 .path도 직접 일하지 않고 짝꿍 .service를 깨우는 모델입니다.

.target: runlevel의 후계​

1편의 런레벨 표를 그대로 가져와 매핑하면 다음과 같습니다.

런레벨systemd target
0poweroff.target
1rescue.target
3multi-user.target
5graphical.target
6reboot.target

init 3 자리에 systemctl isolate multi-user.target 이 들어왔습니다.

런레벨은 단순한 모드 번호였습니다. target은 의존성 그래프 위의 동기화 지점입니다. network-online.target은 "네트워크가 실제로 도달 가능해질 때까지 기다리는 자리", local-fs.target은 "로컬 파일시스템이 모두 마운트된 자리", sockets.target은 "모든 socket activation listen이 끝난 자리"입니다.

/etc/systemd/system/default.target은 심볼릭 링크입니다. 데스크톱이면 graphical.target, 서버면 보통 multi-user.target으로 연결됩니다.

$ systemctl get-default
multi-user.target

.slice / .scope: cgroup 트리에 이름을 붙이는 두 형태​

1편에서는 "cgroup 추적이 systemd 승리의 결정타였다"고 적었습니다. 그 cgroup 트리를 시스템에서 직접 들여다보게 해 주는 유닛이 .slice와 .scope입니다.

$ systemd-cgls
Control group /:
├─user.slice
│ └─user-1000.slice
│ ├─user@1000.service
│ └─session-3.scope
│ └─sshd 와 자식 프로세스들
├─system.slice
│ ├─nginx.service
│ ├─postgresql.service
│ └─sshd.service
└─machine.slice
└─runc 컨테이너들

최상위 슬라이스는 세 개로 나뉩니다. system.slice에는 시스템 데몬이, user.slice에는 로그인 사용자(login session)가, machine.slice에는 VM과 컨테이너가 속합니다.

.scope는 systemd가 직접 만들지 않은 프로세스, 예를 들어 SSH 로그인 세션이나 runc가 띄운 컨테이너를 cgroup으로 묶는 래퍼입니다. systemd 입장에서는 "외부에서 도착한 프로세스 무리에 이름표를 붙여 트리에 끼워 넣는" 도구입니다.

자원 제한은 슬라이스에든 서비스에든 들어갈 수 있습니다.

[Slice]
MemoryMax=4G
CPUQuota=50%
TasksMax=200

/etc/systemd/system/heavy-jobs.slice 한 파일에 4GB / 50% / 200 task 제한을 걸어두고, 거기에 속한 .service들이 그 한도를 공유하는 방식입니다.

.device / .swap: udev와 swapon의 다리​

직접 작성하는 일이 거의 없는 두 종류입니다.

.device는 udev 이벤트로 자동 생성됩니다. /dev/sda가 인식되면 dev-sda.device가 자동으로 활성화되며, 다른 유닛에서 BindsTo=dev-sda.device처럼 의존성을 거는 데 주로 씁니다.

.swap은 fstab의 swap 항목을 앞에서 본 systemd-fstab-generator가 변환해 만듭니다. 직접 작성할 일이 거의 없고, 작성하더라도 Where= 대신 What=으로 디바이스를 지정합니다.

정직하게 말하면, 11종 중 운영자가 직접 작성하는 것은 사실상 6~7종이에요. .device와 .swap은 자동으로 생성되고 .scope도 외부 도구가 만듭니다. 직접 손으로 쓰는 것은 .service, .socket, .timer, .mount, .automount, .path, .target, .slice 정도입니다.

"init 비대화" 비판이 가리키는 것​

여기까지 9개 섹션을 거치면 자연스럽게 한 그림이 보입니다. init 한 자리에 cron, fstab, inetd, autofs, inotify, runlevel, cgroup 도구가 모두 모여 있습니다. 1편에서 비껴간 "Unix 철학과 어긋난다"는 비판이 정확히 이 그림을 가리킵니다.

비판 진영의 가장 또렷한 한마디는 Slackware 창립자 Patrick Volkerding(패트릭 볼커딩)의 2012년 인터뷰에 나옵니다.

"I don't spend all day rebooting my machine, and having looked at systemd config files it seems to me a very foreign way of controlling a system to me, and attempting to control services, sockets, devices, mounts, etc., all within one daemon flies in the face of the UNIX concept of doing one thing and doing it well."

— Patrick Volkerding(패트릭 볼커딩), 2012

"services, sockets, devices, mounts, etc., all within one daemon"은 이 글이 2절부터 9절까지 보여준 11종 투어와 정확히 같은 그림을 묘사합니다. Volkerding은 그것을 부담으로 봤습니다.

반대편에서 Lennart Poettering(레나르트 푀터링)은 2010년 "Rethinking PID 1"에서 정반대 입장을 폈습니다. 단일 데몬이 의존성 그래프와 cgroup 추적을 한 자리에서 가지고 있어야 socket activation, parallel boot, 정확한 프로세스 정리가 일관되게 동작한다는 논지였습니다.

이 글은 어느 쪽이 옳다고 말하지 않습니다. 다만 두 진영이 가리키는 그림 자체, 곧 init 한 자리에 옛 도구 7~8종이 흡수된 모습은 같다는 데까지 보여줍니다. 그 그림을 어떻게 평가할지는 운영하는 시스템의 성격과 운영자의 취향에 달렸습니다.

한 줄로​

1편이 "PID 1 자리에 누가 앉느냐"의 이야기였다면, 2편은 "그 자리에 앉은 것이 자기 영역을 어디까지 정의했느냐"의 이야기입니다. 유닛이라는 한 단어가 cron부터 cgroup까지 끌어안았다는 사실을 두고, 좋은 평가와 나쁜 평가가 같은 그림 위에서 출발합니다.

다음 편에서는 둘 중 하나를 더 깊이 파고들 예정이에요. .service 한 종류를 끝까지 살피는 운영자용 다이브, 또는 1편에서 중요하게 다룬 의존성 그래프(Wants= / Requires= / After=)가 실제로 풀리는 방식이에요. 어느 쪽이 먼저 나올지는 다음 글에서 정할게요.

참고​

systemd 공식 man pages는 freedesktop.org가 upstream입니다. 아래 링크는 모두 그곳을 가리킵니다.