데이터 기반의 의사결정을 하거나 AI 알고리즘/rule을 서비스에 추가하기 위해서, 여러 다양한 데이터를 가공하고 DB에 넣고 querying 할 필요가 있습니다. 본 포스팅에서는 이와 관련된 vector DB 사용법 부터 기본적인 SQL query 문, 시각화 방법 등을 기록합니다.

Qdrant

  • Qdrant는 vector DB로 보기보다는 vector search engine으로 봐야 함
  • Qdrant \to Collection \to Shard \to Segment. 이 부분은 다른 search engine 들과 비교해보면 좋을듯
  • Collections: points(vectors with a payload)의 집합
  • Points: vector와 payload(optional)로 구성
  • Payload: vector와 함께 저장 가능한 추가 정보
  • Search: 기본적으로 dot, cos, euc similarity search 제공
  • Index: 필터링 속도 향상을 위한 payload index와 vector search 속도 향상을 위한 vector index 존재
Vector Search with HNSW

HNSW는 벡터 검색 알고리즘 중 하나인데 qdrant의 작동을 이해하기 위해 중요합니다. NHN Forward 발표에서도 잘 설명해주고 있어 이를 참고 하였습니다.

  • k-Nearest Neighbor: 검색 대상 벡터와 모든 벡터와의 거리를 계산하여 가장 가까운 k개를 리턴하는 방식
  • Approximate Nearest Neighbor (e.g., ANNOY): Spotify에서 개발한 tree-based ANN 기법으로 정확도를 조금 줄이는 대신 검색 속도를 높임. 전체 벡터를 여러 공간으로 분할하여 tree 형태의 자료구조 만들어 검색 수행. 데이터 들어올 때 마다 자료구조 다시 만들어야 해서 데이터 추가시 빌드 비용 높음
  • Hierarchical Navigable Small World: Skip-list와 Navigable small world 개념에 영향을 받은 방식

    • 아래 이미지의 오른쪽을 보면 이해가 쉬운데, 초록색 query에 대해서 빨간색 start point 부터 검색을 시작할 때, 한 번 nearest neighbor을 찾을 때 마다 깊은 layer로 내려간다고 생각하면 됨 (greedy search)
    • 데이터 추가가 쉽고, 데이터 경향성에 따라 성능 차이 있음. 클러스링 데이터 대해 응답속도 빠르고 재현율도 높음
  • HNSW challenges: (1) 여전히 데이터 index 빌드 타임 소요 (2) 100% 정확도/재현율 보장하지 않음 (3) Sequetial read pattern을 가져서 병렬화 불가능. Qdrant는 이런 점들 보완하기 위해 quatization & oversampling 활용
  • Filterable HNSW(payload-based refinement): post-filtering, pre-filtering이 비효율적이기 때문에 in-place filtering 방식으로 구현
23 11 19 1

Taken From, Yury A. Malkov, et al.

Python qdrant client
  1. Client initialization
from qdrant_client import QdrantClient
from qdrant_client.http.models import Distance, VectorParams, PointStruct
from qdrant_client.http.models import Filter, FieldCondition, MatchValue
client = QdrantClient("localhost", port=6333)
  1. Collection 생성
client.create_collection(
    collection_name="test_collection",
    vectors_config=VectorParams(size=4, distance=Distance.DOT),
)
  1. Vector 추가
operation_info = client.upsert(
    collection_name="test_collection",
    wait=True,
    points=[
        PointStruct(id=1, vector=[0.05, 0.61, 0.76, 0.74], payload={"city": "Berlin"}),
        PointStruct(id=2, vector=[0.19, 0.81, 0.75, 0.11], payload={"city": "London"}),
        PointStruct(id=3, vector=[0.36, 0.55, 0.47, 0.94], payload={"city": "Moscow"}),
    ],
)
  1. Query 실행
# run a query without filter
search_result = client.search(
    collection_name="test_collection", query_vector=[0.2, 0.1, 0.9, 0.7], limit=2
)

# run a query with filter
search_result = client.search(
    collection_name="test_collection",
    query_vector=[0.2, 0.1, 0.9, 0.7],
    query_filter=Filter(must=[FieldCondition(key="city", match=MatchValue(value="London"))]),
    limit=2,
)

SQL

Basics
SELECT 컬럼
FROM 테이블 
WHERE 조건 
GROUP BY 묶는 기준 
ORDER BY 정렬 기준 
LIKE 문자열 조건 
LIMIT 데이터 출력수
	OFFSET 앞 부터 N개 제외
	...;
Join

employees 테이블과 departments 테이블에 대해 ChatGPT가 제공해준 join 예시들

employee_id employee_name department_id
1 John Doe 101
2 Jane Smith 102
3 Mike Brown NULL
department_id department_name
101 Human Resources
102 Finance
103 IT
  • INNER JOIN: 두 테이블에서 일치하는 값이 있는 레코드 반환
SELECT employees.employee_name, departments.department_name
FROM employees
INNER JOIN departments 
	ON employees.department_id = departments.department_id;
employee_name department_name
John Doe Human Resources
Jane Smith Finance
  • LEFT JOIN (LEFT OUTER JOIN): 왼쪽 테이블의 모든 레코드와 오른쪽 테이블의 일치하는 레코드 반환
SELECT employees.employee_name, departments.department_name
FROM employees
LEFT JOIN departments
	ON employees.department_id = departments.department_id;
employee_name department_name
John Doe Human Resources
Jane Smith Finance
Mike Brown NULL
  • RIGHT JOIN (RIGHT OUTER JOIN): 오른쪽 테이블의 모든 레코드와 왼쪽 테이블의 일치하는 레코드 반환
SELECT employees.employee_name, departments.department_name
FROM employees
RIGHT JOIN departments 
	ON employees.department_id = departments.department_id;
employee_name department_name
John Doe Human Resources
Jane Smith Finance
NULL IT
  • OUTER JOIN (FULL OUTER JOIN): 왼쪽 또는 오른쪽 테이블에 일치하는 항목이 있는 모든 레코드 반환
SELECT employees.employee_name, departments.department_name
FROM employees
FULL OUTER JOIN departments 
	ON employees.department_id = departments.department_id;
employee_name department_name
John Doe Human Resources
Jane Smith Finance
Mike Brown NULL
NULL IT
  • UNION: 각 select 의 결과 집합(set)을 결합. UNION을 위한 각 열 개수가 모두 동일해야 하고, 데이터 타입이 유사해야 하며, 순서도 동일해야 함.

    • UNION은 중복 rows를 지우므로, 중복을 허용하려면 UNION ALL 사용
SELECT employee_name FROM employees WHERE department_id = 101
UNION
SELECT employee_name FROM employees WHERE department_id = 102;
employee_name
John Doe
Jane Smith
Advanced
  • LIKE: '%'는 N개의 문자, '_'는 1개의 문자를 의미
  • GROUP BY: 같은 값을 가진 rows를 그룹지어 줌. COUNT(), MAX(), MIN(), SUM(), AVG()과 같은 집계함수와 주로 같이 사용됨

    • SELECT문의 모든 열이 집계함수이거나 GROUP BY 절에 존재해야함
    • 집계함수 없이 아래 예시 같이 사용하는 경우에 중복을 제거한 unique rows를 확인해볼 수 있음
select column1, column2, column3, column4
from schema.table
group by 1, 2, 3, 4
  • CASE WHEN condition THEN result1 ELSE result2 END
SELECT
  employee_name,
  CASE
    WHEN department_id IS NULL THEN 'No Department'
    ELSE (SELECT department_name FROM departments WHERE department_id = employees.department_id)
  END AS department_status
FROM employees;
employee_name department_status
John Doe Human Resources
Jane Smith Finance
Mike Brown No Department
  • WITH: 이름을 가진 sub query를 정의한 후 사용하는 구문

    • WITH절을 사용했을 때의 장점이 schatz37님의 블로그에 매우 상세히 설명되어 있어 여러번 읽어보면 좋을 것 같음 (Materilaize 방식의 경우 가독성의 장점 뿐만 아니라 쿼리 성능 개선으로도 이어짐)
WITH 임시테이블1 AS ( SELECT ... ),
WITH 임시테이블2 AS( SELECT ... )
SELECT * FROM 임시테이블1, 임시테이블2
  • Window function: MAX, MIN, RANK 등.. 있지만 ROW_NUM 예시를 적음

    • WINODW_FUNCTION (ARGUMENTS) OVER ([PARTITION BY][ORDER BY][WINDOWING])
    • 아래와 같이 작성시, 중복 데이터 중에서 제일 최근에 데이터 웨어하우스에 추가된 데이터만 골라낼 수 있음 (중복 제거)
SELECT * FROM
(
  SELECT
  	some_data_column,
  	dw_load_timestamp,
  	row_number() OVER (
    	PARTITION BY some_data_column
      ORDER BY dw_load_timestamp DESC
    ) AS rn
  FROM some_table
)
WHERE rn = 1
  • 이 외에도 SELECT INTO, INSERT INTO, EXISTS, HAVING, ANY, ALL 등 존재
  • Key 종류: super key, candidate key, primary key, foreign key, composite key, alternate key
  • Normalization: 1NF, 2NF, 3NF, BCNF
  • Entity Relationship Diagram(ERC)를 그리기 위해 https://dbdiagram.io/home 활용하면 좋음. 특히, 다대다 관계를 가진 경우에 junction(join) table을 만들어주면 좋음
  • Query Optimization: Query plan을 보면 쿼리가 어떻게 실행되고 있는지 확인 가능하여 어떤 부분 튜닝이 필요한지 파악할 수 있음
  • 더 찾아볼 내용: Constraints, Referential integrity, etc...

NoSQL

MongoDB
{
    "$and": [
        {
            "key_name1": {"$regex": "some_value1"}
        },
        {
            "key_name2": {
                "$not": {"$regex": "some_value2"}
            }
        },
        {
        		"key_name3": "some_value3"
        }
    ]
}

Advanced Topics

추가 DB 관련 개념들을 아래에 적고, 관련하여 참고될만한 링크들을 기록합니다.

References