Skip to main content

B2B 플랫폼에서 멀티테넌시를 설계하는 방법 , DVRP와 POP의 격리 구조

4개국 법인과 다수 고객사의 데이터를 하나의 인프라에서 격리하면서 비용 효율과 보안을 동시에 달성하는 것 , 이 글은 REINDEERS의 DVRP(물류 배차 최적화)와 POP(생산 운영 플랫폼)에 적용된 멀티테넌시 아키텍처를 다룬다. 왜 공유 DB + Row-Level Security 모델을 선택했는지, 테넌트 컨텍스트가 시스템 전체에 어떻게 전파되는지, 다층 방어 구조가 어떤 원칙으로 설계되었는지, 그리고 4개국 규제 환경에서의 컴플라이언스 전략까지 정리한다.

한 가지를 먼저 덧붙인다. DVRP와 POP는 지금 AI로 전환되는 구조 위에 올라가고 있다. 사람은 전략과 방향을 결정하고, 실제 업무는 조직도 안에 등록된 AI Agent가 실행한다. 직원을 등록할 때 "사람", "AI Agent", "로봇" 중 선택할 수 있는 구조다. 이 구조에서 멀티테넌시는 "화면이 섞이지 않게 하는" 수준을 훨씬 뛰어넘는 의미를 갖는다. Agent가 사람 대신 데이터를 읽고 실행 결정을 내리기 때문에, 테넌트 격리가 깨지는 순간 "다른 회사의 구매 Agent가 우리 회사 데이터로 발주를 내렸다" 같은 재앙이 발생할 수 있다. 멀티테넌시는 AI 전환의 전제 조건이다.

멀티테넌시 3중 방어 구조
LAYER 1
API Gateway · 요청 인증
JWT 검증 · 테넌트 식별 · 역할 결정
LAYER 2
애플리케이션 · 테넌트 컨텍스트 주입
미들웨어가 모든 쿼리에 tenant_id 바인딩
LAYER 3
데이터베이스 · Row-Level Security
tenant_id 불일치 행은 DB가 직접 차단
한 레이어가 뚫려도 다음 레이어에서 차단. 실수로 인한 데이터 유출 방지.

왜 멀티테넌시가 중요한가

REINDEERS는 두 개의 SaaS 플랫폼을 운영한다. DVRP는 물류 회사를 위한 배차 최적화 시스템이고, POP는 제조사를 위한 생산 운영 플랫폼(MES+ERP+WMS)이다. 두 플랫폼 모두 다수의 기업 고객이 동시에 쓰는 B2B SaaS라는 공통점을 갖는다.

물류 회사 A의 트럭 배차 데이터와 회사 B의 배차 데이터는 절대 섞일 수 없다. 제조사 C의 생산 일정이 제조사 D에게 노출되는 것은 보안 사고이자 비즈니스 리스크다. 재고 수량, 거래처 정보, 원가 데이터, 배송 경로 , 모든 것이 고객사(테넌트) 단위로 완벽하게 격리되어야 한다.

격리가 실패하면 발생하는 일
  • 경쟁사 데이터 노출 , 물류 회사 A가 경쟁사 B의 배차 경로와 운임 단가를 본다
  • 재무 데이터 유출 , 제조사 C의 원가 구조가 거래처 D에게 노출된다
  • 규제 위반 , 태국 PDPA, 한국 PIPA, 중국 PIPL, 말레이시아 PDPA 위반 리스크
  • 신뢰 상실 , B2B SaaS에서 데이터 격리 사고는 플랫폼 자체의 존속을 위협한다

이론적으로는 해법이 간단하다 , 고객사마다 별도의 서버와 DB를 돌리면 된다. 하지만 현실은 다르다. 100개 고객사마다 별도 DB 인스턴스와 서버를 운영하면 인프라 비용만으로 연간 수십억 원이 필요하다. 운영 인력도 비례해서 늘어난다. 스키마 변경이나 버전 업데이트를 고객사별로 따로 배포해야 한다면, 개발 속도는 고객 수에 반비례해 느려진다.

그래서 멀티테넌시가 필요하다. 인프라는 공유하면서 데이터는 완벽하게 격리하는 구조. REINDEERS는 공유 DB + 공유 스키마 + Row-Level Security 모델을 채택하고, 애플리케이션 미들웨어와 DB 정책의 이중 방어 구조로 격리를 담보한다.

격리 모델 선택 , 왜 공유 DB + RLS인가

멀티테넌시 구현 방식은 크게 세 가지로 나뉜다. 각각의 장단점을 DVRP와 POP의 운영 조건에 맞춰 평가했다.

옵션 구조 격리 수준 비용 운영 복잡도
A 테넌트별 별도 DB 최상 매우 높음 매우 높음
B 공유 DB, 테넌트별 스키마 중간 중간
C 공유 DB + 공유 스키마 + RLS 상 (설계 의존) 낮음 낮음

옵션 A , 테넌트별 별도 DB는 격리 수준이 가장 높다. 물리적으로 데이터가 분리되므로 교차 접근 자체가 불가능하다. 금융처럼 규제가 극단적으로 강한 도메인에서는 합리적이지만, 100개 고객사를 유치한 순간부터 100개의 DB 인스턴스를 관리해야 한다. 성장 중인 SaaS에게는 확장성의 벽이 된다.

옵션 B , 공유 DB, 테넌트별 스키마는 중간 지점이다. 격리 수준은 좋지만, 테넌트 수가 늘어날수록 스키마 수도 같이 늘어난다. 수천 개 스키마를 관리할 때 커넥션 풀링과 마이그레이션이 복잡해진다.

옵션 C , 공유 DB + 공유 스키마 + Row-Level Security가 우리의 선택이다. 모든 테넌트의 데이터가 같은 테이블에 저장되지만, 모든 테이블에 tenant_id 컬럼이 있고, 이 값을 기준으로 행(row) 수준에서 접근을 제어한다. 스키마 변경은 한 번만 하면 모든 테넌트에 적용되고, 새 고객사 온보딩은 테넌트 레코드 하나만 추가하면 끝난다.

이 선택에는 하나의 전제가 있다 , RLS가 올바르게 구현되면 옵션 A와 동등한 격리 수준을 달성할 수 있다. 이 전제를 지키기 위해 REINDEERS는 애플리케이션 레이어와 DB 레이어에서 이중으로 격리를 강제한다.

설계 원칙
  • 단일 소스 , tenant_id는 JWT에서 추출. 클라이언트가 직접 지정할 수 없음
  • 이중 방어 , 애플리케이션 미들웨어 + DB 정책이 독립적으로 동작
  • 기본 차단 , tenant_id가 없는 쿼리는 실행 자체가 불가능
  • 감사 추적 , 모든 데이터 접근에 테넌트 컨텍스트가 함께 로깅됨

테넌트 컨텍스트의 전파

멀티테넌시 시스템에서 가장 중요한 것은 "지금 이 요청이 어느 테넌트의 것인가"를 정확하게 판별하고, 그 정보를 시스템 전체에 전파하는 것이다. 단 한 곳에서라도 전파가 끊어지면 격리가 무너진다.

1단계 , 인증과 테넌트 추출. 모든 API 요청은 JWT 토큰을 포함한다. 서버는 토큰을 검증하고, 페이로드에서 tenant_id를 꺼낸다. 이 값은 사용자가 속한 고객사를 식별하는 유일한 키이며, 클라이언트가 헤더나 쿼리스트링으로 지정하거나 변경할 수 없다. 반드시 토큰 서명 검증을 통과한 값만 인정한다.

2단계 , 컨텍스트 주입. 추출된 tenant_id는 요청 컨텍스트 객체에 주입되고, 이후 모든 서비스 레이어와 데이터 접근 레이어에서 참조된다. 개발자가 작성하는 어떤 함수도 tenant_id를 직접 인자로 받지 않는다. 컨텍스트에서 꺼내는 방식으로 통일된다.

3단계 , 쿼리 필터 자동 적용. ORM 인터셉터가 모든 SELECT/UPDATE/DELETE 쿼리에 WHERE tenant_id = :current_tenant 조건을 자동으로 덧붙인다. INSERT의 경우 tenant_id 값을 자동 삽입한다. 개발자가 tenant_id를 명시하지 않아도 시스템이 강제한다.

// Tenant Context Middleware (의사코드)

function tenant_middleware(request, next):

  // 1. JWT에서 tenant_id 추출
  payload = verify_jwt(request.headers.authorization)

  if not payload or not payload.tenant_id:
    return response(401, "Tenant context required")

  // 2. 요청 컨텍스트에 테넌트 정보 주입
  request.tenant_context = {
    tenant_id: payload.tenant_id,
    timezone:  payload.timezone,
    currency:  payload.currency,
    locale:   payload.locale
  }

  // 3. DB 세션 변수 설정 (RLS용)
  db.set_session_var("app.current_tenant", payload.tenant_id)

  // 4. 감사 로그 컨텍스트 설정
  audit.set_context({
    tenant_id:  payload.tenant_id,
    user_id:   payload.user_id,
    request_id: request.headers["X-Request-ID"],
    ip:        request.remote_addr
  })

  return next()

4단계 , 캐시와 메시지 큐. 격리는 DB에만 적용되지 않는다. 분산 캐시의 모든 키는 테넌트 네임스페이스를 포함한다(tenant:{tenant_id}:{resource}:{resource_id}). 메시지 큐의 모든 메시지는 tenant_id를 메타데이터로 포함한다. 네임스페이스가 없는 캐시 키는 거부되고, 테넌트 태그가 없는 메시지는 드랍된다.

원칙은 하나다 , "tenant_id가 없는 데이터 접근은 시스템 어디에서도 불가능하다." 미들웨어에서 추출, DB에서 강제, 캐시에서 네임스페이스화, 메시지 큐에서 태깅. 네 개 레이어 모두에서 테넌트 컨텍스트가 같은 값으로 전파된다.

데이터 격리 보장 , 다층 방어 구조

멀티테넌시에서 가장 위험한 시나리오는 "개발자의 단일 실수로 인한 교차 접근"이다. 설계가 아무리 좋아도 개발자가 WHERE 절에서 tenant_id를 빼먹는 순간 다른 테넌트 데이터가 노출된다. REINDEERS는 이 문제를 세 가지 독립적인 레이어로 방어한다. 각 레이어가 독립이라는 것이 핵심이다 , 한 레이어의 버그가 다른 레이어로 전파되지 않는다.

L1 , 애플리케이션 레이어: ORM 인터셉터

첫 번째 방어선은 애플리케이션 레벨이다. ORM 인터셉터가 모든 쿼리를 가로채서 tenant_id 필터를 자동 주입한다. 개발자는 tenant_id를 명시적으로 쓰지 않아도 된다 , 쓸 수도 없고, 쓸 필요도 없다.

// Query Interceptor (의사코드)

class TenantQueryInterceptor:

  function before_query(query, context):
    tenant_id = context.tenant_context.tenant_id

    if not tenant_id:
      raise Error("Tenant context missing , query blocked")

    if query.type in ["SELECT", "UPDATE", "DELETE"]:
      query.add_where("tenant_id = :tenant", tenant_id)

    if query.type == "INSERT":
      query.add_column("tenant_id", tenant_id)

    return query

// 개발자가 작성하는 코드
orders = db.query("SELECT * FROM orders WHERE status = 'pending'")

// 실제 실행되는 쿼리 (인터셉터가 자동 변환)
// SELECT * FROM orders WHERE status = 'pending' AND tenant_id = :tenant

L2 , 데이터베이스 레이어: Row-Level Security

두 번째 방어선은 DB 자체다. 설령 애플리케이션 레이어의 인터셉터를 우회하더라도(버그, 직접 SQL 실행 등), DB의 RLS 정책이 독립적으로 격리를 강제한다.

-- Row-Level Security 정책 (개념)

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory ENABLE ROW LEVEL SECURITY;
ALTER TABLE routes ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation_select ON orders
  FOR SELECT
  USING (tenant_id = current_setting('app.current_tenant'));

CREATE POLICY tenant_isolation_insert ON orders
  FOR INSERT
  WITH CHECK (tenant_id = current_setting('app.current_tenant'));

CREATE POLICY tenant_isolation_update ON orders
  FOR UPDATE
  USING (tenant_id = current_setting('app.current_tenant'))
  WITH CHECK (tenant_id = current_setting('app.current_tenant'));

ALTER TABLE orders FORCE ROW LEVEL SECURITY;

이 구조의 핵심은 current_setting('app.current_tenant')이다. 미들웨어가 매 요청마다 세션 변수로 tenant_id를 세팅하면, DB는 해당 세션에서 발생하는 모든 쿼리에 RLS 정책을 적용한다. 개발자가 SELECT * FROM orders를 실행해도, RLS에 의해 현재 테넌트의 주문만 반환된다.

L3 , API 레이어: 엔드포인트별 검증

세 번째 방어선은 API 레벨이다. URL 경로에 포함된 리소스 ID가 현재 테넌트에 속하는지 검증한다. 예를 들어 GET /api/orders/ord_12345 요청이 들어오면, 해당 주문이 현재 테넌트의 것인지 확인한 뒤에만 응답한다. L1/L2를 거쳤더라도 L3에서 다시 한 번 검증된다.

세 레이어는 독립적으로 동작한다. L1이 실패해도 L2가 막고, L2를 우회해도 L3가 잡는다. 어느 하나의 레이어에 버그가 발생해도 나머지 두 레이어가 격리를 유지한다. Cross-tenant JOIN이 구조적으로 불가능하므로, SQL Injection이나 ORM 우회 공격이 발생해도 현재 테넌트 범위를 벗어날 수 없다.

모든 데이터 접근은 다음 정보와 함께 로깅된다: tenant_id, user_id, action, resource_type, resource_id, timestamp, ip_address, request_id. 사후 감사와 이상 접근 탐지에 사용된다.

테넌트별 설정과 커스터마이징

REINDEERS는 4개국(태국, 한국, 중국, 말레이시아)에서 운영되며, 각 국가의 고객사마다 서로 다른 시간대, 통화, 언어, 업무 규칙을 갖는다.

설정 항목 예시 값 적용 범위
Timezone Asia/Bangkok, Asia/Seoul, Asia/Shanghai 시간 표시, 스케줄, 리포트 기준시
Currency THB, KRW, CNY, MYR, USD 금액 표시, 정산, 인보이스
Language TH, KO, ZH, EN, MS UI, 알림, 문서 생성 언어
Enabled Modules MES only, WMS only, Full POP, DVRP 기능 접근 범위, 메뉴 구성
Workflow Templates 커스텀 승인 흐름, 알림 규칙 업무 자동화 로직
Branding 로고, 브랜드 색상, 이메일 헤더 화이트라벨 UI, 문서 출력

이 설정들은 tenant_configurations 테이블에 JSON 형태로 저장되고, 테넌트 컨텍스트 주입 시점에 함께 로드된다. 캐시 레이어에서 테넌트 설정을 유지해 매 요청마다 DB를 조회하지 않는다.

모듈 활성화가 중요한 이유는 POP를 쓰는 고객사가 획일적이지 않기 때문이다. 일부는 MES만, 일부는 WMS만, 일부는 풀 패키지가 필요하다. 비활성 모듈의 API 엔드포인트에 접근하면 403 Forbidden이 반환된다. "우리 계약엔 없는 기능인데 실수로 들어간다" 같은 상황이 구조적으로 불가능하다.

브랜딩 설정은 화이트라벨 전략과 연결된다. 각 고객사가 자사 로고·색상으로 POP나 DVRP를 쓸 수 있으므로, 현장 직원은 REINDEERS 플랫폼을 "자사 시스템"으로 인식한다. 도입 저항을 줄이고 플랫폼 의존도를 높이는 전략적 설계이기도 하다.

성능 최적화 , 공유 인프라에서의 효율

멀티테넌시의 성능 설계에서 가장 중요한 것은 "노이지 네이버(Noisy Neighbor) 방지"다. 한 테넌트의 대량 작업이 다른 테넌트의 서비스 품질을 떨어뜨리지 않아야 한다.

커넥션 풀링. 모든 테넌트가 같은 DB를 공유하므로 커넥션 관리가 핵심이다. 커넥션 풀러를 앞에 두고 수백 개의 동시 요청을 제한된 수의 실제 DB 커넥션으로 처리한다. 트랜잭션 단위 풀링이므로 트랜잭션이 완료되면 커넥션이 즉시 풀로 돌아간다.

테넌트 인식 캐시 전략. 캐시는 테넌트별로 완전히 분리된 네임스페이스를 쓴다. 한 테넌트의 데이터 변경이 다른 테넌트의 캐시에 영향을 주지 않는다. 동시에 테넌트별 캐시 쿼터를 둬서 한 테넌트가 메모리를 독점하지 못하게 한다. TTL은 리소스 유형별로 차등 적용한다(재고 5분, 생산 계획 30분, 테넌트 설정 1시간).

복합 인덱스 (tenant_id, primary_key). 공유 DB 모델에서 쿼리 성능의 핵심은 인덱스 설계다. 모든 핵심 테이블에 (tenant_id, id) 복합 인덱스를 만든다. RLS 정책과 애플리케이션 쿼리 모두 tenant_id를 첫 필터로 쓰므로, 이 인덱스가 풀 테이블 스캔을 원천 차단한다.

-- 복합 인덱스 설계 (예시)

CREATE INDEX idx_orders_tenant        ON orders (tenant_id, id);
CREATE INDEX idx_orders_tenant_status ON orders (tenant_id, status, created_at);
CREATE INDEX idx_inventory_tenant     ON inventory (tenant_id, warehouse_id, sku);
CREATE INDEX idx_routes_tenant        ON routes (tenant_id, date, driver_id);
CREATE INDEX idx_production_tenant    ON production_plans (tenant_id, plan_date);

공정 큐(Fair Queue). 백그라운드 작업(배차 최적화, 리포트 생성, 데이터 동기화)은 테넌트별 공정 큐로 스케줄링한다. 한 테넌트가 1,000건의 배차 최적화를 동시에 요청해도, 다른 테넌트의 작업이 뒤로 밀리지 않도록 라운드 로빈으로 처리한다. 테넌트별 동시 실행 작업 수(concurrency limit)를 설정해 리소스 독점을 구조적으로 막는다.

DVRP 고유의 격리 요구사항

DVRP(Dynamic Vehicle Routing Problem)는 물류 배차 최적화 플랫폼이다. 일반적인 SaaS 멀티테넌시 위에 물류 도메인의 고유 요구가 추가된다.

차량/기사 데이터 격리. 물류사 A의 트럭 20대와 기사 30명은 물류사 B에게 절대 보이지 않는다. 차량 위치, 적재량, 운행 이력 모두 테넌트 범위 내에서만 조회된다.

경로 최적화 격리. 배차 최적화 알고리즘은 반드시 단일 테넌트의 차량과 배송 건만 입력으로 받는다. 교차 테넌트 차량 공유나 경로 병합은 불가능하다. 최적화 연산은 테넌트별로 독립 실행된다.

GPS 실시간 데이터 스트림. GPS 위치 데이터는 초 단위로 쏟아진다. 모든 GPS 레코드에 tenant_id가 태깅되며, 실시간 대시보드는 현재 테넌트의 차량만 지도에 표시한다. WebSocket 연결도 테넌트별로 채널이 분리된다.

대시보드 필터링. 차량 위치 맵, 배송 현황, 기사 상태 등 모든 위젯은 서버 사이드에서 tenant_id 필터가 적용된 데이터만 전송한다. 클라이언트 사이드 필터링에 의존하지 않는다. "클라이언트가 필터를 깜빡해서 다른 회사 데이터가 보이는" 상황이 원천 불가능하다.

특히 주의해야 하는 시나리오는 GPS 데이터의 시계열 처리다. 좌표가 초 단위로 대량 유입되며, 이를 실시간 저장·조회하는 과정에서 격리가 유지되어야 한다. 시계열 전용 확장 기능을 쓰더라도, 모든 하이퍼테이블(시계열 파티션 테이블)에 tenant_id 기반 RLS를 동일하게 적용한다.

경로 최적화 엔진은 CPU 집약적 연산이다. 한 물류사가 500건의 배송에 대한 최적화를 요청하면 수십 초가 걸릴 수 있다. 이때 다른 테넌트의 요청이 대기열에서 밀리지 않도록 앞서 설명한 공정 큐가 적용된다. 테넌트별 동시 최적화 요청 수를 제한하고, 대형 요청은 분할(chunking)해서 처리한다.

보안 감사와 컴플라이언스

REINDEERS는 4개국에서 운영된다. 각국의 개인정보 보호법과 데이터 규제가 다르며, 멀티테넌시 시스템은 이 모든 규제를 동시에 충족해야 한다.

국가 법률 핵심 요구사항 대응
태국 PDPA 동의 기반 수집, 데이터 이동권 테넌트별 동의 관리, 데이터 내보내기
한국 PIPA 최소 수집, 파기 의무 보유 기간 정책, 자동 파기
중국 PIPL 데이터 현지화, 국외 이전 제한 중국 리전 별도 운영
말레이시아 PDPA 통지 의무, 보안 조치 암호화, 접근 통제

격리 검증 자동화

데이터 격리가 실제로 동작하는지 검증하는 것은 설계만큼 중요하다. REINDEERS는 자동화 테스트로 격리를 주기적으로 검증한다.

// 격리 검증 테스트 (의사코드)

function test_tenant_isolation():

  // 테넌트 A로 데이터 생성
  set_tenant_context("tenant_A")
  order_A = create_order({ product: "Widget", qty: 100 })

  // 테넌트 B로 전환
  set_tenant_context("tenant_B")

  // A의 데이터 조회 시도 → 반드시 null
  result = get_order(order_A.id)
  assert(result == null)

  // B의 전체 조회에 A의 데이터가 섞이지 않아야 함
  all_orders = list_orders()
  for order in all_orders:
    assert(order.tenant_id == "tenant_B")

  // Raw SQL로 RLS 우회 시도 → 반드시 실패
  direct = db.raw_query(
    "SELECT * FROM orders WHERE id = :id",
    { id: order_A.id }
  )
  assert(direct.rows.length == 0)

이 테스트는 CI/CD 파이프라인에서 매 배포마다 실행되며, 실패하면 배포가 중단된다. 주간 단위로는 프로덕션 환경에서도 격리 검증 스크립트를 돌려, 실제 운영 데이터 수준에서 격리가 유지되는지 확인한다.

테넌트별 데이터 보유/삭제 정책

각국 규제에 따라 데이터 보유 기간이 다르다. 한국은 5년, 태국은 동의 철회 시 즉시 삭제, 중국은 데이터 현지화가 필수다. 테넌트 설정에 data_retention_policy를 포함해서 국가별/테넌트별 보유 기간을 관리한다. 만료된 데이터는 배치로 자동 삭제되며, 삭제 기록 자체는 감사 로그에 영구 보관된다.

특히 중요한 것은 테넌트 단위 완전 삭제(right to erasure)다. 고객사가 서비스를 해지하면 해당 테넌트의 모든 데이터를 완전히 삭제할 수 있어야 한다. tenant_id가 모든 테이블에 있으므로 DELETE FROM table WHERE tenant_id = :id로 한 번에 삭제할 수 있다. 이것이 공유 DB 모델에서도 GDPR/PDPA 수준의 삭제권을 충족할 수 있는 구조적 이유다.

Multi-region 인프라

4개국 운영에서 중국은 특수하다. PIPL에 의해 중국 내 수집 데이터는 원칙적으로 중국 내 서버에 저장되어야 한다. REINDEERS는 중국 테넌트의 데이터를 중국 리전의 별도 DB 인스턴스에 저장하고, 나머지 국가는 APAC 허브 리전을 중심으로 운영한다. 멀티테넌시 구조 자체는 동일하지만, 테넌트 생성 시 국가 규제에 따라 데이터 저장 리전이 자동으로 결정된다.

Multi-region 전략
  • APAC 허브 리전 , 태국, 한국, 말레이시아 테넌트
  • 중국 리전 , 중국 테넌트 전용 (PIPL 준수)
  • 리전 간 동기화 , 글로벌 공통 데이터(환율, 제품 마스터 등)만 복제
  • 리전 결정 , 테넌트 생성 시 국가 코드 기반 자동 라우팅

마무리 , 멀티테넌시는 SaaS의 뼈대다

멀티테넌시는 SaaS 비즈니스의 기술적 뼈대다. 데이터 격리가 실패하면 플랫폼의 존재 이유가 사라진다. 반대로 격리를 위해 고객사마다 별도 인프라를 운영하면, 스타트업은 인프라 비용에 매몰되어 제품 개발에 집중할 수 없다.

REINDEERS의 선택은 명확하다. 공유 DB + Row-Level Security 모델로 비용 효율을 확보하면서, 애플리케이션 미들웨어 + DB RLS + API 검증의 3중 방어 구조로 격리 수준을 담보한다. 테넌트 컨텍스트가 JWT에서 추출되어 시스템 전체(DB, 캐시, 메시지 큐, 감사 로그)에 전파되므로, tenant_id 없는 데이터 접근은 구조적으로 불가능하다.

이 구조 위에서 DVRP는 물류 회사별 차량·경로·GPS 데이터를 격리하고, POP는 제조사별 생산·재고·재무 데이터를 격리한다. 4개국의 서로 다른 규제(PDPA, PIPA, PIPL)를 테넌트 설정과 multi-region 인프라로 동시에 충족한다.

멀티테넌시 설계에서 가장 중요한 것은 코드가 아니라 원칙이다 , "기본은 차단, 예외만 허용". tenant_id가 없으면 아무것도 실행되지 않는다. 이 원칙이 모든 레이어에 일관되게 적용될 때, 공유 DB 모델에서도 별도 DB와 동등한 격리 수준을 달성할 수 있다.

REINDEERS는 이 멀티테넌시 인프라 위에서 DVRP와 POP를 운영하고 있으며, 테넌트 수가 1,000+을 넘어서면 테이블 파티셔닝과 리전 확장을 단계적으로 적용할 예정이다. SaaS 플랫폼의 성장은 결국 멀티테넌시 아키텍처의 확장성에 의해 결정된다.

AI Agent 시대에 멀티테넌시가 갖는 새로운 의미

이 글의 마지막을 조금 다른 각도에서 덧붙인다. 멀티테넌시는 지금까지 "사람 사용자가 다른 테넌트의 데이터를 보지 못하게 하는 것"에 초점이 맞춰져 있었다. 앞으로는 여기에 한 층이 더 붙는다. 조직도 안에 등록된 AI Agent , 구매·물류·생산·재무 Agent , 도 같은 격리 규칙을 따라야 한다.

REINDEERS Works에서 AI Agent는 일반 사용자처럼 JWT로 인증되고, 그 JWT에 소속 테넌트와 권한이 박혀 있다. 사람 직원과 동일한 RLS 규칙, 동일한 감사 로그, 동일한 월 예산 한도를 적용받는다. 구매 Agent가 A사 소속이면 A사의 거래 데이터만 읽고, 쓰기를 실행하면 A사의 승인 체계를 거친다. 이것은 "Agent를 위한 새 규칙"이 아니라, 기존 멀티테넌시 뼈대 위에서 자동으로 강제되는 일관된 규칙이다.

AI Agent 진화 4단계(Tool → Assistant → Agent Team → Autonomous Operator)로 보면 이 원칙이 왜 중요한지 더 분명해진다. Tool 단계에서는 사람이 매번 Agent를 감독하므로 격리 실수가 즉시 잡힌다. Agent Team 단계로 가면 여러 부서 Agent가 밤 사이 자율적으로 업무를 실행한다. 이때 격리가 한 번이라도 우회되면, 다음 날 아침 사람이 발견할 때는 이미 돌이킬 수 없는 상태다. 그래서 Tool 단계부터 RLS, 캐시 네임스페이스, 큐 태깅, 감사 로그를 전부 "Agent도 사람처럼 이 규칙을 지킨다"는 전제로 설계했다. 멀티테넌시 아키텍처는 AI 전환의 안전벨트다. 이 벨트가 없으면 다음 단계로 넘어갈 수 없다.

관련 글

Popular posts from this blog

Reindeers Workflow: B2B 파트너 업무 효율과 자동화를 위한 워크플로우 플랫폼

B2B 국제 무역에서 하나의 거래가 완료되기까지 관여하는 시스템과 사람의 수는 예상보다 훨씬 많다. 견적 요청에서 시작해 공급사 선정, 발주, 포워딩 비딩, 통관 서류 준비, 출하, 배송, 정산까지 — 각 단계마다 서로 다른 담당자가 서로 다른 도구에서 수작업을 반복한다. 이 현장에서 반복적으로 발생하는 비효율은 분명하다. 바이어가 견적을 확정하면 공급사에게 이메일이나 메신저로 직접 통보해야 하고, 결제가 완료되면 수동으로 정산 시트에 옮기면서 1~3일이 소요된다. 출하 후에는 선적 정보를 기반으로 CI, PL, CO를 수동 생성하며 누락이 발생하고, 배송 완료 후 공급사/포워더 정산을 수작업으로 대조하면서 오차가 누적된다. ERP, 이메일, 스프레드시트, CRM에 같은 데이터를 반복 입력하는 것도 일상이다. 이 문제들의 공통점은 명확하다. "이벤트가 발생했을 때 후속 작업이 자동으로 실행되지 않는다" 는 것이다. 견적이 확정되었다는 '사실'은 시스템에 기록되지만, 그 사실이 다음 단계의 업무를 자동으로 트리거하지는 않는다. Reindeers Workflow는 이 문제를 해결하기 위해 만들어졌다. 단순히 "자동화 도구를 제공한다"가 아니라, REINDEERS 플랫폼에서 발생하는 실제 거래 이벤트를 기반으로 후속 업무가 자동 실행되는 구조를 만드는 것이다. REINDEERS 플랫폼과의 연결: 거래 이벤트가 워크플로우를 트리거한다 Reindeers Workflow의 가장 중요한 차별점은 범용 자동화 도구가 아니라 REINDEERS 본 플랫폼의 거래 이벤트에 직접 연결 된다는 것이다. REINDEERS에서 발생하는 핵심 거래 이벤트가 MQ(Message Queue)를 통해 워크플로우의 트리거가 된다. 거래 이벤트 트리거되는 워크플로우 실행 내용 quote.confirmed 공...

레인디어스, Buybly로 동남아시아 산업자재 시장 혁신

B2B 오픈마켓 REINDEERS, 한국 기업의 글로벌 진출을 돕다 레인디어스, 머신러닝 기반의 산업자재 매칭 솔루션으로 경쟁력 강화 김명훈 레인디어스 대표 산업자재 시장의 복잡성과 유통장벽은 많은 기업들에게 큰 도전 과제가 되어왔다. 특히 동남아시아 시장 진출을 원하는 한국의 산업자재 제조사들은 현지의 불투명한 거래 환경과 물류 문제로 어려움을 겪어왔다. 이러한 상황에서 레인디어스의 REINDEERS 플랫폼은 새로운 기회를 제시하고 있다. REINDEERS는 B2B 오픈마켓으로, 한국 기업들이 손쉽게 동남아시아 시장에 진출할 수 있도록 지원하며, 유통의 복잡성을 해결하는 혁신적인 솔루션으로 주목받고 있다. 이러한 변화의 중심에는 레인디어스 대표가 있다. 그는 지난 9년간 태국에서의 경험을 바탕으로 고객의 pain point를 해결하기 위해 REINDEERS를 개발했다. 이번 인터뷰를 통해 그의 비전과 경영 철학, 그리고 REINDEERS가 어떻게 산업자재 시장을 변화시키고 있는지에 대해 깊이 있는 이야기를 나누게 되었다. 김명훈 레인디어스 대표 -.소개 레인디어스는 국내 산업자재 제조사들이 동남아시아 시장에 쉽게 진출할 수 있도록 돕는 B2B 오픈마켓인 REINDEERS를 운영하고 있다. 해외 시장 진출에서 가장 큰 장애물인 유통, 물류, 무역의 장벽을 해결해주는 것이 이 플랫폼의 핵심이다. REINDEERS는 단순한 거래 플랫폼이 아니라, 산업자재 구매와 공급 과정을 간소화하고 최적화하는 One-Stop 솔루션으로 자리 잡았다. 레인디어스의 서비스는 REINDEERS와 Enterprise Solution(ERP, POP, WMS)으로 구성되어 있다. 이 솔루션은 동남아시아 현지의 고객사와 공급사에 맞춤형으로 제공되며, 산업현장의 선진화를 이끌어낸다. 기업 운영과 생산 관리, 재고 관리를 전산화해 이익을 극대화하는 데 기여하고 있다. REINDEERS는 산업현장에서 획득한 Raw data를 활용해 인공지능 분석을 통해 발주 ...

JD 플랫폼 매니저 (Platform Manager )

🇰🇷 플랫폼 매니저 (운영 / 글로벌 B2B & AI Agent 기반 자동화 플랫폼) 회사명: (주)레인디어스 | REINDEERS Co., Ltd. 근무지: 서울 / 방콕 (Hybrid 가능) 고용형태: 정규직 (계약-전환형 가능) 회사 소개 REINDEERS는 산업자재 및 무역 중심의 글로벌 B2B 플랫폼을 운영하는 기술 기반 기업입니다. 한국, 태국, 말레이시아, 중국 4개 주요 아시아 시장에서 견적–발주–물류(3PL)–통관–정산–재고관리(WMS)를 통합 관리하는 시스템을 제공합니다. REINDEERS는 POP과 DVRP를 AI로 전환되는 구조로 설계하고 있습니다. 사람은 전략과 방향을 결정하고, 실제 업무는 AI Agent가 실행하는 구조입니다. 조직도에 직원을 등록할 때 사람, AI Agent, 로봇 중에서 선택할 수 있으며, 같은 워크플로우와 같은 권한 체계로 협업합니다. CEO Agent가 전사 전략과 자원 배분을 총괄하고, 구매·생산·영업·물류·재무·통관 Agent가 각 부서 업무를 자율적으로 실행합니다. REINDEERS는 운영 중심의 플랫폼 관리 전문가를 찾습니다. 본 포지션은 플랫폼의 운영·유지·관리·발전·확장을 담당하며, 사람 담당자와 AI Agent, 그리고 향후 합류할 로봇 작업자가 같은 조직도 안에서 협업하는 환경을 관리하는 역할을 맡습니다. (※ 개발 업무를 직접 수행하지 않으며, 개발팀 및 AI Agent 팀과 협업해 개선을 주도합니다.) 이 포지션이 일하는 환경 REINDEERS는 POP과 DVRP를 "조직도 기반 AI 법인" 구조로 설계하고 있습니다. 외부 AI 도구를 연결하는 방식이 아니라, AI Agent가 회사 조직 구조에 직접 통합되어 있습니다. 플랫폼 매니저는 이 Agent들이 정상적으로 작동하는지 모니터링하고, 예외 상황에 대한 승인과 에스컬레이션을 처리하며, 사람 운영자와 AI Agent 간의 협업 경계를 정의하는 역할을 합니다. 현재는 Tool 단계(사...