[dbt] Semantic Layer 지표 정의 구조와 마트 테이블 방식 비교

1. 개요

시맨틱 레이어(Semantic Layer)는 데이터의 물리적 구조와 비즈니스 의미를 분리하고 지표와 데이터 간 관계를 일관된 기준으로 정의하는 계층이다. 시맨틱 레이어를 통해 사용자는 복잡한 테이블 구조나 SQL을 직접 이해하지 않아도 공통으로 정의된 비즈니스 지표를 조회하고 활용할 수 있다.

dbt Semantic Layer는 이 계층을 dbt 프로젝트 안의 YAML로 정의하고 MetricFlow라는 엔진이 정의를 읽어 SQL을 만들어 데이터 플랫폼에서 실행하는 구조다. 이 글은 dbt v1.12 이상에서 쓰는 최신 YAML 스펙을 기준으로 구성 요소와 정의 단위를 정리하고 같은 질문을 마트 테이블로 답할 때와 비교한다.

본문의 테이블명과 컬럼명은 예시 값이다. 생성 SQL은 MetricFlow의 실제 출력이 아니라 조인과 집계 논리를 보여주기 위해 줄여 쓴 것이고, 실제 출력은 서브쿼리로 감싸져 더 길다. 공식 문서는 2026년 10월에 확인한 내용이다.


2. 구성 요소와 요청 경로

dbt Semantic Layer는 오픈소스 엔진과 유료 플랫폼 서비스로 나뉜다. MetricFlow는 Apache 2.0 라이선스의 오픈소스 엔진으로, YAML 정의를 읽어 SQL을 생성한다. 로컬 환경에 설치하면 정의 검증, SQL 생성, CLI 조회까지 할 수 있다. BI 도구나 외부 애플리케이션이 API로 지표를 조회하는 기능과 캐시, exports는 dbt 플랫폼의 Starter 이상 플랜에서 제공한다.

BI 도구 Tableau · Sheets AI 에이전트 MCP 서버 경유 CLI mf / dbt sl dbt 플랫폼 · Starter 이상 Semantic Layer API JDBC · GraphQL · 캐시 MetricFlow YAML 정의로 SQL 생성 Apache 2.0 데이터 플랫폼 생성된 SQL 실행 SQL
그림 1. 정의와 SQL 생성은 MetricFlow가 맡고, BI·AI 도구가 거치는 API와 캐시만 dbt 플랫폼에 있다. CLI는 플랫폼 없이 MetricFlow를 직접 호출한다.

dbt 플랫폼에서 제공하거나 플랫폼 연결이 필요한 기능은 다음과 같다.

  • Semantic Layer API: JDBC와 GraphQL로 외부 도구가 지표를 조회한다.
  • 공식 연동: Tableau, Google Sheets, Excel 등 BI 도구와 스프레드시트에서 지표를 선택해 쓴다.
  • 결과 캐시와 exports: 자주 쓰는 조회를 미리 계산해 두거나 데이터 플랫폼에 테이블로 기록한다.
  • dbt MCP 서버: self-hosted와 remote 방식이 있다. Semantic Layer 지표 조회 도구를 사용할 수 있는 범위는 연결한 dbt 플랫폼의 플랜과 API 권한에 따라 달라진다.

3. 정의 단위

semantic model은 dbt 모델 하나에 의미를 붙인 것이다. 최신 스펙에서는 모델 YAML 안에 semantic_model: enabled: true를 두고 컬럼마다 entity 또는 dimension을 선언하며, 지표는 같은 파일의 metrics: 목록에 둔다. 구 스펙의 최상위 semantic_models: 블록과 measures는 폐지 수순이다.

entity는 조인 키다. 타입은 네 가지다.

  • primary: 행마다 하나씩 있고 null이 없는 키
  • unique: 유일하지만 null을 허용하는 키
  • foreign: 다른 semantic model을 가리키는 키
  • natural: SCD Type 2처럼 같은 키가 시간에 따라 여러 행에 걸치는 경우

dimension은 지표를 자르거나 필터링하는 기준이며 categorical과 time 두 타입이 있다. time dimension에는 컬럼 수준에서 granularity를 지정하고 모델 수준의 agg_time_dimension이 그 모델에 정의된 지표의 기본 시간축이 된다.

metric은 다섯 타입이다.

타입 정의 예
simple 컬럼 식 하나를 집계한다 (sum, count, count_distinct, average, percentile 등) 매출 = sum(amount)
ratio 지표 ÷ 지표 객단가 = 매출 / 주문수
derived 지표끼리의 산식. 기간 오프셋 지정 가능 전주 대비 매출 증감
cumulative 누적 또는 윈도우 집계 월 누적 매출, 최근 30일 활성 사용자
conversion 기준 이벤트 뒤 일정 기간 안에 전환 이벤트가 일어난 비율 가입 후 결제 전환율

주문 fact 테이블과 고객 dimension 테이블을 최신 스펙으로 정의하면 다음과 같다.

models:
  - name: fct_orders
    semantic_model:
      enabled: true
    agg_time_dimension: ordered_at
    columns:
      - name: order_id
        entity:
          type: primary
          name: order
      - name: customer_id
        entity:
          type: foreign
          name: customer
      - name: ordered_at
        granularity: day
        dimension:
          type: time
      - name: order_status
        dimension:
          type: categorical
    metrics:
      - name: revenue
        type: simple
        agg: sum
        expr: amount
      - name: order_count
        type: simple
        agg: count
        expr: 1

  - name: dim_customers
    semantic_model:
      enabled: true
    agg_time_dimension: first_ordered_at
    columns:
      - name: customer_id
        entity:
          type: primary
          name: customer
      - name: first_ordered_at
        granularity: day
        dimension:
          type: time
      - name: region
        dimension:
          type: categorical

metrics:
  - name: avg_order_value
    type: ratio
    numerator: revenue
    denominator: order_count

두 semantic model을 잇는 것은 entity 이름이다. fct_orders.customer_id는 foreign, dim_customers.customer_id는 primary로 선언됐고 둘 다 이름이 customer라서 MetricFlow는 이 둘 사이에 조인 경로가 있다고 본다. 조인 조건은 따로 적지 않는다.

fct_orders semantic model entities order_id primary · order customer_id foreign · customer dimensions ordered_at time · day order_status categorical metrics revenue simple · sum(amount) order_count simple · count dim_customers semantic model entities customer_id primary · customer dimensions first_ordered_at time · day region categorical avg_order_value 최상위 metrics ratio · revenue / order_count
그림 2. 두 semantic model은 customer라는 entity 이름으로 이어지고, 다른 지표를 참조하는 지표는 최상위 metrics에 둔다.

공식 문서는 이 관계를 semantic graph라고 부른다. dbt DAG가 모델 사이의 실행 의존성을 담는 것과 달리 semantic graph는 지표와 기준 사이의 사용 가능한 조합을 담는다. MetricFlow는 entity 타입으로 조인 가능한 경로를 정하고 행이 불어나는 fan-out 조인과 chasm 조인은 만들지 않는다고 문서에 설명돼 있다. primary나 unique entity가 없는 테이블(이벤트 로그 등)의 dimension은 그 테이블 안의 지표에서만 쓸 수 있다.


4. 조회 명령과 생성 SQL

dimension은 엔티티__디멘전 형식으로 부른다. customer__region은 customer 엔티티를 따라 도달하는 dim_customers의 region이고, order__order_status는 fct_orders 자체의 상태다. 시간축은 metric_time이며 metric_time__month처럼 grain을 붙이면 컬럼의 granularity(day)보다 큰 단위로 올린다.

로컬 환경에 MetricFlow를 직접 설치한 경우 mf 명령을 쓴다.

mf query --metrics revenue,avg_order_value --group-by metric_time__month,customer__region

dbt 플랫폼 CLI에서는 dbt sl 명령을 쓰고 필터는 --where에 dimension 참조 문법으로 적는다.

dbt sl query --metrics revenue --group-by metric_time__month --where "{{ Dimension('customer__region') }} = 'KR'"

mf query에 --explain, dbt sl query에 --compile을 붙이면 실행하지 않고 생성 SQL만 본다. 정의 검증은 mf validate-configs 또는 dbt sl validate다.


5. 마트 테이블 방식과의 비교

dbt 모델만 쓰는 경우 질문 하나에 마트 모델 하나를 만든다. 월별 매출은 mart_revenue_by_month, 지역별 매출은 mart_revenue_by_region 같은 식이다. Semantic Layer는 새 테이블을 만들지 않고 3장의 정의로 요청 시점에 SQL을 생성한다. 아래에서 지표와 기준을 바꿔 가며 두 방식이 실행하는 SQL과 누적되는 마트 수를 비교할 수 있다. 기준은 여러 개를 함께 고를 수 있다.

지표
기준
질문 · 월별 매출
결과 1행 = 월 1개
마트 테이블 방식질문마다 모델 1개를 작성한다새 마트 추가
-- models/marts/mart_revenue_by_month.sql
select
  date_trunc('month', o.ordered_at) as month,
  sum(o.amount) as revenue
from fct_orders o
group by 1
1개
만든 마트 테이블
1곳
매출 계산식 사본
Semantic Layer요청을 보내면 정의로 SQL을 생성한다
mf query --metrics revenue \
  --group-by metric_time__month
MetricFlow가 생성한 SQL (논리만 남기고 줄인 형태)
select
  date_trunc('month', o.ordered_at) as metric_time__month,
  sum(o.amount) as revenue
from fct_orders o
group by 1
0개
만든 테이블
1곳
매출 계산식
SQL 조각의미Semantic Layer에서 온 곳
date_trunc('month', o.ordered_at)주문일을 월 단위로 잘라 묶는다agg_time_dimension(ordered_at, day)을 __month로 올린다
sum(o.amount)주문 금액을 모두 더한다revenue 지표 · simple · agg: sum · expr: amount
from fct_orders o주문 1건이 1행인 fact 테이블에서 시작한다지표가 정의된 semantic model이 출발점이다
group by 1고른 기준 조합마다 한 행으로 묶는다--group-by 목록을 그대로 쓴다
  • 월은 주문일(ordered_at) 기준이다. 결제일 기준이 필요하면 agg_time_dimension이 다른 지표를 따로 둔다.
그림 3. 같은 질문에 두 방식이 실행하는 SQL은 거의 같고, 마트 방식만 질문이 늘 때마다 테이블과 매출 계산식 사본이 늘어난다.

두 방식이 실행하는 SQL 자체는 거의 같다. 차이는 두 가지다. 마트 방식은 사람이 SQL을 쓰고 질문마다 테이블을 추가하며, Semantic Layer는 엔진이 SQL을 생성하고 테이블을 추가하지 않는다.

고객 지역별을 고르면 양쪽 모두 dim_customers 조인이 붙는다. 마트 방식에서는 작성자가 조인 조건을 쓰고, Semantic Layer에서는 customer 엔티티의 foreign·primary 관계를 보고 MetricFlow가 붙인다. 주문 상태별은 fct_orders 안의 dimension이라 조인 없이 바로 쓴다.

마트 수는 지표 수와 기준 조합 수의 곱으로 늘어난다. 위 예시는 지표 3개, 기준 3개라 조합이 3 × 2³ = 24종이고 기준이 하나 늘 때마다 조합은 두 배가 된다. 실무에서는 보통 자주 묻는 조합만 마트로 두고 나머지는 그때마다 SQL을 쓴다. 더할 수 있는 지표(매출, 주문수)는 더 잘게 자른 마트를 다시 집계해 상위 수준 값을 얻을 수 있어 실제 마트 수는 조합 수보다 적다. 비율 지표는 그렇게 되지 않는데, 6장에서 다룬다.


6. 비율 지표와 정의 변경 범위

객단가처럼 비율로 정의된 지표는 그룹별 값을 다시 평균 내도 상위 수준의 값이 나오지 않는다.

지역 매출 주문수 객단가
A 1,000 10 100.0
B 300 1 300.0
전체 1,300 11 118.2

두 지역 객단가의 평균은 200이지만 전체 객단가는 118.2다. 마트 방식에서도 마트에 객단가 대신 매출과 주문수를 저장해 두고 조회할 때 각각 합산한 뒤 나누면 맞는 값이 나온다. 다만 이 규칙은 마트를 만드는 사람과 마트를 읽는 도구가 각자 지켜야 한다. 마트에 객단가 컬럼이 있으면 BI 도구가 그 컬럼을 평균 내는 것을 막을 방법이 없고, 분자와 분모만 저장했다면 나누는 식을 조회하는 쪽마다 다시 써야 한다. Semantic Layer에서는 avg_order_value가 ratio 타입이라는 사실이 정의에 들어 있어서, 어떤 기준으로 조회하든 MetricFlow가 분자와 분모를 각각 집계한 뒤 나눈다. 조회하는 쪽은 나누는 식을 모른 채 지표 이름만 부른다.

정의가 바뀔 때도 고칠 범위가 다르다. 취소 주문을 매출에서 빼기로 했다면 마트 방식에서는 매출 계산식이 들어간 마트를 전부 찾아 고쳐야 한다. 5장 위젯의 매출 계산식 사본 수가 이 범위다. Semantic Layer에서는 revenue 지표의 expr 한 곳을 고치면 revenue를 참조하는 avg_order_value와 모든 조회에 반영된다.

마트 테이블 방식매출 마트마다 수정
-- mart_revenue_by_month.sql
-- mart_revenue_by_region.sql
-- mart_avg_order_value_by_*.sql
-- 매출 계산식이 있는 마트 전부
sum(case when order_status
         <> 'cancelled'
         then amount else 0 end)
  as revenue
Semantic Layer지표 정의 한 곳 수정
metrics:
  - name: revenue
    type: simple
    agg: sum
    expr: >
      case when order_status
      <> 'cancelled'
      then amount else 0 end
그림 4. 취소 주문 제외는 마트 방식에서는 매출 계산식이 있는 마트마다, Semantic Layer에서는 revenue 지표의 expr 한 곳에서 반영된다.

7. 도입 시 확인 사항

공식 문서 기준으로 확인한 제약이다.

  • YAML 스펙 변경: v1.12부터 최상위 semantic_models:, measures, type_params가 구 스펙이 됐다. 블로그와 예제 대부분이 구 스펙이라 섞이지 않게 해야 하고 기존 정의는 dbt-autofix deprecations --semantic-layer로 옮긴다. 설치한 패키지가 구 스펙 정의를 담고 있으면 패키지 쪽도 갱신돼야 파싱 오류가 나지 않는다.
  • dbt Mesh 프로젝트 간 참조: 최신 스펙에서는 아직 지원하지 않고 구 스펙에서만 된다.
  • 지원 데이터 플랫폼: 현재 공식 문서의 목록은 Snowflake, BigQuery, Databricks, Redshift, Postgres, Trino(Starburst Galaxy 포함)다. Microsoft Fabric은 지원하지 않는다. 버전과 실행 환경에 따라 지원 범위가 달라질 수 있으므로 도입 전에 다시 확인한다.
  • expr에는 순수 SQL만 쓴다. MetricFlow는 dbt 내장 Jinja 함수와 패키지 매크로를 아직 지원하지 않는다.
  • 플랫폼 의존: Semantic Layer API, 공식 BI 연동, 캐시와 exports는 dbt 플랫폼 Starter 이상 기능이다. MCP 서버는 self-hosted와 remote 방식이 있지만 Semantic Layer 관련 도구의 사용 범위는 플랜과 권한에 따라 달라진다. 로컬 MetricFlow만 사용하면 CLI 조회까지 가능하다.
  • v1.12부터는 dbt YAML 대신 Apache Ossie 문서로 semantic model을 정의하는 선택지도 있다.

8. 정리

  • dbt Semantic Layer는 entity, dimension, metric을 모델 YAML에 선언하고 MetricFlow가 그 정의로 SQL을 생성하는 구조다. SQL 생성 엔진은 Apache 2.0 오픈소스고 API, 연동, 캐시는 플랫폼 기능이다.
  • 두 semantic model은 entity 이름이 같으면 조인 경로가 생기고 조인 조건은 따로 적지 않는다.
  • 같은 질문에 대해 마트 방식과 생성되는 SQL은 거의 같다. 마트 방식은 지표와 기준 조합마다 테이블이 늘고 Semantic Layer는 테이블을 추가하지 않는다.
  • 비율 지표는 그룹별 값을 다시 평균 내면 틀린다. 마트 방식에서는 분자와 분모를 따로 저장하고 조회 시점에 나누는 규칙을 사람이 지켜야 하고, Semantic Layer는 ratio 타입 정의를 보고 엔진이 매번 그렇게 계산한다. 정의 변경도 지표 한 곳에서 반영된다.
  • v1.12 스펙 변경, 프로젝트 간 참조 미지원, 지원 플랫폼 목록, Jinja 미지원은 도입 전에 확인해야 한다.

9. 참고 자료

Categories:

Updated:

Leave a comment