이번 주에는 공급망 공격, 데이터베이스 성능, 분산 시스템 설계, AI 에이전트 인프라 소식이 한꺼번에 나왔습니다. 개발자에게 직접 영향이 있는 소식 다섯 개를 골랐습니다. 소식마다 원문을 직접 확인했습니다.
■ 오늘 소식 한눈에
소식 | 분야 | 한 줄 요약 | 영향받는 사람
----------------------------------------------------
Zod 저장소에 악성 커밋 | 보안·공급망 | 탈취된 계정으로 원격 코드 로더를 심은 커밋이 main에 올라갔다가 되돌려짐 | Zod를 쓰는 모든 TS/JS 프로젝트, npm 배포 파이프라인 운영자
Postgres가 아니라 스토리지가 느리다 | 데이터베이스 | 같은 인스턴스에서 로컬 NVMe가 gp3 EBS보다 처리량 9.24배 | 클라우드에서 Postgres를 직접 운영하는 팀
S3는 합의를 대체하지 않았다 | 분산 시스템 | S3 조건부 쓰기(CAS)는 드문 결정만 맡을 수 있음 | 오브젝트 스토리지 기반 시스템 설계자
Uber MCP Gateway | AI 인프라 | 800개 넘는 MCP 서버, 5000개 넘는 도구를 게이트웨이 하나로 | 사내 API를 에이전트에 붙이려는 플랫폼 팀
브라우저 지문은 새로운 쿠키 | 웹·프라이버시 | 쿠키 없이도 Canvas·WebGL·오디오로 사용자를 식별 | 프론트엔드 개발자, 개인정보 담당자
■ 1. Zod 저장소에 원격 코드 로더를 심은 커밋
▶ 무슨 일인가
Zod는 TypeScript 스키마 검증 라이브러리입니다. 이 저장소의 main 브랜치에 "Fix bugs and improve performance"라는 제목의 커밋(d821685)이 올라왔습니다. 커밋 시각은 2026-09-21 11:56 UTC, 작성자는 메인테이너 계정(colinhacks)입니다.
커밋이 바꾼 것은 다음과 같습니다.
- zod와 @zod/mini의 버전을 4.6.5에서 4.6.6으로 올림.
- 두 패키지에 meta.test.mjs 파일을 추가하고 files 목록에 넣음.
- package.json 최상위에 "preinstall": "node meta.test.mjs" 필드를 추가함.
- .github/workflows/release.yml에 main 푸시 시 자동으로 npm에 배포하는 트리거를 추가함. "수동 실행과 npm 환경 승인이 필요하다"는 주석은 지움.
같은 날 21:33 UTC에 메인테이너가 PR #6620 "Cleanup bad commits"로 이 커밋을 되돌렸습니다. 함께 되돌린 목록에는 "4.6.7"·"4.6.8" 버전 커밋과 "ci: drop the npm environment approval gate from publish" 같은 CI 변경도 있습니다.
▶ 로더는 어떻게 도는가
meta.test.mjs는 이름과 달리 테스트가 아닙니다. 압축된 짧은 스크립트이고, 흐름은 다음과 같습니다.

1. 공개 이더리움 RPC(eth.llamarpc.com, ethereum-rpc.publicnode.com, cloudflare-eth.com)에 차례로 eth_call을 보냅니다. 대상은 고정된 컨트랙트 주소와 함수 셀렉터 0x53ed5143입니다.
2. 응답을 문자열 배열로 디코딩합니다. 이것이 다음 단계 서버의 도메인 목록입니다.
3. 각 도메인의 https://<도메인>/loader에 요청합니다. 응답 JSON에 setupB64, payloadName, payloadB64가 모두 있어야 다음으로 넘어갑니다.
4. 임시 경로에 ld- 접두사 디렉터리를 만들고 페이로드를 Base64로 풀어 저장합니다.
5. setupB64를 data:text/javascript;base64, URL로 만들어 동적 import() 합니다.
6. 실행이 끝나면 임시 디렉터리를 통째로 지웁니다.
핵심은 1~2단계입니다. 다음 단계 서버 주소가 코드에 없고 블록체인 위에 있습니다. 공격자는 컨트랙트 상태만 바꾸면 패키지를 다시 배포하지 않고도 접속 서버를 바꿀 수 있습니다. 페이로드 본문은 아직 공개 분석이 없습니다.
▶ npm에 나간 4.6.6은 깨끗한가
여기가 헷갈리기 쉬운 부분입니다. 현재 npm의 zod@4.6.6은 2026-10-11 16:18 UTC에 배포된 별개 릴리스입니다. 레지스트리에서 tarball을 직접 받아 확인했습니다. meta.test.mjs 파일은 없고, package.json에 preinstall도 없습니다. 출처 증명(provenance attestation)도 붙어 있습니다. 즉 악성 커밋의 "4.6.6"은 npm에 나가지 않았고, 이름만 같은 정상 버전이 나중에 나간 것으로 보입니다.
설령 그 커밋이 그대로 배포됐더라도 preinstall이 실행됐을지는 불확실합니다. npm은 라이프사이클 스크립트를 scripts 객체 안에서 찾습니다. 이 커밋은 최상위에 넣었습니다. 다만 공격자가 함께 넣은 CI 변경(자동 배포·승인 게이트 제거)이 살아 있었다면 이야기가 달라집니다. 진짜 위험은 파일 한 개가 아니라 배포 경로를 열려는 시도였습니다.
▶ 확인할 것
[코드: bash]
# 1) 설치된 zod 버전과 의존 경로 확인
npm ls zod @zod/mini
# 2) 혹시 악성 파일이 들어왔는지 확인
find node_modules -name 'meta.test.mjs' -path '*zod*'
# 3) 앞으로 설치 스크립트를 기본으로 끄기
npm config set ignore-scripts true
[/코드]
ignore-scripts를 켜면 네이티브 모듈 빌드처럼 설치 스크립트가 꼭 필요한 패키지가 깨질 수 있습니다. 그런 패키지만 npm rebuild <이름>으로 따로 돌리세요. 패키지를 배포하는 쪽이라면 릴리스 워크플로 파일을 CODEOWNERS로 묶고, 배포 환경에 수동 승인을 걸어 두세요. 이번 공격자가 지운 것이 바로 그 게이트입니다.

■ 2. "Postgres가 느린 게 아니라 스토리지가 느린 것이다"
▶ 무슨 일인가
ClickHouse 블로그의 POSETTE 2026 발표 정리(2026-08-20)가 이번 주 긱뉴스 상위에 올랐습니다. 같은 인스턴스(m6id.4xlarge, vCPU 16, RAM 64GiB, shared_buffers 16GiB)에 Postgres 클러스터 8개를 띄웠습니다. 4개는 인스턴스 스토어 NVMe, 4개는 기본 gp3 EBS(3,000 IOPS)입니다. 데이터는 33억 행, 힙 482GiB인 pgbench_accounts입니다. 클라이언트 64개가 5분 동안 무작위 UPDATE를 돌렸습니다.
지표 | 로컬 NVMe | gp3 EBS (3,000 IOPS)
------------------------------------------------------------
처리량 중앙값 | 16,030 TPS | 1,734 TPS
트랜잭션 지연 중앙값 | 4.0 ms | 36.9 ms
페이지 읽기 | 0.3 ms | 약 19 ms
커밋 시 WAL fsync | 1.5 ms | 약 11 ms
트랜잭션당 CPU 작업 | 약 2 ms | 약 2 ms
10GB 블로트 VACUUM | 366 초 | 964 초
10GB 논리 복제 백로그 해소 | 139 초 | 238 초
처리량 차이는 9.24배입니다. 원문은 이 결과가 해당 EBS 설정에만 해당한다고 적었습니다.
▶ 왜 이렇게 벌어지는가
표에서 가장 중요한 줄은 "트랜잭션당 CPU 작업 약 2ms"입니다. 양쪽이 같습니다. 나머지 시간은 전부 기다리는 데 썼습니다.
데이터가 RAM과 OS 페이지 캐시보다 커지면, UPDATE 하나가 이렇게 흐릅니다. 버퍼에 없는 페이지를 디스크에서 읽고, 튜플을 고치고, 커밋 시점에 WAL을 fsync합니다. 읽기와 fsync는 둘 다 스토리지 왕복입니다. 네트워크로 붙은 EBS에서는 이 왕복 한 번이 밀리초 단위로 늘어납니다. 원문 스냅샷에서 IO:DataFileRead로 대기 중인 백엔드는 NVMe 쪽 9개, EBS 쪽 29개였습니다.
같은 디스크를 VACUUM, 체크포인트, 논리 복제의 WAL 디코딩도 씁니다. 그래서 스토리지가 느리면 쿼리만 느려지는 게 아니라 VACUUM이 밀리고, 블로트가 쌓이고, 체크포인트가 지연 스파이크를 만들고, 복제 슬롯이 커집니다.
▶ 확인할 것
CPU 사용률과 쿼리 플랜만 보면 이 병목이 보이지 않습니다. 대기 이벤트를 봐야 합니다.
[코드: sql]
SELECT wait_event_type, wait_event, count(*)
FROM pg_stat_activity
WHERE state = 'active'
GROUP BY 1, 2
ORDER BY 3 DESC;
[/코드]
IO 계열(DataFileRead, WALSync 등)이 위에 몰려 있으면 인덱스 튜닝보다 스토리지를 먼저 의심하세요. 로컬 NVMe로 옮길 때 대가도 분명합니다. 인스턴스가 사라지면 데이터도 사라지고, EBS 스냅샷도 못 씁니다. 원문은 쿼럼 동기 복제(synchronous_standby_names = 'ANY 1 (standby1, standby2)'), 가용 영역 분산, 클러스터 밖의 WAL 아카이브와 베이스 백업(WAL-G 등)을 함께 권합니다.

■ 3. S3는 합의를 대체하지 않았다 — 합의가 필요한 빈도를 바꿨다
▶ 무슨 일인가
WarpStream, turbopuffer, SlateDB처럼 ZooKeeper·etcd·Raft 클러스터 없이 S3만으로 상태를 관리하는 시스템이 늘었습니다. 그래서 "S3가 새 합의 계층"이라는 말이 돕니다. 2026-10-12에 올라온 글은 이 말이 절반만 맞다고 정리합니다.
▶ 동작 원리: 단일 키 CAS
리더 선출, 분산 락, 버전 확정은 결국 같은 문제입니다. 여러 경쟁자 중 정확히 하나만 이기고, 진 쪽은 졌다는 걸 알아야 합니다. 이걸 하려면 "확인"과 "쓰기"가 스토리지 안에서 원자적으로 묶여야 합니다. 확인한 뒤 따로 쓰면 그 사이 틈으로 다른 쓰기가 끼어듭니다.
글이 드는 예가 Delta Lake 커밋입니다. 두 Spark 클러스터가 모두 버전 9를 읽고 _delta_log/...010.json을 씁니다. 조건 없는 S3 쓰기라면 나중 쓰기가 앞 쓰기를 덮고, 오류도 나지 않습니다. 커밋 하나가 조용히 사라집니다.
S3는 2024년 8월 If-None-Match(키가 없을 때만 쓰기), 2024년 11월 If-Match(ETag가 일치할 때만 쓰기)를 넣었습니다. 이것이 단일 키 compare-and-set입니다.

[코드: python]
import boto3
from botocore.exceptions import ClientError
s3 = boto3.client("s3")
def commit(bucket: str, version: int, body: bytes) -> bool:
key = f"_log/{version:020d}.json"
try:
# 키가 아직 없을 때만 쓴다 — 동시에 두 번 오면 하나만 성공
s3.put_object(Bucket=bucket, Key=key, Body=body, IfNoneMatch="*")
return True
except ClientError as e:
if e.response["Error"]["Code"] == "PreconditionFailed":
return False # 다른 쓰기 주체가 먼저 커밋했다
raise
[/코드]
실패한 쪽은 412 PreconditionFailed를 받고, 최신 로그를 다시 읽은 뒤 다음 버전으로 재시도합니다. 표준 패턴은 "데이터는 불변 파일로 쓰고, 현재 상태를 가리키는 작은 포인터 파일 하나만 조건부로 갱신"입니다. Iceberg·Delta 커밋, SlateDB의 쓰기 주체 교체가 이 방식입니다.
▶ 왜 "대체"가 아닌가
S3는 느리고 요청마다 돈을 받습니다. 그래서 S3 기반 시스템은 쓰기를 묶어 드물게 커밋합니다. turbopuffer는 네임스페이스당 WAL 커밋을 초당 1회로 제한합니다. 반면 카프카는 파티션마다 메시지 순서(오프셋)를 초당 수천 번 정해야 합니다. 이 빈도는 S3 CAS로 감당할 수 없습니다.
그래서 WarpStream도 데이터는 S3에 쓰되 파티션 내 배치 순서는 별도 메타데이터 저장소에 커밋합니다. 카프카 디스크리스 제안(KIP-1150, KIP-1176)도 순서 결정은 S3에 넘기지 않습니다.
결정 빈도 | 예 | 적합한 도구
------------------------------------
쓰기 주체가 바뀔 때만 | 리더 선출, 쓰기 주체 교체 | S3 조건부 쓰기
초당 1회 안팎 | 매니페스트 교체, 테이블 커밋 | S3 조건부 쓰기
초당 수천 회 | 카프카 오프셋 할당 | 별도 합의 계층 + S3는 저장소
질문이 "Raft를 운영할까 ZooKeeper를 운영할까"에서 "이 시스템은 얼마나 자주 합의가 필요한가"로 바뀌었다는 게 글의 결론입니다. 배경은 비용입니다. AZ 간 전송은 방향마다 GB당 $0.01인데, 같은 리전 S3↔EC2 전송은 무료입니다.
■ 4. Uber MCP Gateway — 사내 API 수천 개를 에이전트 도구로
▶ 무슨 일인가
Uber가 2026-10-01 MCP Gateway 설계를 공개했습니다. MCP 서버 800개 이상, 도구 5000개 이상이 게이트웨이 하나에 올라가 있습니다. 수백 개 팀이 제각각 만든 MCP 연동을 하나로 모은 것입니다.
▶ 요청은 어떻게 흐르는가
구조는 컨트롤 플레인과 데이터 플레인으로 나뉩니다.

등록 경로(컨트롤 플레인)
1. AutoCrawler가 사내 IDL 레지스트리(Protobuf·Thrift)를 구독해 새 서비스와 스키마 변경을 찾습니다. 직접 만든 MCP 서버는 하트비트로 찾습니다.
2. IDL 서비스는 메서드 이름, 요청·응답 스키마, 문서 주석을 뽑습니다. LLM이 에이전트용 도구 설명을 쓰고, 스키마는 MCP 형식으로 바꿉니다.
3. 도구는 MCP Registry에 비활성 상태로 등록됩니다.
4. 서비스 소유 팀이 검토하고 켭니다. 이후 도구 설명이 바뀌면 설정 diff가 생기고 소유자 승인이 필요합니다.
5. 데이터 플레인이 주기적으로 설정을 당겨 옵니다. 재시작·재배포가 없습니다.
실행 경로(데이터 플레인)
1. 에이전트가 서비스별 /<서비스명>/mcp 엔드포인트나 Omni MCP로 들어옵니다.
2. 핸들러가 호출자(사람·서비스·에이전트)의 도구별 권한을 확인합니다.
3. 입력 JSON을 Protobuf·Thrift 바이트로 바꿉니다.
4. 서비스 메시 사이드카(Muttley)를 거쳐 기존 백엔드를 그대로 호출합니다.
5. 응답을 JSON으로 되돌리고, 개인·민감 필드는 기본으로 지우고, 요청한 필드만 남깁니다(Response Projection).
▶ 왜 중요한가
에이전트 쪽 문제는 컨텍스트입니다. Omni MCP는 discover_server → discover_tools → get_tool_schema → invoke_tool 네 단계로 필요한 것만 점점 좁혀 가져오게 합니다. 코딩 에이전트에는 Code Mode가 기본입니다. aifx CLI로 도구를 호출하고 큰 응답은 파일로 받아 grep으로 걸러 읽습니다.
가져갈 설계 결정은 세 가지입니다. 기존 API를 다시 쓰지 말고 감쌀 것. 자동 발견된 도구는 기본 비활성으로 둘 것. 도구 설명도 코드처럼 승인과 롤백을 거칠 것. 도구 설명이 바뀌면 에이전트 동작도 조용히 바뀌기 때문입니다.
■ 5. 브라우저 지문은 새로운 쿠키다
▶ 무슨 일인가
royfish.dev의 글이 해커뉴스에서 551점을 받았습니다. 쿠키를 지우고, 시크릿 창을 쓰고, 네트워크를 바꿔도 기기의 하드웨어·소프트웨어 특성은 그대로라는 내용입니다. 글은 구글이 2025년 광고 제품에서 지문 수집을 허용했다고 지적합니다.
▶ 동작 원리
권한 요청 없이 자바스크립트로 읽을 수 있는 값이 많습니다.
- Canvas: 여러 글꼴·이모지·그라데이션을 그린 뒤 픽셀을 읽습니다. 안티앨리어싱 결과가 OS·GPU·설치 글꼴마다 조금씩 다릅니다.
- WebGL: GPU 제조사·모델, 지원 확장과 한계값, 렌더링한 삼각형의 픽셀을 읽습니다.
- OfflineAudioContext: 소리를 내지 않고 톤을 컴프레서에 통과시켜 렌더링합니다. 출력 부동소수점의 미세한 차이가 오디오 엔진과 CPU를 드러냅니다.
- Intl·navigator: 시간대, 로캘, 숫자 형식, CPU 코어 수, 메모리. VPN은 IP 위치를 바꾸지만 시간대는 바꾸지 않습니다.
이 값들을 이어 붙여 SHA-256으로 해시하면 식별자 하나가 됩니다. 2명 중 1명이 가진 특성은 1비트, 128명 중 1명이면 7비트의 정보입니다. 백만 명 도시에서 한 명을 골라내는 데 약 20비트면 됩니다.
▶ 확인할 것
글은 엔진 차원의 방어만 제대로 통한다고 봅니다. Brave는 값에 잡음을 넣어 지문을 매번 바꾸고, Tor와 Mullvad Browser는 모든 사용자를 똑같이 보이게 만듭니다. API를 가로채는 확장 프로그램은 쓰는 사람이 적어 오히려 지문을 더 튀게 합니다. 서비스에 사기 방지용 지문 라이브러리를 넣었다면 수집 항목이 개인정보 처리방침에 적혀 있는지 확인하세요. EFF의 Cover Your Tracks에서 자기 브라우저가 몇 비트를 흘리는지 볼 수 있습니다.
■ 흔한 오해 세 가지
"npm 패키지 이름에 악성 버전 번호가 나왔으니 그 버전은 위험하다." 이번 Zod 건에서 악성 커밋이 붙인 버전 번호는 4.6.6이지만, npm의 4.6.6은 그 커밋과 다른 정상 릴리스였습니다. 버전 번호는 이름일 뿐입니다. 판단 근거는 tarball 내용, 무결성 해시(integrity), 출처 증명입니다. 반대로 "npm에 안 나갔으니 끝"도 틀립니다. CI 승인 게이트를 지우는 커밋이 들어왔다면 다음 시도는 배포까지 갈 수 있습니다.
"S3가 강한 일관성을 지원하니 락으로 써도 된다." S3는 2020년 12월부터 강한 읽기 일관성을 보장하지만, 그것만으로는 "읽고 확인한 다음 쓰기"의 틈을 못 막습니다. 원자적 비교-쓰기가 필요하고, 그것이 If-None-Match·If-Match입니다. 이 헤더 없이 만든 락은 동시 쓰기를 조용히 잃습니다.
"Postgres가 느리면 CPU를 늘리거나 인덱스를 더 건다." 위 벤치마크에서 트랜잭션당 CPU 작업은 양쪽 모두 약 2ms였습니다. 차이는 I/O 대기에서 났습니다. 이때 CPU를 늘리면 기다리는 프로세스만 늘어납니다. 인덱스를 더 걸면 UPDATE마다 고칠 페이지와 WAL이 늘어 I/O가 오히려 커질 수 있습니다.
■ 이번 주 정리
다섯 소식은 한 질문으로 묶입니다. 비용이 어디서 생기는가. Postgres는 CPU가 아니라 스토리지 왕복에서, S3 기반 시스템은 합의 횟수에서, AI 에이전트는 컨텍스트에 올리는 도구 정의에서 비용을 냅니다. 공급망 공격은 사람이 승인하는 단계를 노렸습니다.
■ 참고 자료
- Zod 악성 커밋 d821685 (GitHub)(https://github.com/colinhacks/zod/commit/d821685ea6732436fcdf786a81d076958471e467)
- Zod 되돌림 커밋, PR #6620 Cleanup bad commits (GitHub)(https://github.com/colinhacks/zod/commit/10dda3a3a4bc76ed0bf83e994de194587e489ef8)
- Zod 4.6.6, 악성코드에 감염됐나? (GeekNews)(https://news.hada.io/topic?id=35238)
- Postgres isn't slow, your storage is (ClickHouse Blog)(https://clickhouse.com/blog/posette-talk-recap-postgres-isnt-slow-your-storage-is)
- Postgres가 느린 게 아니라 스토리지가 느린 것이다 (GeekNews)(https://news.hada.io/topic?id=35244)
- S3는 합의를 대체하지 않았다 (원문 블로그)(https://sanspareilsmyn.github.io/blog/s3-consensus/kr/)
- S3는 합의를 대체하지 않았다 (GeekNews)(https://news.hada.io/topic?id=35233)
- Designing MCP Gateway (Uber Blog)(https://www.uber.com/in/en/blog/designing-mcp-gateway/)
- MCP Gateway - Uber의 플랫폼 설계 (GeekNews)(https://news.hada.io/topic?id=35213)
- Fingerprints are the new cookies (royfish.dev)(https://royfish.dev/fingerprints-are-the-new-cookies)
- Fingerprints are the new cookies (Hacker News 토론)(https://news.ycombinator.com/item?id=50048642)
'[News]' 카테고리의 다른 글
| DNS 루트 KSK 교체·Bun AOT·C2y — 이번 주 IT 뉴스 (0) | 2026.10.12 |
|---|---|
| 월드컵 4강 탈락 프랑스 불탄 (0) | 2026.07.20 |