사전 패싯
개념

패싯

gabury1고친 사람 github-actions[bot]

패싯은 검색 결과를 기준별로 갈라 보여 줍니다. 쇼핑몰에서 운동화를 검색하면 옆에 「나이키 3 · 아디다스 3」처럼 뜨는 브랜드 목록이 패싯입니다. 값 하나를 누르면 결과가 그 값을 가진 상품으로 좁혀집니다. 이 낱말은 다른 분야에서도 씁니다. 이 항목은 검색의 패싯을 다룹니다.

쉽고 빠른 이해

패싯은 검색 결과를 기준마다 묶어 몇 건씩인지 보여 줍니다. 운동화 여덟 켤레가 검색되면 「브랜드: 나이키 3 · 아디다스 3 · 뉴발란스 2」 같은 목록이 결과 옆에 뜹니다.

이게 없으면 사용자는 결과를 좁힐 길을 짐작으로 찾아야 합니다. 검색어를 이리저리 바꿔 보다가 결과가 0건인 화면을 자주 만납니다.

어떻게 도나:

  1. 검색어로 결과를 먼저 얻습니다
  2. 결과 안에서 기준마다 값별 건수를 셉니다
  3. 사용자가 값 하나를 누르면 그 값을 조건으로 더해 결과를 좁힙니다. 건수도 다시 셉니다

대가는 셈에 드는 시간입니다. 검색할 때마다 결과를 훑어 세야 하므로 결과가 많을수록 느려집니다. 값의 종류가 너무 많은 기준은 목록만 길어져 쓸모가 없습니다.

상세

이 절은 운동화 여덟 켤레짜리 작은 쇼핑몰을 예로 듭니다. 패싯이 건수를 세고 결과를 좁히는 과정을 따라간 뒤, 건수를 빨리 세는 법과 패싯이 안 맞는 경우를 봅니다.

옷 가게 점원 비유

옷 가게에서 「셔츠 있어요?」 하고 물었다고 합시다. 친절한 점원은 「셔츠가 스무 벌 있어요. 흰색이 열두 벌, 파란색이 여덟 벌이에요」 하고 알려 줍니다.

「파란색만 볼게요」 하면 점원은 파란 셔츠 여덟 벌만 꺼냅니다. 그러고는 그 여덟 벌이 크기별로 몇 벌씩인지 다시 알려 줍니다. 손님은 셔츠를 한 벌씩 뒤지지 않고도 원하는 옷에 닿습니다.

결과 집합

검색이 찾아 주는 대상 한 건을 문서라고 부릅니다. 쇼핑몰 검색이라면 상품 하나가 문서 하나입니다.

문서는 이름, 브랜드, 색상, 가격 같은 칸을 갖습니다. 이 칸 하나를 필드라고 합니다. 패싯은 이 필드의 값을 가지고 문서를 가릅니다.

검색어에 맞는 문서를 모은 것을 결과 집합이라고 부릅니다. 「운동화」로 찾았더니 아래 여덟 켤레가 나왔다고 합시다. 이 절의 예는 전부 이 여덟 켤레로 합니다.

번호 브랜드 색상 가격
1 나이키 흰색 89,000원
2 나이키 검은색 129,000원
3 나이키 흰색 59,000원
4 아디다스 흰색 99,000원
5 아디다스 검은색 79,000원
6 뉴발란스 회색 109,000원
7 뉴발란스 흰색 139,000원
8 아디다스 회색 69,000원

패싯과 패싯 값

결과를 가르는 기준 하나가 패싯입니다. 위 표에서는 브랜드가 패싯 하나입니다. 색상이 또 하나입니다. 대개 필드 하나가 패싯 하나가 됩니다.

기능 전체도 패싯이나 패싯 검색이라고 부릅니다. 이 절부터 「패싯 하나」는 기준 하나를 말합니다. 화면에 뜨는 브랜드 목록은 브랜드 패싯 하나를 보여 준 것입니다.

그 기준이 갖는 값 하나하나는 패싯 값이라고 부릅니다. 브랜드 패싯의 값은 나이키, 아디다스, 뉴발란스 셋입니다.

패싯 값 옆에는 건수가 붙습니다. 건수는 결과 집합 안에서 그 값을 가진 문서의 수입니다. 여덟 켤레를 세면 아래와 같습니다.

패싯 값과 건수
브랜드 나이키 3 · 아디다스 3 · 뉴발란스 2
색상 흰색 4 · 검은색 2 · 회색 2

화면에서는 이 표가 결과 목록 옆에 뜹니다. 사용자는 아무것도 누르지 않았는데도 흰색이 네 켤레라는 것을 압니다.

건수를 세는 코드

세는 일 자체는 단순합니다. 결과 문서를 하나씩 보며 값마다 카운터를 하나씩 올립니다.

아래는 파이썬으로 브랜드 패싯을 센 것입니다. hits 가 결과 집합입니다. 튜플 하나가 문서 하나의 브랜드와 색상입니다. Counter 는 값마다 개수를 세어 담는 딕셔너리입니다. 줄 끝 주석이 그 줄이 내는 값입니다.

Python
from collections import Counter

hits = [("나이키", "흰색"), ("나이키", "검은색"),
        ("나이키", "흰색"), ("아디다스", "흰색"),
        ("아디다스", "검은색"), ("뉴발란스", "회색"),
        ("뉴발란스", "흰색"), ("아디다스", "회색")]

brand = Counter(b for b, c in hits)
brand["나이키"]    # 3
brand["아디다스"]  # 3
brand["뉴발란스"]  # 2

색상 패싯도 같은 방식으로 한 번 더 셉니다. 패싯이 다섯이면 결과 집합을 다섯 번 훑습니다.

SQL(Structured Query Language)로 옮기면 패싯 하나는 GROUP BY 한 번입니다. 같은 검색 조건 아래에서 브랜드로 묶어 셉니다. 색상으로도 한 번 더 묶어 셉니다.

여러 값을 묶어 한 값으로 접는 이런 계산을 집계라고 부릅니다. 패싯은 검색 결과에 거는 집계입니다.

패싯 값 선택과 필터

사용자가 색상 패싯에서 흰색을 누르면 「색상이 흰색이다」라는 조건이 검색에 붙습니다. 검색어에 맞으면서 이 조건도 맞는 문서만 남습니다. 이렇게 결과를 좁히는 조건을 필터라고 부릅니다.

여덟 켤레 가운데 흰색은 1, 3, 4, 7번 네 켤레입니다. 결과 집합이 이 넷으로 줄었으니 브랜드 건수도 이 넷 안에서 다시 셉니다.

Python
white = [h for h in hits if h[1] == "흰색"]
b2 = Counter(b for b, c in white)
b2["나이키"]    # 2
b2["아디다스"]  # 1
b2["뉴발란스"]  # 1

값을 한 번 누를 때마다 같은 순환이 한 바퀴 돕니다. 필터가 붙습니다. 결과가 줄어듭니다. 건수를 다시 셉니다. 사용자는 새 건수를 보고 다음에 누를 값을 고릅니다.

여러 값을 고를 때

다른 패싯의 값을 또 누르면 두 조건을 모두 만족하는 문서만 남습니다. 흰색을 고른 뒤 브랜드에서 나이키를 누르면 1, 3번 두 켤레가 남습니다.

같은 패싯 안에서 값을 둘 고르면 뜻이 달라집니다. 흰색과 검은색을 함께 고른 사람은 「흰색이거나 검은색」인 운동화를 보고 싶어 합니다. 한 켤레의 색상은 하나뿐이라 둘 다 맞기를 요구하면 결과가 0건이 됩니다.

그래서 패싯끼리는 조건을 모두 만족해야 합니다. 한 패싯 안의 값끼리는 하나만 맞아도 됩니다. 아래 표가 두 경우를 나란히 놓습니다.

고른 값 묶는 법 남는 번호
흰색 · 나이키 (패싯이 다르다) 둘 다 맞아야 한다 1, 3
흰색 · 검은색 (패싯이 같다) 하나만 맞으면 된다 1, 2, 3, 4, 5, 7

두 규칙은 한 검색에 함께 걸립니다. 색상에서 흰색과 검은색을 고르고 브랜드에서 나이키를 누르면 아래처럼 묶입니다. 나이키이면서 흰색이나 검은색인 1, 2, 3번이 남습니다.

flowchart TD
    R["패싯끼리 · 둘 다 맞아야 한다"]
    R --> C["색상 패싯 안 · 하나만 맞으면 된다"]
    R --> B["브랜드 패싯 안 · 하나만 맞으면 된다"]
    C --> W["흰색"]
    C --> K["검은색"]
    B --> N["나이키"]

자기 필터를 뺀 건수

흰색을 누르면 결과가 네 켤레로 좁혀집니다. 색상 건수까지 이 네 켤레 안에서 세면 문제가 생깁니다. 네 켤레는 전부 흰색이라 색상 패싯이 「흰색 4 · 검은색 0 · 회색 0」이 됩니다.

사용자는 검은색도 함께 보고 싶었을 수 있습니다. 그런데 화면에는 검은색이 0건으로 뜹니다. 검은색을 더하면 결과가 늘어난다는 것을 사용자가 알 길이 없습니다.

그래서 패싯마다 건수를 셀 때는 그 패싯 자신이 건 필터만 빼고 셉니다. 색상 패싯은 색상 필터를 뺀 결과에서 셉니다. 지금은 필터가 색상 하나뿐이라 여덟 켤레 전체가 됩니다.

결과 목록은 모든 필터를 건 네 켤레를 보여 줍니다. 건수만 패싯마다 다른 결과 집합에서 셉니다. 아래 그림은 한 화면에 뜨는 셋이 어느 집합에서 나오는지 보입니다.

flowchart TD
    S["결과 집합 8켤레"]
    S --> CC["색상 건수 · 흰색 4 · 검은색 2 · 회색 2"]
    S -->|색상이 흰색| F["4켤레 · 1 · 3 · 4 · 7"]
    F --> L["결과 목록"]
    F --> BC["브랜드 건수 · 나이키 2 · 아디다스 1 · 뉴발란스 1"]

색상 건수만 여덟 켤레에서 나옵니다. 결과 목록과 브랜드 건수는 흰색 네 켤레에서 나옵니다. 고른 패싯이 많아질수록 따로 세야 할 결과 집합도 늘어 셈이 무거워집니다.

필터와의 차이

필터만 있는 검색 화면도 있습니다. 조건 칸에서 색상을 골라 넣고 검색 버튼을 누르는 화면입니다. 이런 화면에서는 누르기 전에 결과가 몇 건일지 모릅니다.

패싯은 필터마다 건수를 미리 붙여 보여 줍니다. 사용자는 0건이 될 값을 누르기 전에 알아봅니다. 막다른 결과로 들어섰다가 되돌아 나오는 일이 줄어듭니다.

건수는 결과를 요약하는 구실도 합니다. 「흰색 4 · 검은색 2 · 회색 2」만 봐도 이 결과에 무엇이 얼마나 있는지 한눈에 들어옵니다.

패싯이 되는 필드

패싯은 값이 몇 가지로 추려지는 필드에 맞습니다. 분류, 브랜드, 색상, 크기가 그렇습니다. 값 하나에 문서가 여럿 몰려야 건수가 뜻을 갖습니다.

한 필드가 갖는 서로 다른 값의 개수를 카디널리티라고 합니다. 색상은 카디널리티가 낮습니다. 상품 번호는 높습니다. 상품 번호로 패싯을 만들면 건수가 전부 1 인 값이 결과 수만큼 늘어섭니다.

상품 설명 같은 긴 글 필드도 패싯이 안 됩니다. 글마다 내용이 달라서 두 문서가 같은 값을 가질 일이 없습니다.

숫자·날짜 필드와 범위 패싯

가격은 문서마다 값이 거의 다 다릅니다. 여덟 켤레의 가격도 전부 다릅니다. 그래서 숫자 필드는 구간을 정해 묶습니다. 그 구간을 패싯 값으로 씁니다.

여덟 켤레를 10만 원을 경계로 나누면 「10만 원 미만 5 · 10만 원 이상 3」이 됩니다. 이렇게 구간을 값으로 쓰는 패싯을 범위 패싯이라고 부릅니다. 날짜도 「오늘 · 이번 주 · 이번 달」 같은 구간으로 묶습니다.

건수를 빨리 세는 두 방법

결과가 수백만 건이면 검색할 때마다 수백만 건을 세야 합니다. 그래서 패싯은 검색을 느리게 만드는 흔한 원인입니다. 건수를 세는 방법은 크게 둘입니다. 값에서 출발하느냐 문서에서 출발하느냐로 갈립니다.

두 방법 모두 문서를 통째로 꺼내 읽지 않습니다. 문서 번호와 필드 값만 다뤄서 셈을 가볍게 합니다. 이 절은 브랜드 패싯과 흰색 네 켤레(1, 3, 4, 7번)로 둘을 견줍니다.

검색 엔진은 대개 역색인을 갖고 있습니다. 역색인은 값마다 그 값을 가진 문서 번호를 적어 둔 찾아보기입니다. 책 뒤의 찾아보기가 낱말 옆에 쪽 번호를 적는 것과 같은 모양입니다.

역색인에서 값 하나에 붙은 번호 목록을 포스팅 리스트라고 합니다. 브랜드 필드라면 나이키의 포스팅 리스트는 1, 2, 3번입니다. 아디다스의 것은 4, 5, 8번입니다.

첫째 방법은 값에서 출발합니다. 값마다 포스팅 리스트와 결과 집합을 맞대어 양쪽에 다 있는 번호를 셉니다. 두 목록에 함께 있는 번호를 모은 것이 교집합입니다. 나이키의 1, 2, 3번과 결과 집합 1, 3, 4, 7번의 교집합은 1, 3번이라 나이키 건수는 2입니다.

이 방법은 문서를 한 건도 열지 않습니다. 번호 목록끼리 맞대기만 합니다. 대신 값마다 교집합을 한 번씩 해야 합니다. 값의 종류가 늘면 교집합 횟수도 그만큼 늘어납니다.

둘째 방법은 문서에서 출발합니다. 결과 문서를 하나씩 보며 그 문서의 값을 읽어 카운터를 올립니다. 앞의 파이썬 코드와 같은 셈법입니다. 값이 몇 종류이든 결과 문서마다 값 하나만 읽으면 끝납니다.

이 방법이 가벼우려면 문서 번호로 값을 바로 찾을 수 있어야 합니다. 역색인은 값에서 문서로 가는 방향이라 이 일에는 거꾸로입니다. 그래서 패싯을 세는 검색 엔진은 흔히 필드 값을 문서 번호 순서로 늘어놓은 저장소를 따로 둡니다. 번호 3을 주면 그 문서의 브랜드인 나이키가 바로 나옵니다.

아래 그림은 두 구조가 서로 반대 방향을 가리키는 것을 보입니다. 위는 역색인의 포스팅 리스트와 결과 집합의 교집합으로 나이키 건수를 얻는 모습입니다. 아래는 문서 번호에서 값으로 가는 저장소입니다.

flowchart TD
    subgraph INV["역색인 · 값에서 문서 번호로"]
        N["나이키"] --> NP["포스팅 리스트 · 1 · 2 · 3"]
        A["아디다스"] --> AP["포스팅 리스트 · 4 · 5 · 8"]
        V["뉴발란스"] --> VP["포스팅 리스트 · 6 · 7"]
    end
    R["결과 집합 · 1 · 3 · 4 · 7"]
    NP --> I["교집합 · 1 · 3 · 나이키 2건"]
    R --> I
    subgraph COL["필드 값을 문서 번호 순서로 늘어놓은 저장소"]
        D1["1 → 나이키 · 2 → 나이키"]
        D2["3 → 나이키 · 4 → 아디다스"]
        D3["5 → 아디다스 · 6 → 뉴발란스"]
        D4["7 → 뉴발란스 · 8 → 아디다스"]
        D1 ~~~ D2 ~~~ D3 ~~~ D4
    end
    I ~~~ COL

필드 하나의 값만 한곳에 이어 담는 방식을 열 지향 저장이라고 합니다. 문서 번호 순서로 늘어놓은 저장소가 이 방식을 따릅니다. 브랜드를 셀 때 브랜드 값만 훑습니다. 이름이나 가격은 건드리지 않습니다.

어느 쪽이 싼지는 값의 종류가 정합니다. 브랜드나 색상처럼 값이 몇 가지뿐이면 첫째 방법이 교집합 몇 번으로 끝납니다. 값이 수천 가지면 교집합도 수천 번이 됩니다. 이때는 결과 문서 수만큼만 읽는 둘째 방법이 쌉니다.

아래 표가 두 방법을 간추립니다.

방법 무엇을 훑나 잘 맞는 경우
값에서 출발 값마다 포스팅 리스트 값의 종류가 적을 때
문서에서 출발 결과 문서마다 필드 값 값의 종류가 많을 때

같은 낱말의 다른 뜻

패싯의 영어 facet 은 보석을 깎아 낸 면 하나를 가리키는 말입니다. 한 대상을 여러 면에서 본다는 뜻으로 여러 분야가 이 낱말을 빌려 씁니다.

도서 분류에서는 한 책을 주제, 지역, 시대처럼 여러 기준으로 동시에 가르는 방식을 패싯 분류라고 부릅니다. 검색의 패싯은 이 생각을 검색 결과에 옮긴 것입니다. 기준마다 따로 좁혀 들어간다는 점이 같습니다.

XML(eXtensible Markup Language, 확장 가능한 마크업 언어) 문서의 모양을 정하는 XML 스키마에서는 값에 거는 제약 하나를 패싯이라고 부릅니다. 문자열의 최대 길이나 숫자의 최솟값 같은 제약이 그렇습니다. 검색의 패싯과는 이름만 같습니다.

쓸 때와 안 쓸 때

맞는 쓰임과 안 맞는 쓰임을 함께 놓아 봅니다.

하려는 일 패싯이 맞나
분류 · 브랜드처럼 정해진 속성을 가진 상품이나 문서를 좁히기 맞다
결과에 무엇이 얼마나 있는지 한눈에 보여 주기 맞다. 건수가 요약이 된다
결과가 몇 건 안 되는 검색 필요 없다. 몇 건뿐이면 목록을 한 번 훑어 끝난다
속성 필드 없이 본문 글만 있는 문서 검색 안 맞다. 가를 기준이 없다
상품 번호처럼 문서마다 값이 다른 필드 안 맞다. 건수가 전부 1 이다

관련 항목

패싯이 속하는 상위 분류

검색 · 검색 엔진 · 전문 검색 · 탐색적 검색 · 검색 모델

패싯이 건수를 셀 때 쓰는 집계 계산

집계 · GROUP BY · 카디널리티 · 히스토그램 · 근사 집계

패싯 값을 조건으로 바꿔 결과를 좁히는 기능

필터 · 불리언 검색 · 범위 쿼리 · 드릴다운 · 브레드크럼

패싯의 건수를 빨리 세는 저장 구조

역색인 · 포스팅 리스트 · 교집합 · 열 지향 저장 · 비트맵 인덱스

패싯과 함께 검색 결과 화면을 이루는 기능

자동 완성 · 하이라이팅 · 결과 그룹핑 · 랭킹 · 정렬 · 페이지네이션

패싯을 제공하는 검색 라이브러리와 서버

Apache Lucene · Elasticsearch · OpenSearch · Solr

패싯이 비롯된 분류 체계

패싯 분류 · 문헌정보학 · 분류 체계 · 택소노미 · 메타데이터 · 태그

다른 이름: facet · facets · faceted search · faceted navigation · 패싯 검색 · 패싯 내비게이션 · 패싯 탐색