분리
코드가 아니라 데이터베이스가 지키는 경계
모든 CRM이 멀티테넌트를 표방해요. 주장은 공짜지만, 중요한 건 어느 계층이 방어선을 지키느냐예요. 답이 "우리 애플리케이션이 항상 올바른 필터를 추가합니다"라면, 그 보장은 쿼리를 작성한 개발자 중 가장 피곤했던 사람의 수준만큼밖에 못 미쳐요.
iKonekt는 나중에 소급 적용된 게 아니라 첫 마이그레이션부터 멀티테넌트였어요. 자랑처럼 들리지만 사실은 고백이에요: 소급 적용은 설득력 있게 하기가 거의 불가능하고, 시도해 본 사람마다 하나씩 사연이 있거든요.
분리는 데이터베이스 안에 살아요. 스물여섯 개 테이블 모두 자신이 속한 테넌트를 함께 담고 있고, 이를 검증하는 정책은 모든 읽기와 쓰기에서 애플리케이션 완전히 아래에서 실행돼요. 경계를 잊은 쿼리는 남의 고객을 돌려주지 않아요. 아무것도 돌려주지 않아요.
강제 적용되는 행 수준 보안
관례도 아니고, 코드 리뷰 규칙도 아니에요. 데이터베이스가 거부해요. 일흔두 개의 정책은 생성된 게 아니라 하나하나 직접 작성했어요 — 생성된 정책은 아무도 읽지 않는 정책이니까요.
복합 키
하위 레코드는 자신의 테넌트 그리고 상위 레코드를 한 쌍으로 함께 참조해요. 다른 회사의 잘못된 식별자는 해석되지 않아요 — 애초에 유효한 참조가 아니에요.
기본 테넌트는 단 한 번도 없음
범위 없는 요청이 조용히 테넌트 0에 대한 요청으로 변하지 않아요. 데이터베이스에 닿기 전에 예외를 던져요. 깜빡 끄기 쉬운 "범위 없음" 모드 같은 것도 없어요.
스스로 드러나지 못했을 버그
테넌시 작업을 진행하는 동안 대부분의 실수는 시끄러웠어요. 두 회사에 같은 고객 도메인을 주면 데이터베이스가 오류 코드와 스택 트레이스와 함께 즉시 거부하고, 점심 먹기 전에 고칠 수 있어요.
하나는 거부하지 않았어요. 편집 가능한 프롬프트 재정의 테이블이 프롬프트의 종류만으로 키가 잡혀 있었어요 — 종류 그리고 테넌트가 아니라요. 두 번째 회사가 자기 사본을 편집하면 완전히 유효한 SQL을 통해, 오류도 경고도 실패하는 테스트도 없이 조용히 첫 번째 회사의 것을 덮어썼을 거예요. 키를 하나 변경한 뒤 남은 모든 고유 인덱스를 하나씩 읽어 확인하면서, 시끄러운 것들에도 짝이 있으리라 의심해 발견했어요.
말씀드리는 이유는 이 작업의 정직한 모습이기 때문이에요. 분리는 켜는 기능이 아니라, 스키마의 지루한 부분을 "여기 어딘가엔 나와 다른 생각을 하는 게 있겠지"라는 전제로 읽는 습관이에요.
기록을 위해 밝히면, 테스트는 총 942개이고, 변경이 있을 때마다 처음부터 다시 만든 데이터베이스로 실행돼요. 설계가 영리하다는 걸 증명하려는 게 아니에요. 이 설계의 실패 방식은 조용하기 때문에, 조용한 실패를 대신해 외쳐 줄 누군가가 필요하니까요.
분리가 평범한 사람에게 주는 것
- 한 그룹이 하나의 설치로 여러 회사를 운영할 수 있고, 한 회사의 영업 담당자는 다른 회사의 고객을 절대 볼 수 없어요 — 정책이 아니라 구조상으로요.
- 파트너 프로그램, 프롬프트 세트, 고객 소유권 주장: 모두 회사별로 범위가 지정되어, 한 테넌트의 설정이 모두의 것이 될 수 없어요.
- 삭제 요청은 공유된 더미를 넘나드는 게 아니라 하나의 경계 안에서 처리되고, 그래서 정직하게 답변할 수 있어요.
- 그리고 아무도 이걸 기억할 필요가 없어요. 그게 핵심이에요 — 기억하는 데 의존하는 보장은 보장이 아니에요.
데이터베이스 이야기가 아닌 한 줄 더
패밀리의 모든 형제 서비스는 자신만의 이름 있는 자격 증명을 가지고 있어요. 아무것도 공유 자격 증명으로 통신하지 않으니, 어떤 서비스도 다른 서비스인 것처럼 실수로 동작할 수 없어요 — 예전에 공유 토큰을 써 봤고 그게 마음에 들지 않았던 경험에서 직접 나온 작은 규율이에요.
데이터베이스 규칙과 같은 본능이에요. 잘못된 일을 만류하는 게 아니라 불가능하게 만들면, 경계심 대신 고객에게 주의를 쓸 수 있어요.
- 126개 테이블모든 테이블이 자신의 테넌트를 담고 있고, 검증은 강제로 켜져 있어요.
- 272개 정책하나하나 직접 작성했기에, 각 정책을 사람이 읽었어요.
- 3테스트 942개변경이 있을 때마다 처음부터 다시 만든 데이터베이스로 실행해요.
- 4기본 테넌트 0개범위 없는 요청은 데이터에 닿지 않아요.
라이브 빌드에서 측정

"저희 애플리케이션은 항상 회사별로 필터링합니다."
그 문장에서 '항상'이라는 말이 사실 많은 것을 떠안고 있어요.
경계를 데이터베이스 안쪽으로 내려보내면, WHERE 절을 빠뜨려도 다른 회사의 고객 데이터 대신 아무것도 반환되지 않아요. 어색한 소개보다는 빈 화면이 더 나은 실패예요.
여러 브랜드를 운영하는 그룹이라면 →