본문으로 건너뛰기

국경을 넘는 데이터: FDA, EU, PIC/S, NMPA, PMDA, MFDS

📍 현재 위치: 6부 · 규모 있게 운영하기 — 26장. 플랫폼은 이미 구축되었고, 신뢰할 수 있으며, 검증되었습니다. 이제 그것은 둘 이상의 규제 당국을 동시에 만족시켜야 합니다. 단 하나의 CHO(중국 햄스터 난소 세포, Chinese Hamster Ovary cell) 단일클론항체(monoclonal antibody, mAb) 라인 — 하나의 제품을 만드는, 조작되고 마스터 뱅킹된 하나의 세포 집단 — 의 데이터가 다섯 개의 국가 의약품 규제 당국(각각 핵심 용어에 풀어 적었습니다)의 심사를 받습니다. 미국 FDA, EU, 중국 NMPA, 일본 PMDA, 그리고 한국 MFDS입니다. 이 장에서는 거주지(residency), 보관(retention), 국경 간 이전(cross-border-transfer) 규칙을 데이터로서의 정책(policy-as-data)으로 부호화하고, 강제력이 있는 규칙들 — 특히 중국의 데이터 현지화, 곧 특정 데이터가 물리적으로 나라 안에 머물러야 한다는 요구 사항 — 은 PostgreSQL의 행 수준 보안(row-level security)으로 직접 시행합니다.

쉽게 말하면

다섯 개 나라로 갈 소포를 보관하는 하나의 창고를 떠올려 보세요. 대부분의 규칙은 공유됩니다 — 모든 소포는 라벨이 붙고, 봉인되고, 여러 해 동안 보관되어야 합니다 — 그래서 창고를 다섯 개가 아니라 하나만 운영할 수 있습니다. 그러나 몇 가지 규칙은 타협 불가능하며 현지적(local)입니다. 중국은 이렇게 말합니다. "중국 앞으로 온 소포는 물리적으로 중국에 머물러야 하고, 허가 없이 외국 당국에 넘겨서는 안 된다." 그래서 바닥에 선을 긋고, 각 하역장(loading dock)에 국가 스탬프를 찍고, 문에 배선을 연결해 EU 하역장 출입증을 가진 작업자가 중국 우리(cage)의 문을 말 그대로 열 수 없게 만듭니다. 그 바닥의 선, 그 문의 잠금장치, 그리고 "N년 동안 보관한 뒤 파쇄하라"는 일정 — 이것이 바로 이 장에서 우리가 만드는 것입니다. 데이터베이스 안에서, 잠금장치는 행 수준 보안이고 일정은 보관 시계(retention clock)입니다.

이 장에서 다루는 내용

스물두 개 장 동안 우리는 하나의 플랫폼을 구축했습니다. 이 장은 순진한 설계를 무너뜨리는 질문을 던집니다. 하나의 플랫폼이 겹치지만 동일하지는 않은 규칙을 가진 다섯 개 규제 당국에 응답해야 한다면 무슨 일이 벌어질까요? 우리는 다음을 다룹니다.

  • 규제 당국 전반에 걸쳐 공유되는 것(ALCOA+ 데이터 무결성 기준선 — 레코드가 귀속 가능(Attributable)·판독 가능(Legible)·동시 기록(Contemporaneous)·원본(Original)·정확(Accurate)해야 하고, 더해서 완전(Complete)·일관(Consistent)·항구적(Enduring)·이용 가능(Available)해야 한다는 규제 당국의 기대 — 감사 추적 검토, 장기 보관)을 지역별로(per-region) 다른 것(정확한 보관 기간, 거주지, 국경 간 이전)과 분리합니다.
  • 지역별 규칙을 데이터로서의 정책(policy-as-data) — 애플리케이션, 보관 시계, 정책 엔진이 모두 단 하나의 진실의 원천에서 읽는 gov.jurisdiction_policy 테이블 — 으로 부호화합니다.
  • PostgreSQL 행 수준 보안(row-level security, RLS)으로 데이터 거주지(data residency)를 시행하여, 한 지역 출입증을 가진 세션이 다른 지역의 행을 보거나 쓸 수 없게 합니다.
  • 어떤 레코드가 자기 지역의 기간을 넘겨 노후화되었는지를 정확히 드러내는, 질의 가능한 뷰(view)로서 보관 시계(retention clock)를 운영합니다.
  • 그리고 구조적 장벽을 정직하게 마주합니다. 중국의 NMPA에 PIPL/DSL을 더한 현지화(PIPL, 개인정보 보호법; DSL, 데이터 보안법) 체제는 "하나의 플랫폼, 여러 지역"이 더 이상 소프트웨어 트릭이 아니라 배포(deployment) 결정이 되는 유일한 지점입니다 — 여기서 배포란 코드만으로 하는 변경이 아니라 소프트웨어가 실제로 어디서 어떻게 실행되는가(하나의 공유 클러스터인가, 아니면 물리적으로 중국 안에 있는 두 번째 클러스터인가)를 뜻합니다.

여기 나오는 모든 코드 조각은 실제로 테스트된 두 파일에서 나옵니다. 하나는 거버넌스 스키마 examples/platform/db/40-gov.sql로, PostgreSQL 컨테이너(동반 스택이 구동하는 격리되고 자기완결적인 PostgreSQL 인스턴스)가 처음 초기화될 때 자동으로 적용합니다 — 스키마 파일들이 든 ../db 디렉터리가 컨테이너 안의 특별한 경로 /docker-entrypoint-initdb.d(Postgres가 첫 기동 시 거기서 발견하는 모든 .sql 파일을 파일 이름 순서대로 실행하는 폴더)에 마운트(보이게 됨)되며, 그래서 파일들에 0060 번호가 매겨져 있습니다. 다른 하나는 거주지/보관 로직과 시연이 담긴 examples/chapters/23-multi-jurisdiction/residency.sql입니다. 정책 행, RLS 객체, 그리고 여러분이 보게 될 모든 출력은 노트북에서 실행 중인 PostgreSQL 17 서비스에 대해 residency.sql을 실행하여 생성됩니다.

docker exec -i -e PGPASSWORD=bioproc bioprocess-data-stack-postgres-1 \
psql -U bioproc -d bioproc -q < chapters/23-multi-jurisdiction/residency.sql

이 한 줄의 명령은 데이터베이스 컨테이너 안에서(docker exec) 실행되어, PostgreSQL의 명령줄 클라이언트(psql)를 bioproc 사용자로 bioproc 데이터베이스에 대해 열고, residency.sql 파일을 거기에 입력으로 넣습니다(< 리디렉션). 이 명령이 관할권 정책을 시딩하고, 규제 레코드 테이블과 그 행 수준 보안 정책을 생성하고, 지역별 데모 레코드를 삽입하고, 아래에 나오는 세션 질의를 실행합니다 — 그래서 여러분은 모든 행을 재현할 수 있습니다.

지형: 수렴했으되 동일하지는 않은

전 세계 시장을 위해 만들어진 CHO + Protein A 단일클론항체(그 제조 과정을 1권이 따라가는 바이오의약품)는 한 무리의 검사관에게 점검받습니다. 좋은 소식은 그 무리가 대체로 의견이 일치한다는 것입니다. 세계 대부분의 의약품 GMP 검사 기관 — 그중에 미국 FDA, EU 회원국 당국, 일본 PMDA, 한국 MFDS가 있습니다 — 은 PIC/S 참여 당국(Participating Authorities)이며, 이는 그들이 공유된 기준선 위에서 데이터 관리 기대치를 정렬했다는 뜻입니다 [7]. 그 기준선이 PIC/S PI 041로, ALCOA+ 데이터 무결성, 감사 추적 검토, 접근 제어, 보관을 공통의 검사 언어로 바꾸는 지침서입니다 [3]. MHRA의 데이터 무결성 지침 — PIC/S, WHO, OECD, EMA와 명시적으로 조화를 이룬 — 도 영국식 영어로 같은 말을 합니다 [4]. 그리고 그 모든 것 위에 ICH(국제의약품규제조화위원회, International Council for Harmonisation)가 있으니, 그 수명주기 관리 프레임워크(Q12 지침과 그것이 정의하는 "확립된 조건(established conditions)")는 FDA, EU, PMDA, MFDS 전반에 걸쳐 채택되어, 제품의 과학적 모델은 진정으로 공유됩니다 [8].

그 수렴이야말로 하나의 플랫폼을 생각이라도 해 볼 수 있는 이유입니다. 23장과 24장에서 만든 감사 추적, 해시 체인(hash chain), 전자 서명은 모든 PIC/S 회원에 대해 공유된 ALCOA+ 핵심을 한꺼번에 만족시킵니다. 우리는 감사 시스템을 다섯 개 만들지 않습니다.

나쁜 소식은 그 틈새에 있습니다. 세 가지는 수렴하기를 거부하며, 그것이 바로 이 장이 부호화해야 하는 세 가지입니다.

갈라지는 규칙미국 (FDA)EU중국 (NMPA)일본 (PMDA)한국 (MFDS)
보관 기간유효기간 경과 후 ≥1년; 통상 ~10년 [1]유효기간 경과 후 1년 또는 QP 인증 후 5년 중 더 긴 쪽 [2]~10년 (NMPA GMP)~5년 (PMDA)~5년 (MFDS)
거주지global_okin_region (GDPR 형태)in_region (의무) [6]global_okin_region
국경 간 이전검색 가능한 사본이면 OK [1]적정성(adequacy) / SCC평가 + 동의; 외국 당국으로의 인도 금지 [5]낮은 마찰동의 기반

거주지 값은 정책 테이블의 플래그 그 자체입니다. global_ok = 데이터가 자기 지역을 떠나도 됨; in_region = 물리적으로 머물러야 함. 보관 기간은 아래에서 데이터로 시딩된, 의도적으로 보수적인 어림수입니다(약 10년 = 3650일; 약 5년 = 1825일). 정확한 법정 숫자는 여러분의 검증된 SOP에 들어 있습니다. 일본과 한국은 여기서 더 짧은 5년 기간으로 시딩됩니다 — 모호하지 않고 구체적으로 — 그래서 표가 시스템의 나머지가 읽는 정책 행과 일치합니다.

보관 차이는 학술적인 것이 아닙니다. EU 규칙 — 유효기간 경과 후 1년 또는 적격자(Qualified Person, QP)의 인증 후 5년 중 더 긴 쪽 — 은 QP 인증 — 완제 배치를 판매용으로 출하하는, 법적으로 책임을 지는 적격자(Qualified Person)에 의한 EU의 승인 — 이 출하 시점에 일어나기 때문에 미국의 "유효기간 경과 후 1년"보다 엄밀히 더 길 수 있습니다 [2]. "10년"을 하드코딩하면 EU 배치를 조용히 과소 보관하게 됩니다. 그래서 보관은 스크립트 안의 상수가 아니라 데이터여야 합니다. (거주지 컬럼의 "GDPR 형태" 표기는 개인 데이터가 어디에 거주할 수 있는지를 규율하는 법인 EU의 일반 데이터 보호 규정(General Data Protection Regulation)을 가리킵니다. "적정성(adequacy) / SCC"는 그 데이터를 국외로 이동시키는 두 가지 경로입니다 — 신뢰할 수 있는 국가에 대한 적정성 결정, 또는 그런 결정이 없는 곳에서의 표준 계약 조항(Standard Contractual Clauses)입니다.)

데이터로서의 정책: 관할권 테이블

여러 규제 당국을 섬기는 가장 깔끔한 방법은 그들의 규칙을 코드 곳곳에 흩뿌리는 것을 멈추고, 모든 것이 읽는 하나의 테이블에 넣는 것입니다. 그 테이블은 examples/platform/db/40-gov.sql에서 단 한 번 정의됩니다.

-- Per-region residency/retention policy as data (Ch 26); OPA reads this too.
CREATE TABLE gov.jurisdiction_policy (
region text NOT NULL, -- US | EU | CN | JP | KR
data_class text NOT NULL, -- gmp_record | personal | telemetry
residency text NOT NULL, -- in_region | global_ok
retention_days int NOT NULL,
PRIMARY KEY (region, data_class)
);

네 개의 컬럼이 멀티 관할권 이야기 전체를 담아냅니다. regiondata_class가 키를 이루는데, 한 나라가 배치 기록개인 데이터를 다르게 규율할 수 있기 때문입니다(중국의 PIPL은 개인정보에 관한 것이고, DSL의 등급화 데이터 규칙은 "중요 데이터(important data)"에 관한 것입니다 — 같은 지역, 두 개의 클래스). residency는 데이터가 떠날 수 있는지를 결정합니다. in_region 또는 global_ok입니다. retention_days는 파쇄 시계입니다. 이것이 테이블이기 때문에, 규제 변경은 변경 관리(27장) 아래 한 행짜리 UPDATE이지 코드 릴리스가 아닙니다 — 그리고 같은 행을 애플리케이션과 보관 시계가 읽으며, 외부 정책 엔진(이 장 끝에서 논의하는 OPA 훅)이 읽도록 설계되어 있어서, 그것을 공유하는 구성 요소들이 서로 어긋날 수 없습니다.

그런 다음 장 파일이 실제 기간으로 이를 시딩합니다. examples/chapters/23-multi-jurisdiction/residency.sql입니다.

-- retention policy as data (per region + data class), seeded with real spans
INSERT INTO gov.jurisdiction_policy (region, data_class, residency, retention_days) VALUES
('US', 'gmp_record', 'global_ok', 3650), -- 21 CFR 211: >=1 yr past expiry; ~10 yr typical
('EU', 'gmp_record', 'in_region', 3650), -- Annex 11 / GMP retention
('CN', 'gmp_record', 'in_region', 3650), -- NMPA + data-localization (PIPL/DSL)
('JP', 'gmp_record', 'global_ok', 1825), -- PMDA
('KR', 'gmp_record', 'in_region', 1825) -- MFDS
ON CONFLICT (region, data_class) DO UPDATE
SET residency = EXCLUDED.residency, retention_days = EXCLUDED.retention_days;

ON CONFLICT ... DO UPDATE(업서트(upsert))는 시드를 멱등(idempotent)하게 만듭니다. 다시 실행하면 오류를 내는 대신 지역의 기간을 갱신하는데, 이것이 규칙이 바뀔 때 정확히 원하는 동작입니다. (위의 명령으로) residency.sql을 실행한 뒤 테이블을 질의하면 시스템의 나머지가 따르는 정책이 나옵니다.

region | data_class | residency | retention_days
--------+------------+-----------+----------------
CN | gmp_record | in_region | 3650
EU | gmp_record | in_region | 3650
JP | gmp_record | global_ok | 1825
KR | gmp_record | in_region | 1825
US | gmp_record | global_ok | 3650
(5 rows)

이 기간들은 모든 조항을 다투기 위해서가 아니라 방어 가능하도록 의도적으로 보수적인 어림수입니다(3650일 ≈ 10년; 1825 ≈ 5년). 이 장이 가르치는 핵심은 메커니즘이며, 실제 숫자는 여러분의 검증된 SOP에 들어 있습니다. 중요한 것은 그것들이 가정되는 것이 아니라 조회된다(looked up)는 점입니다.

구조에 의한 거주지: 행 수준 보안

보관은 시계입니다. 거주지는 벽입니다 — 그리고 벽은 세션이 물리적으로 그것을 넘어설 수 없을 때에만 진짜입니다. PostgreSQL의 행 수준 보안(row-level security)은 데이터베이스 안에 그 벽을 세워 줍니다. 테이블에 부착된 정책이 모든 질의를 필터링하여, 세션(인증된 하나의 데이터베이스 연결)은 USING 표현식이 허용하는 행만 보게 되며, 데이터베이스는 애플리케이션이 기억하기를 믿는 대신 SELECT, INSERT, UPDATE, DELETE에 대해 그것을 시행합니다 [9]. 다음은 examples/chapters/23-multi-jurisdiction/residency.sql에서 가져온 규제 레코드 테이블과 그 정책입니다.

-- regulated records carry the region that owns them
CREATE TABLE IF NOT EXISTS gov.regulated_record (
record_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
region text NOT NULL, -- US | EU | CN | JP | KR
batch_id text,
data_class text NOT NULL DEFAULT 'gmp_record',
created_ts timestamptz NOT NULL DEFAULT now(),
payload jsonb NOT NULL DEFAULT '{}'
);

ALTER TABLE gov.regulated_record ENABLE ROW LEVEL SECURITY;
ALTER TABLE gov.regulated_record FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS region_isolation ON gov.regulated_record;
CREATE POLICY region_isolation ON gov.regulated_record
USING (region = current_setting('app.region', true)
OR current_setting('app.region', true) = 'GLOBAL');

두 가지 설계 선택이 실질적인 무게를 짊어집니다. FORCE ROW LEVEL SECURITY는 정책이 테이블 소유자에게도 적용되게 합니다 — 이것이 없으면 테이블을 소유한 사용자는 면제되어, 벽에 곧장 구멍을 뚫게 됩니다. 그리고 USING 표현식은 current_setting('app.region', true)를 기준으로 삼는데, 이는 애플리케이션이 연결 시점에 SET app.region = 'EU'로 설정하는 세션별 변수입니다. true 인자는 "설정되어 있지 않으면 오류를 내지 말고 NULL을 반환하라"는 뜻이어서 — 자기 지역을 선언하는 것을 잊은 연결은 아무것도 보지 못하며, 이것이 안전한 기본값입니다. 'GLOBAL' 탈출구(escape hatch)는 지역을 가로질러 검토할 정당한 필요가 있는 특권 감사/QA 역할을 위한 것입니다.

파일의 주석이 짚어 주는 솔직한 미묘함이 하나 있습니다. PostgreSQL 슈퍼유저, 또는 BYPASSRLS로 생성된 어떤 역할이든 행 보안을 완전히 무시합니다. RLS는 여러분의 애플리케이션이 최소 권한 역할로 연결할 때에만 여러분을 보호합니다. 그래서 데모는 정확히 그런 것을 만듭니다 — app_rls, 즉 NOSUPERUSER NOBYPASSRLS 역할 — 그리고 그것으로 실행합니다. 감사 장과 같은 정직함입니다. 이 통제는 애플리케이션과 일반 사용자를 구속하지만, 플랫폼 자신의 관리자를 이기지는 못하며, 그 틈은 SQL이 아니라 운영상의 직무 분리(segregation of duties)로 메워집니다.

규제 레코드의 해부: 벽이 읽는 필드들

이 장의 모든 것 — 거주지, 보관, 조인, 파기 날짜 — 은 gov.regulated_record한 행에서 읽힙니다. 이 행이야말로 중심 산출물이므로, 각 필드의 이름을 하나씩 부르며 천천히 살펴볼 가치가 있습니다. 행이 지닌 region은 RLS 벽이 필터링하는 거주지 키이면서 동시에 보관 시계가 따라가는 조인 키입니다. 다음은 의도적으로 오래된 CN 레코드(record_id 6, BATCH-2015-099)를 필드별로 해부하고, 그것이 매칭되는 gov.jurisdiction_policy 행, 그리고 그 조인이 도출하는 purge_after 날짜를 함께 보여 줍니다.

gov.regulated_record의 한 행을 해부한 신원 카드: record_id는 GENERATED ALWAYS AS IDENTITY bigint 기본 키이고, region CN은 행 수준 보안 정책이 필터링하는 거주지 키이며, batch_id BATCH-2015-099는 GMP 배치이고, data_class는 gmp_record로 기본값이 정해지며 정책 조회의 절반 키이고, created_ts 2015-07-02는 보관 앵커이며, payload는 빈 jsonb이다. 보라색 패널은 region과 data_class로 조인된 매칭 gov.jurisdiction_policy 행을 보여 주며 residency in_region과 retention_days 3650을 준다. 초록색 패널은 created_ts에 retention_days를 더해 계산한, 도출된 purge_after 2025-06-29를 보여 준다.

gov.regulated_record의 한 행: region은 벽이 읽는 거주지 키이고, data_classregion이 매칭되는 gov.jurisdiction_policy 행(in_region, 3650일)으로 조인되며, purge_after는 보관 시계가 도출하는 날짜 — 여기서는 2025-06-29로 이미 과거이고, 그래서 이 행이 표시됩니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

행을 위에서 아래로 읽어 보세요. record_idGENERATED ALWAYS AS IDENTITY입니다 — 애플리케이션이 아니라 데이터베이스가 발급하므로 기본 키는 위조되거나 재사용될 수 없습니다. region은 하중을 짊어지는 필드입니다. current_setting('app.region', true)가 비교되는 바로 그 값이어서, 행의 지역이 곧 세션이 그 행을 볼 수 있는지를 결정합니다. batch_id는 GMP 배치 레코드에 대한 사람이 읽는 연결이고, data_classgmp_record로 기본값이 정해지며 region과 함께 정책 테이블로 들어가는 두 부분 키를 이룹니다 — 그래서 한 나라가 배치 기록과 개인 데이터를 서로 다른 시계로 규율할 수 있습니다. created_ts는 보관 앵커(timestamptz, 기본값 now())이고, payload는 레코드가 실제로 무엇인지를 담는 jsonb 본문입니다. 이 필드들 중 어느 하나도 장식이 아닙니다. region을 빼면 벽이 필터링할 것이 없고, created_ts를 빼면 시계가 셀 기준이 없습니다.

region_isolation 정책의 해부: USING 절 하나로 된 벽

그 필드들을 읽는 벽 자체도 하나의 해부 가능한 객체입니다. region_isolationCREATE POLICY로, 그 전체 동작이 하나의 USING 표현식과 두 개의 ALTER TABLE 플래그 안에 들어 있습니다 — 통째로 읽을 만큼 작지만, 틀리면 각 절이 실제 실패 양상으로 이어질 만큼 중대합니다.

region_isolation 행 수준 보안 정책을 해부한 신원 카드: ALTER TABLE ENABLE ROW LEVEL SECURITY는 정책을 켜고, ALTER TABLE FORCE ROW LEVEL SECURITY는 테이블 소유자에게까지 적용하며, 세션 키는 app.region의 current_setting으로 true(missing_ok) 인자가 오류 대신 NULL을 반환하여 설정되지 않은 세션은 아무것도 보지 못하고, app.region이 GLOBAL과 같으면 지역 간 감사 탈출구이다. 초록색 패널은 region이 세션 지역과 같거나 OR 세션이 GLOBAL과 같다는 USING 표현식을 보여 주며, 이는 읽기 검사이자 FORCE RLS 아래에서 암묵적 쓰기 검사로 쓰인다. 장미색 주의 패널은 슈퍼유저나 BYPASSRLS 역할이 행 보안을 완전히 무시한다는 점, 그래서 데모가 최소 권한 app_rls 역할로 연결한다는 점, 그리고 RLS가 백업이나 복제가 CN 바이트를 국외로 복사하는 것을 막을 수 없다는 점을 경고한다.

거주지 벽 전체가 하나의 정책입니다: ENABLE이 그것을 켜고, FORCE가 소유자에게까지 확장하며, USING 표현식은 읽기와 쓰기 양쪽에서 검사되고, GLOBAL은 감사 탈출구이며, 장미색 패널은 정직한 경계입니다 — RLS는 애플리케이션을 구속하지, DBA나 백업이나 복제 스트림을 구속하지 않습니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

초록색 블록이 정책의 심장입니다. 같은 USING 표현식이 이중 역할을 합니다. SELECT에서는 읽기 필터(세션과 region이 맞는 행만 반환)이고, FORCE ROW LEVEL SECURITY 아래에서는 암묵적 쓰기 검사(새 행이 표현식을 통과하지 못하는 INSERTUPDATE는 거부)이기도 합니다. 그 단일한 재사용이 거주지가 나가는 길뿐 아니라 들어오는 길에도 버티는 이유입니다 — 아래의 SNEAKY-001 삽입이 실패하는 것도 정확히 이 때문입니다. current_settingtrue 인자는 조용한 안전장치입니다. "설정되어 있지 않으면 NULL을 반환하고 오류를 내지 말라"는 뜻이어서, SET app.region을 잊은 연결은 열린 채로 오류를 내는 대신 어떤 행에도 맞지 않습니다. 그리고 장미색 패널은 경계를 분명히 말합니다. 이것은 애플리케이션과 일반 사용자가 위반할 수 없는 정책이지만, BYPASSRLS 역할, 슈퍼유저, 또는 통제되지 않은 백업 스트림은 여전히 위반할 수 있습니다 — 이것이 중국이 SQL이 아니라 배포 차원의 답을 강제하는 바로 그 이유입니다.

다섯 개의 색깔 지역 밴드(US, EU, CN, JP, KR)를 가진 단일 PostgreSQL regulated_record 테이블. EU, CN, GLOBAL 출입증을 단 세 개의 애플리케이션 세션이 행 수준 보안 게이트를 통해 연결되며, 게이트는 각 세션을 자기 지역의 행으로만 걸러낸다. 보관 시계 화살표가 테이블을 훑으며 노후화된 CN 레코드를 파기 대상으로 표시한다. CN 밴드는 in-region / no foreign handover라고 표시된, 더 두껍게 잠긴 경계 안에 그려져 있다.

하나의 테이블, 여러 규제 당국: 행 수준 보안은 각 세션을 그것이 출입증을 단 지역으로 걸러내고(GLOBAL은 지역 간 감사 역할), 보관 시계는 지역별로 노후화된 레코드를 훑으며, 중국의 in_region 밴드는 소프트웨어만으로는 완화할 수 없는 단단한 거주지 경계 뒤에 자리합니다. 저자가 AI의 도움을 받아 직접 제작한 그림입니다.

세션의 생애 주기: 출입증을 달고 들어가고, 거부당하기

메커니즘은 그것이 작동하는 것을 볼 수 있을 때에만 설득력이 있고, 그것을 보는 가장 깔끔한 방법은 하나의 세션을 그 생애 전체에 걸쳐 따라가는 것입니다. (1) 최소 권한 역할이 되고, (2) 지역을 선언하여 출입증을 달고, (3) 읽으면 — 자기 지역만 보이고, (4) 지역을 바꾸면 보이는 것이 달라지고, (5) 벽을 가로질러 행을 밀반입하려 하면 — 거부당합니다. 지역마다 레코드 하나씩(여기에 의도적으로 오래된 CN 레코드 BATCH-2015-099 하나를 더하되, 보관 계산이 검증 가능하도록 명시적인 created_ts2015-07-02로 부여)을 시딩한 뒤, 우리는 app_rls로 연결하고, 지역을 선언하고, 살펴봅니다. EU 세션입니다.

SET ROLE app_rls;
SET app.region = 'EU';
SELECT record_id, region, batch_id FROM gov.regulated_record ORDER BY record_id;
record_id | region | batch_id
-----------+--------+----------------
2 | EU | BATCH-2026-002
(1 row)

한 행 — EU의 것입니다. US, CN, JP, KR 행은 "UI에 의해 숨겨진" 것이 아닙니다. 이 세션의 SQL 입장에서는 그것들이 존재하지 않습니다. 같은 역할을 중국 세션으로 전환하면 보이는 것이 완전히 바뀝니다.

SET app.region = 'CN';
SELECT record_id, region, batch_id FROM gov.regulated_record ORDER BY record_id;
record_id | region | batch_id
-----------+--------+----------------
3 | CN | BATCH-2026-003
6 | CN | BATCH-2015-099
(2 rows)

이제 진짜 시험입니다 — 세션이 벽을 가로질러 데이터를 밀반입할 수 있을까요? 중국 출입증을 단 세션이 EU 레코드를 쓰려고 시도합니다.

SET app.region = 'CN';
INSERT INTO gov.regulated_record (region, batch_id) VALUES ('EU','SNEAKY-001');
ERROR: new row violates row-level security policy for table "regulated_record"

데이터베이스가 거부합니다. FORCE ROW LEVEL SECURITY 아래에서 USING 표현식은 암묵적인 쓰기 검사로 적용되므로, 세션은 자기 지역에 맞는 행만 삽입할 수 있습니다 — 거주지는 나가는 길에만이 아니라 들어오는 길에도 시행됩니다. 이것이 여러분이 문서화하는 정책과 시스템이 위반할 수 없는 정책의 차이입니다.

보관 시계

거주지는 데이터가 어디에 사는지를 결정하고, 보관은 데이터가 언제 죽는지를 결정합니다. 기간이 데이터이기 때문에, 시계는 그저 gov.jurisdiction_policy에 대한 조인(join)일 뿐입니다(각 레코드를 공유 키로 자기 지역의 정책 행에 매칭하여, 하나의 질의가 두 테이블을 함께 읽습니다). 장 파일은 이를 뷰로 정의합니다. examples/chapters/23-multi-jurisdiction/residency.sql입니다.

-- find records past their region's retention window (the clock job acts on these)
CREATE OR REPLACE VIEW gov.v_retention_due AS
SELECT r.record_id, r.region, r.batch_id, r.data_class, r.created_ts, p.retention_days,
(r.created_ts + (p.retention_days || ' days')::interval)::date AS purge_after
FROM gov.regulated_record r
JOIN gov.jurisdiction_policy p
ON p.region = r.region AND p.data_class = r.data_class
WHERE now() > r.created_ts + (p.retention_days || ' days')::interval;

이 뷰는 각 레코드의 purge_after 날짜 — 생성 타임스탬프에 지역의 retention_days를 간격(interval)으로 렌더링하여 더한 뒤, 출력이 달력의 하루로 읽히도록 순수 date로 캐스팅한 것 — 를 계산하고, 그 날짜가 이미 과거인 레코드만 반환합니다. 이를 질의하면 스케줄된 작업(크론으로 구동되는 psql 호출이나 서비스)이 처리해야 할 레코드가 정확히 드러납니다.

record_id | region | batch_id | retention_days | purge_after
-----------+--------+----------------+----------------+-------------
6 | CN | BATCH-2015-099 | 3650 | 2025-06-29
(1 row)

2015년 CN 레코드는 10년 기간을 넘겨 노후화되어(created_ts 2015-07-02 + 3650일 = 파기 가능일 2025-06-29, 오늘은 2026년 중반) 표시되었으며, 다른 모든 레코드는 여전히 자기 지역의 기간 안에 있어 올바르게 그대로 둡니다. 결정적으로, 뷰는 할 일을 명명하지만 그것을 하지는 않습니다 — 그리고 이것은 의도적입니다. GMP 보관은 "언제까지 보관하라"이지 "시계가 치는 순간 자동 삭제하라"가 아닙니다. 규제 레코드의 실제 삭제는 QA 승인 아래 변경 관리로 진행되며, 삭제 행위 자체가 감사되고 해시 체인으로 묶인 이벤트입니다(23장). 시계의 역할은 후보 집합을 명시적이고 검토 가능하게 만드는 것이지, 레코드를 조용히 파괴하는 것이 아닙니다.

고속 히스토리안(historian)이 만들어내는 규모에서, 파기를 실행하는 효율적인 방법은 행 단위 DELETE가 아니라 PostgreSQL 선언적 범위 파티셔닝(range partitioning)입니다. 기저의 시계열을 시간으로(그리고 거주지가 요구하는 곳에서는 지역으로) 파티셔닝하면, 만료된 데이터의 한 구간을 퇴역시키는 일이 비용이 큰 스캔-앤-삭제가 아니라 전체 파티션의 거의 즉각적인 DETACH/DROP이 됩니다 [10]. 뷰는 무엇이 처리 대상인지 알려 주고, 파티셔닝은 그것을 어떻게 저렴하게 떨어내는지입니다.

멀티 관할권 거버넌스 설계의 데이터 흐름 그래프. gov.jurisdiction_policy 테이블은 모든 것이 읽어 가는 왼쪽 허브이다. read by라고 표시된 화살표가 app.region을 설정하는 App session 박스로 향하고, 그것은 RLS region_isolation이라고 표시된 간선을 통해 gov.regulated_record를 공급한다. 정책 테이블과 regulated_record 테이블은 둘 다 gov.v_retention_due(기간을 넘긴 레코드)로 조인되며, 그것은 reviewed, QA sign-off를 거쳐 장미색 Partition DETACH 또는 DROP 감사된 삭제 박스로 흘러간다. 보라색 점선 간선 designed to be read by가 정책 테이블에서 아래쪽 예시적 OPA / Rego 국경 간 결정 박스를 가리킨다.

왜 중요한가

멀티 관할권을 틀리면 그 실패 양상은 정반대 방향으로 비쌉니다. 과소 보관 — QP 인증 시계가 더 긴 보관을 요구했는데 미국의 10년 시점에 EU 배치 기록을 파쇄 — 하면, 검사관이 여전히 요청할 수 있는 GMP 기록을 파괴한 것입니다 [2]. 과잉 공유 — 중국 거주 레코드를 미국 클라우드 지역으로 동기화하게 두거나, 통상적인 요청 중에 외국 당국에 넘기게 두는 것 — 하면, 중국의 PIPL과 DSL을 위반한 것이며, 이는 GMP 지적사항보다 한 자릿수 더 큰 제재를 동반합니다 [5][6].

구조적 교훈은 공유되는 80%와 갈라지는 20%가 서로 다른 처치를 원한다는 것입니다. 공유된 ALCOA+ 기준선은 이미 구축된 감사·서명 장치로 한 번, 깊이 있게 해결하는 것이 최선입니다 — 사본 다섯 개에는 가치가 없습니다. 갈라지는 규칙은 데이터 더하기 벽으로 해결하는 것이 최선입니다. 어떤 구성 요소든 읽을 수 있는 정책 테이블, 그리고 애플리케이션 버그와 무관하게 데이터베이스가 시행하는 RLS 경계입니다. 규칙을 이렇게 부호화하면 "우리는 그것에 대한 절차가 있다"가 "시스템은 달리 할 수 없다"로 바뀌며, 이것이 바인더와 통제의 차이입니다.

실제 현장에서는

여기 정직한 결산이 있습니다. 이 장의 대부분은 순수 오픈 소스에서 진정으로 작동하며, 잘 작동합니다. PostgreSQL RLS는 성숙하고 실전에서 검증된 통제이며 — 멀티테넌트 SaaS 제품들이 의지하는 바로 그 메커니즘 — 그것을 데이터 거주지에 쓰는 것은 그 설계 안에 정면으로 들어맞습니다 [9]. 데이터로서의 정책 더하기 보관 뷰는 단순하고, 감사 가능하며, 버전 관리됩니다. ALCOA+ 기준선을 공유하는 수렴된 PIC/S 회원인 FDA, EU, PMDA, MFDS의 경우 — 지역별 정책 행을 가진 하나의 검증된 플랫폼은 방어 가능한 구조입니다 [3][7].

중국은 "하나의 플랫폼"이 어떤 SQL 정책도 오를 수 없는 벽에 부딪히는 곳입니다. PIPL은 개인정보가 중국을 떠나기 전에 보안 평가, 인증, 또는 표준 계약과 별도의 동의를 요구하며 — 그리고 못 박아 말하자면 — PRC 승인 없이 중국에 저장된 데이터를 외국의 사법 또는 법 집행 당국에 넘기는 것을 금지합니다 [5]. DSL은 그 위에 자체적인 역외 반출 관리 통제를 가진 등급화 "중요 데이터" 체제를 겹쳐 놓습니다 [6]. RLS는 질의가 CN 행을 읽는 것은 막을 수 있지만, 여러분의 백업, 복제, 재해 복구가 바이트를 다른 나라로 복사하는 것은 막을 수 없습니다 — 그리고 그 사본이 곧 위반입니다. 현실적인 해답은 구조적입니다. 스택의 별도 중국 내 배포(separate in-China deployment)(자체 PostgreSQL, 객체 스토어, 백업을 모두 물리적으로 중국 안에) — 비식별화되거나 집약된, 이전 승인을 받은 데이터만 국경을 넘게 하는 것입니다. residency = 'in_region' 플래그는 그 결정의 트리거이고, 결정 자체는 두 번째 클러스터입니다. 순수 OSS가 요구 사항을 사라지게 하지는 않습니다 — 그저 경계를 명시적으로 만들고 같은 소프트웨어를 양쪽에 이식 가능하게 할 뿐입니다.

벽으로 충분하지 않을 때: in_region 뒤에 있는 명명된 이전 체제

"별도 중국 내 배포"가 지나치게 조심스러운 해석인지, 아니면 실제 법적 요구 사항인지 물어볼 만합니다. 정직한 답은, 중국의 역외 이전 체제가 막연한 분위기가 아니라 구체적이고, 명명되어 있으며, 등급화되어 있다는 것입니다. 중국 국가인터넷정보판공실(CAC)의 국경 간 데이터 흐름을 촉진하고 규율하는 규정(Provisions on Promoting and Regulating Cross-Border Data Flows)은 2024년 3월 22일 발효되어, 어떤 바이트라도 나라를 떠나기 전에 특정 이전이 세 가지 법정 메커니즘 중 어느 것을 통과해야 하는지를 결정하는 임계값을 정합니다 [11].

한 해 동안 이전되는 개인정보의 규모이전이 통과해야 하는 메커니즘
10만 명 미만 (비민감)면제 — 메커니즘 불필요
10만 ~ 100만 명 (비민감)CAC 표준 계약 또는 인증
100만 명 초과, 또는 임의의 "중요 데이터"의무 CAC 보안 평가

2024년 규정은 사실 이전의 2022/2023년 조치를 완화했습니다 — 면제 상한을 올리고 누적 규모 산정 기간을 2년에서 1년으로 줄였습니다 — 그러나 그 완화 이후에도 두 가지 사실이 별도 클러스터를 GMP 플랫폼의 현실적 기본값으로 만듭니다. 첫째, "중요 데이터"에는 규모 하한이 없습니다. 중요 데이터로 분류된 단 하나의 레코드도 건수와 무관하게 완전한 CAC 보안 평가를 촉발하며 [11], 여러 해에 걸친 배치 이력은 규제 당국이 나중에 그렇게 지정할 법한 바로 그런 데이터셋입니다. 둘째, 평가는 일회성 양식이 아닙니다. 만료되며(이제 3년 후), 갱신해야 하고, PIPL의 별도 동의 및 외국 인도 금지 규칙 위에 얹힙니다 [5]. 비용이 "모든 국경 간 동기화마다 국가 보안 평가를 무기한 통과하고 또 통과하라"인 통제는 핫 패스에서 설계로 빼내는 것입니다 — residency = 'in_region'과 두 번째 클러스터가 정확히 그렇게 합니다. 정책 행은 법이 아닙니다. 그것은 이 행이 그 법의 규율을 받는다고 말하는 플래그이며, 그래서 아키텍처가 이전을 통째로 우회합니다.

여기는 또한 국경 간 결정이 단일 SQL USING 절을 넘어 자라나는 곳입니다. "이 레코드가 CN에서 US 분석 워크스페이스로 이동해도 되는가?"는 지역, 데이터 클래스, 동의, 이전 메커니즘을 함께 따집니다 — 행 필터보다 풍부한 정책입니다. 자연스러운 다음 단계는 그 결정을 Open Policy Agent(OPA) 같은 전용 정책 엔진으로 외부화하여, 데이터베이스와 엔진이 하나의 진실의 원천을 공유하도록 바로 그 gov.jurisdiction_policy 테이블을 읽게 하는 것입니다. OPA는 Apache-2.0 라이선스로 배포되므로 채택에 라이선스 함정이 없습니다. 스키마는 이를 위해 의도적으로 설계되었습니다 — 40-gov.sql의 주석은 정책 테이블이 OPA에 의해 "도(too)" 읽히도록 의도되었다고 적습니다 — 하지만, 분명히 하자면, 이 장의 코드는 OPA 서비스나 Rego 정책을 함께 배포하지 않습니다. 동반 compose.yamlopa 컨테이너는 없고 리포지토리에 .rego 파일도 없습니다. 테이블은 정직하고 현재형의 기초이며, 그것을 소비할 Rego 정책은 스택의 실행 부분이 아니라 예시적 확장으로 남겨 두었습니다.

같은 정책을 그래프로: RDF 형상이자 역량 질문으로서의 거주지

관계형 벽은 거주지를 시행하는 한 가지 방법입니다. 같은 산출물이 이 책의 시맨틱스와 디지털 스레드 장이 짓고 Book 4가 수명주기 둘레로 형식화하는 시맨틱 스택에서 어떻게 읽히는지를 보는 것은 의미가 있습니다. 두 관점은 경쟁이 아니라 상보적이기 때문입니다. gov.regulated_record의 한 행은 이미 트리플의 묶음입니다. bp:BATCH-2015-099 bp:region "CN", bp:BATCH-2015-099 a bp:RegulatedRecord, bp:BATCH-2015-099 bp:dataClass "gmp_record"입니다. RLS USING 절이 부호화하는 거주지 규칙은 그래프 용어로 SHACL(형상 제약 언어, Shapes Constraint Language) 형상입니다 — Book 4가 출하 결정에 쓰는 바로 그 닫힌 세계 게이트입니다. bp:RegulatedRecord를 표적으로 하여 정확히 하나의 bp:region을 통제된 집합에서 요구하고, in_region 클래스에 대해서는 그 지역 밖의 bp:storedAt 위치를 금하는 sh:NodeShape입니다.

# illustrative residency shape — the RLS USING clause, expressed closed-world
bp:ResidencyShape a sh:NodeShape ;
sh:targetClass bp:RegulatedRecord ;
sh:property [ sh:path bp:region ; sh:minCount 1 ; sh:maxCount 1 ;
sh:in ( "US" "EU" "CN" "JP" "KR" ) ] ;
sh:property [ sh:path bp:storedAt ; sh:minCount 1 ;
sh:message "in_region record stored outside its jurisdiction" ] .

보관 시계에도 그래프 쌍둥이가 있습니다. gov.v_retention_due 조인은 SPARQL 역량 질문입니다 — Book 4가 23개 질문에 걸쳐 돌리는 바로 그 테스트로서의 요구 사항 규율입니다 — "어떤 bp:RegulatedRecord 개체가 created_ts에 그 지역의 retention_days를 더한 값이 이미 과거인가?", 기대 답(여기서는 2015년 CN 로트 하나)이 고정된 ASK/SELECT여서, 그것을 표시하기를 멈추는 모델은 눈에 띄게 실패합니다. 그리고 그 뷰가 이끄는 감사된 삭제는 정확히 PROV-O 이벤트입니다 — prov:wasGeneratedBy가 그 파기를 prov:Agent(QA 승인자)와 prov:atTime을 지닌 prov:Activity에 묶으며, 출처를 다루는 W3C 표준 어휘가 "우리는 변경 관리 아래 그것을 삭제했다"를 바인더의 한 줄이 아니라 질의 가능한 사실로 바꿉니다. region 필드가 세 가지 역할을 한다는 점 — 거주지 키, 보관 조인 키, 그리고 이제 RDF 속성 — 이 관계형 관점과 그래프 관점이 결코 어긋나지 않는 이유입니다. 둘은 같은 속성을 읽되, 하나는 컬럼으로, 하나는 술어로 읽습니다.

거주지가 모델에 하는 일: 훈련 세트에는 국경이 있다

거주지는 저장 제약일 뿐 아니라, 모델이 학습할 수 있는 모든 데이터셋의 경계를 조용히 다시 긋습니다 — 이것이 이 장이 Book 5의 ML 파이프라인과 닿는 지점입니다. 출하 CQA를 예측하거나 라만 스펙트럼에서 역가를 복원하도록 훈련된 모델은 얻을 수 있는 가장 크고 대표성 있는 훈련 세트를 원합니다 — 그러나 residency = 'in_region'은 CN 배치 기록이 위의 CAC 메커니즘을 통과하지 않고는 US나 EU 훈련 말뭉치에 합법적으로 합쳐질 수 없다는 뜻입니다. 현실적 귀결은 법적 분할과 통계적 분할이 함께 지켜져야 한다는 것입니다. 누출을 막는 바로 그 배치 그룹 분할(한 배치의 행이 훈련과 시험 양쪽에 들어가지 못하게 하는 것)이, 모델이 함께 훈련할 권리가 없었던 사이트들을 가로질러 일반화해야 할 때 지역별 분할(leave-one-region-out) 규율이 됩니다. EU와 US 로트로만 적합한 뒤 CN 공정에 대해 채점된 모델은, 표류와 적용 영역의 언어로 말하면, 자신이 보정된 입력 지역인 적용 영역(applicability domain) 밖에서 외삽하고 있는 것입니다 — 그리고 거주지 플래그는 유용하게도 그 경계의 사전적(a-priori) 표식입니다. 모델이 본 적 없는 지역을 지닌 레코드는 사후적 거리 점검이 아니라 구성상 영역 밖입니다. 모델 계보(model lineage)가 고리를 닫습니다 — 레코드를 규율하는 regiondata_class는 그로부터 도출된 어떤 특징과도 함께 이동해야 하며, 그래서 감사는 배포된 모델이 볼 법적 권리가 없었던 데이터로 결코 훈련되지 않았음을 입증할 수 있습니다. 이는 Book 5가 모델 자체에 적용하는 변경 관리되고 잠그고-나서-재학습하는 거버넌스와 같습니다.

두 번째 클러스터는 복사-붙여넣기가 아니라 기술 이전이다

제조 독자를 위한 마지막 정직한 메모입니다. "별도 중국 내 배포"를 세우는 일은 배포 잡무가 아니라 GMP 의미의 기술 이전(technology transfer)입니다. 같은 스택을 구동하는 두 번째 물리적 사이트는 독립적으로 자격검증되어야 합니다 — 그 PostgreSQL, 객체 스토어, 백업이 GAMP 5와 CSV-에서-CSA 모델 아래 IQ/OQ/PQ(설치 적격성·운영 적격성·성능 적격성 — 시스템이 올바르게 설치되었고, 사양대로 운영되며, 사용 중 재현 가능하게 수행됨을 입증하는 문서화된 증거)를 거쳐야 하며 — 단 하나의 CN 배치 기록이 그 위에 살기 전에 그래야 합니다. 규제 당국은 소스 트리가 아니라 사이트를 검사하기 때문입니다. 데이터로서의 정책 설계가 여기서 빛을 발하는 것은 바로 스키마가 이식 가능하기 때문입니다. 동일한 40-gov.sqlresidency.sql이 두 클러스터를 모두 초기화하므로, 중국 클러스터는 글로벌 클러스터와 코드가 아니라 시딩된 region 행과 물리적 위치에서만 다릅니다 — 이것이 바로 두 사이트, 다중 관할권 검증을 유지보수해야 할 분기(fork)가 아니라 다룰 만한 것으로 유지하는 속성입니다.

핵심 용어

  • 관할권 / 규제 당국(Jurisdiction / regulator) — 레코드가 그 GMP 및 데이터 규칙을 만족시켜야 하는 법적 권한 기관(FDA, EU 회원국, NMPA, PMDA, MFDS).
  • PIC/S — 의약품 실사 상호협력 기구(Pharmaceutical Inspection Co-operation Scheme); 그 PI 041 지침이 참여 검사 기관 전반에 걸쳐 데이터 무결성 기대치를 조화시키며, 그래서 하나의 ALCOA+ 기준선이 그중 다수를 섬깁니다.
  • ALCOA+ — 신뢰할 수 있는 GMP 레코드를 정의하는 데이터 무결성 속성들(귀속 가능(Attributable), 판독 가능(Legible), 동시 기록(Contemporaneous), 원본(Original), 정확(Accurate), 더해서 완전(Complete), 일관(Consistent), 항구적(Enduring), 이용 가능(Available)); PIC/S PI 041이 공통의 검사 언어로 바꾸는 공유 기준선.
  • 데이터로서의 정책(Policy-as-data) — 규제 규칙(거주지, 보관)을 하드코딩하는 대신, 앱과 보관 시계가 읽는(그리고 OPA 같은 외부 정책 엔진이 읽도록 설계된) gov.jurisdiction_policy의 행으로 부호화하는 것.
  • 데이터 거주지(Data residency) — 특정 데이터가 물리적으로 한 지역 안에 머물러야 한다는 요구 사항; 여기서는 행 수준 보안으로, 그리고 중국에 대해서는 별도의 지역 내 배포로 시행됩니다.
  • 행 수준 보안(Row-level security, RLS)CREATE POLICYUSING 표현식이 세션 컨텍스트(app.region)로 모든 질의를 필터링하는 PostgreSQL 기능; FORCE ROW LEVEL SECURITY는 이를 테이블 소유자에게까지 확장합니다.
  • BYPASSRLS / 슈퍼유저 — RLS를 완전히 무시하는 역할; 애플리케이션이 최소 권한 역할(app_rls)로 연결해야 하고 직무 분리가 여전히 필요한 이유.
  • 보관 기간(Retention period) — 레코드가 보관되어야 하는 기간(retention_days)으로 지역마다 다릅니다; EU의 "유효기간 경과 후 1년 또는 QP 인증 후 5년 중 더 긴 쪽"은 미국 기간을 초과할 수 있습니다.
  • 보관 시계(Retention clock) — 자동 파괴가 아니라 검토된 삭제를 위해, 기간을 넘긴 레코드를 드러내는 gov.v_retention_due 뷰.
  • PIPL / DSL — 중국의 개인정보 보호법(Personal Information Protection Law)과 데이터 보안법(Data Security Law); 둘이 함께 데이터 현지화와 국경 간 이전 통제를 부과하여 별도의 중국 내 배포를 강제합니다.
  • 국경 간 이전(Cross-border transfer) — 관할권 사이에서 데이터를 이동하는 것; 일부 지역에서는 자유롭게 허용되지만 중국에 대해서는 평가/동의/승인으로 통제됩니다.
  • gov.regulated_record — 이 장의 중심 산출물; 지역이 소유한 GMP 레코드 한 건당 한 행으로, region(거주지 키), data_class, created_ts(보관 앵커), jsonb payload를 담으며, region + data_class가 그것을 규율하는 정책 행으로 조인됩니다.
  • USING 표현식 / 암묵적 쓰기 검사region_isolation의 단 하나의 술어로, PostgreSQL이 읽기 필터로도 적용하고 FORCE ROW LEVEL SECURITY 아래에서는 모든 INSERT/UPDATE에 대한 검사로도 적용하여, 거주지가 나가는 길뿐 아니라 들어오는 길에도 시행됩니다.
  • CAC 보안 평가 / 표준 계약 — 중국 개인정보나 "중요 데이터"의 이전이 중국을 떠나기 전에 통과해야 하는 등급화 메커니즘(2024년 국경 간 데이터 흐름 규정에 따른); 별도 중국 내 배포를 기본값으로 만드는 비용입니다.
  • SHACL 거주지 형상 — RLS USING 절의 닫힌 세계 그래프 쌍둥이; bp:RegulatedRecord를 표적으로 정확히 하나의 집합 내 bp:region을 요구하고 in_region 레코드가 그 관할권 밖에 저장되는 것을 금하는 sh:NodeShape로, Book 4가 출하 결정에 쓰는 바로 그 게이트입니다.
  • 역량 질문 / PROV-O — 기대 답이 고정된 SPARQL 질문으로 읽히는 보관 시계 조인(테스트로서의 요구 사항), 그리고 prov:Agentprov:atTime을 지닌 PROV-O prov:Activity로 모델링된 감사된 삭제 — 그래서 출처가 바인더의 한 줄이 아니라 질의 가능한 사실이 됩니다.
  • 지역별 분할 / 적용 영역 — 거주지는 배치 그룹 분할을 지역 분할로 바꿉니다. 사이트를 가로질러 합법적으로 함께 훈련된 적이 없는 모델은, 본 적 없는 지역에 대해 채점될 때 자신의 적용 영역 밖에서 외삽하는 것이며, region 플래그가 그 영역 밖 경계를 사전적으로 표시합니다.
  • IQ/OQ/PQ(두 번째 클러스터 자격검증) — 설치 적격성·운영 적격성·성능 적격성; 별도 중국 내 배포가 복사-붙여넣기가 아니라 자격검증된 사이트로의 진짜 GMP 기술 이전임을 입증하는 GAMP 5 / CSA 증거로, 데이터로서의 정책 스키마가 이식 가능하기에 다룰 만해집니다.

다음 이야기

거주지와 보관은 플랫폼 자체가 가만히 멈춰 있다고 가정합니다 — 그러나 가동 중인 플랜트에서는 레시피가 개정되고, 스키드(skid)가 교체되고, 데이터 형식이 진화하며, 그 모든 변경은 우리가 방금 만든 감사 추적, 계보(genealogy), 지역 태깅을 깨뜨리지 않고 일어나야 합니다. 27장 — 변경 관리: 공정 변경, 설비 교체, 스키마 진화에서는 변경을 일급 데이터 문제로 다룹니다. 유효일자 기반(effective-dated) 레시피 버전, 로트 계보를 보존하면서 바이오리액터나 계측기를 교체하기, 그리고 변경 관리 아래 가역적이고 검증된 마이그레이션으로 바뀐 데이터 형식을 이전하기 — 그래서 플랫폼은 신뢰 사슬의 단 한 고리도 잃지 않으면서 앞으로 나아갈 수 있습니다.