DuckDB in Action·Manning, 2024· 제7장 정독
7 DUCKDB IN THE CLOUD WITH MOTHERDUCK
원서 144–162쪽

질의 하나가 내 기계와 클라우드에 걸쳐 실행된다

여섯 장 동안 초점은 내 컴퓨터에서 직접 데이터셋을 질의하는 데 있었다. 그 방식이 폭넓은 필요를 채우지만, 원격 데이터베이스 서버가 이점을 더해주는 특정한 상황이 있다. 여기서 MotherDuck이 등장한다. 단순화된 규모 확장 전략으로 SQL 분석을 강화하는 해법이며, 이 장의 핵심어는 하나다. 하이브리드 질의 실행—원격에 호스팅된 DuckDB와 내 기계에서 돌아가는 DuckDB를 한 질의 안에서 함께 쓰는 것이다. 질의를 빠르게 돌리려고 클라우드에 어떤 크기의 기계를 띄워야 하는지 고민하는 대신, 질의 자체에 집중하게 하는 것이 설계 목표다.

이 장이 다루는 것
MotherDuck의 착상 · 아키텍처가 그 아래에서 어떻게 작동하는가 · 서버리스 SQL 분석의 쓰임새 · MotherDuck 데이터베이스를 만들고 관리하고 공유하기 · 사용 최적화 요령
절 구성
7.1 – 7.3 및 하위 절 아홉 개, 요약
실린 것
도판 6점(그중 다섯은 화면 갈무리·로고) · 표 1건 · NOTE 3건 · 번호 붙은 목록 없음
연혁
2023년 6월 비공개 베타 · 2023년 9월 일반 공개
클라우드의 관리형 DuckDB CLIENTS · 로컬 Web UI 노트북 · SQL IDE · 대화형 결과 탐색기 Python DuckDB 로컬 연산과 저장 CLI DuckDB 로컬 연산과 저장 하이브리드 질의 실행 HYBRID Service Layer 공유 · 관리 · 비밀 · 모니터링 Ducklings 서버리스 DuckDB 연산 · 표준 SQL Catalog 테이블과 데이터베이스 Storage 내부 저장 · 투명한 유지 관리 외부 질의 간편 복사 EXTERNAL EXTERNAL S3 Parquet · CSV · JSON Databases Postgres · SQLite 질의 엔진 안에서 확장이 테이블이 로컬에 있는지 원격에 있는지 분석하고, 알맞은 실행 엔진을 골라 데이터를 합친다. 필요하면 로컬 데이터의 일부를 조인이나 필터를 위해 서버로 보내거나, 원격 쪽 데이터를 가져와 로컬에서 조인한다.
〈도판 7.1〉MotherDuck 안에 구현된 하이브리드 질의 실행. 원서 그림의 구성을 옮겨 새로 작도했다.
← 좌우로 밀어 보기 →

이 장은 앞의 여섯 장과 성격이 다르다. 지금까지는 오픈소스 데이터베이스 하나를 다뤘는데, 여기서는 그것을 감싼 상업 서비스를 다룬다. 그래서 서술에 값과 요금제가 등장하고, 화면 갈무리가 여섯 도판 가운데 다섯을 차지한다. 저자들은 MotherDuck이 DuckDB Labs 팀과 긴밀히 협력해 클라우드 플랫폼에서 모든 기능의 상호운용성과 가용성을 확보한다고 밝혀두었다.

그럼에도 기술적으로 읽을 대목이 분명하다. MotherDuck은 오픈소스 DuckDB에 선택적으로 붙는(opt-in) 기능으로 확장을 통해 제시된다. 이 확장은 md: 또는 motherduck: 프로토콜로 데이터베이스를 열 때 자동으로 적재되며, 질의 파서와 엔진 양쪽에 통합된다. 파서는 데이터베이스와 공유 관리 기능으로 확장되고, 질의 엔진에서는 확장이 테이블의 소재를 분석해 알맞은 실행 엔진을 고른다.

필요하면 로컬 데이터의 일부가 조인이나 필터를 위해 서버로 보내지고, 또는 원격 쪽의 데이터가 가져와져 로컬에서 조인된다. 7.1.1절, 하이브리드 질의 실행의 정의
이 장의 작업 순서 — 번호 붙은 목록이 하나도 없고, 본문 자체가 세션 기록과 서비스 안내다
  1. 7.1서버리스가 무슨 뜻이고 아키텍처가 어떻게 생겼는지 본다
  2. 7.2가입하고 토큰으로 CLI를 인증한다
  3. 7.3.1로컬 데이터베이스를 클라우드로 올린다
  4. 7.3.2클라우드에서 데이터베이스를 처음부터 만든다
  5. 7.3.3읽기 전용 스냅숏을 공유하고 붙인다
  6. 7.3.4·5S3 비밀을 두고, 실행 위치와 비용을 제어한다
  7. 7.3.6·7자연어로 질의하고, 파이프라인 가운데에 앉힌다
7.1

MotherDuck 소개Introduction to MotherDuck

MotherDuck은 협업형 서버리스 분석 플랫폼이다. 브라우저나 DuckDB의 어떤 API로든 클라우드 데이터베이스와 클라우드 저장소의 데이터를 질의하고 분석하게 해준다. 여기서 서버리스가 뜻하는 바를 저자들이 분명히 정의한다. 사용자가 서버나 클러스터를 띄우거나 데이터베이스 인스턴스를 설정하는 일을 다룰 필요가 없다는 뜻이다. 대신 데이터베이스만 만들면 나머지는 서비스가 알아서 한다.

어떻게 작동하는가 7.1.1

쓰는 길이 여럿이다. 서비스에 가입하면 브라우저에서 돌아가는 MotherDuck 웹 UI에 이르게 된다. 이 UI는 MotherDuck과 통신하는 법을 아는 특별한 DuckDB 판본을 브라우저에서 돌리고 있다. UI는 데이터베이스를 관리하는 도구이면서, 질의를 입력하고 실행하고 결과를 보는 노트북 기반 접근이기도 하다.

다른 입구는 물론 CLI와 파이썬 같은 언어 통합이다. 여기서 md: 확장의 두 갈래 역할이 갈린다.

  • 파서 — 데이터베이스와 공유 관리를 둘러싼 기능으로 확장된다.
  • 질의 엔진 — 확장이 테이블이 로컬에 있는지 원격에 있는지 분석하고 알맞은 실행 엔진을 써서 데이터를 그에 맞게 조인한다. 필요하면 로컬 데이터의 일부를 조인이나 필터를 위해 서버로 보내거나, 원격 쪽 데이터를 가져와 로컬에서 조인한다.

아키텍처의 핵심 구성 요소는 넷이다.

서비스 계층 (service layer)

공유, 관리, 서비스, 모니터링. 안전한 신원 확인, 인가, 관리, 모니터링, 과금 같은 능력을 제공한다.

더클링 (Ducklings)

서버리스 DuckDB 연산 인스턴스다. 질의의 "원격" 부분을 실행한다. 이름 하나에 이 서비스의 성격이 다 담겼다.

카탈로그 (catalog)

데이터베이스와 테이블. 저장 계층에서 관리되는 데이터베이스, 테이블, 뷰를 노출한다.

저장소 (storage)

내부 저장과 유지 관리. 지속적이고 안전하며 최선의 성능을 위해 자동으로 최적화된다.

그리고 이 절에서 뒤의 비용 논의로 이어지는 복선이 하나 놓인다. MotherDuck은—다른 현대적 클라우드 데이터 플랫폼과 마찬가지로—저장과 연산 설비를 분리한다. 이것이 결국 MotherDuck을 쓸 때 발생하는 비용에 중요해진다. 7.3.5절에서 그 분리가 요금표로 어떻게 나타나는지 보게 된다.

왜 MotherDuck을 쓰는가 7.1.2

첫째이자 무엇보다, DuckDB에 기반한 단순하고 성능 좋고 효율적인 데이터 웨어하우스를 제공한다. 저자들의 진단이 이 책 전체의 문제의식과 이어진다. 대부분의 사람은 클라우드 데이터 웨어하우스 사용자의 긴 꼬리에 속하고, 수십 수백 테라바이트의 뜨거운 데이터를 위한 분석 처리 능력을 필요로 하지 않는다. 그런 쓰임새는 분산 시스템에 기반하지 않고 클라우드를 주된 데이터 저장소로, DuckDB를 질의 엔진으로 쓰는 더 단순하고 효율적인 아키텍처에서 이득을 볼 수 있다.

계산해보라는 권유

제3·4장의 가상 시스템—에너지 생산 감시—을 다시 떠올려보라는 것이 저자들의 방식이다. 수백 곳이나 수천 곳의 사이트를 15분 간격으로 감시한다 해도, 연간 10억 레코드에 가까운 것은 나오지 않으며 크기도 대개 몇 기가바이트에 머문다.

이것을 빅데이터라 부르기는 어렵고, MotherDuck이 손쉽게 다룰 수 있는 규모다. 규모를 재보지 않고 분산 시스템을 세우는 습관에 대한 조용한 반문이다.

나머지 세 쓰임새도 정리해둘 만하다.

  • 다종 원천으로 이루어진 데이터 레이크의 질의 엔진 — Parquet으로 저장된 차가운 데이터, S3의 CSV 파일, 또는 Apache Iceberg에 저장된 데이터 같은 것이다. 그 데이터를 MotherDuck에 직접 저장된 뜨거운 데이터와 손쉽게 조인할 수 있다.
  • 데이터 애플리케이션·대시보드·API의 서버리스 백엔드 — 주 트랜잭션 데이터베이스에서 분석 질의를 돌리는 대신, 전용 분석 데이터베이스에서 그 질의를 돌린다.
  • 읽기 전용 스냅숏의 공유 — 다른 MotherDuck 사용자에게 데이터베이스의 스냅숏을 공유할 수 있다. 그들은 공유된 대로 원천을 쓸 수도 있고, 자기 인스턴스의 데이터를 그 데이터셋과 조인할 수도 있다.
7.2

MotherDuck 시작하기Getting started with MotherDuck

책 전체에서 CLI나 파이썬 통합을 썼는데, MotherDuck을 시작하려면 먼저 브라우저를 띄워야 한다. 가입 버튼을 눌러 GitHub 계정이나 구글 계정으로, 또는 이메일 주소를 주고 무료 계정을 만들 수 있다.

〈도판 7.2〉 · 화면 갈무리

원서의 이 도판은 MotherDuck UI의 화면 갈무리다. 왼쪽에 데이터베이스와 그 스키마가 탐색 가능한 트리로 표시되고, 주 화면에 질의와 그 결과가—인라인 막대 차트를 포함해—표시되는 구성이다. 화면 갈무리는 이 문서에서 재현하지 않고 서술로 대신한다.

UI로 MotherDuck 쓰기 7.2.1

웹 기반 UI는 세 가지를 한자리에 모아준다. 모든 원격 데이터베이스에 접근하고 질의하기, 계정 설정 관리하기, 그리고 S3의 원격 데이터 원천을 질의하는 데 필요한 비밀을 저장하기다. UI 밖에서 MotherDuck에 접근하는 데 필요한 API 토큰도 여기서 얻는다.

NOTE — 브라우저 안의 것도 임베디드다

웹 UI에서 돌아가는 데이터베이스도 실은 로컬 임베디드 데이터베이스다! WebAssembly(WASM)로 컴파일된 판본을 쓰고 있어 브라우저 안에서 로컬로 돌아간다. 물론 질의는 앞서 설명한 대로 MotherDuck 클라우드에서 실행된다.

대안으로, MotherDuck 계정이 없거나 원하지 않는다면 shell.duckdb.org를 방문할 수 있다. 본질적으로 CLI처럼 동작하지만 데이터를 영속화하거나 MotherDuck에 접속할 수는 없다.

이 구조에서 따라오는 이득이 있다. SQL 질의의 결과가 브라우저에 로컬한 DuckDB 인스턴스에 캐시되어, 질의 결과를 즉시 정렬하고 피벗하고 필터링할 수 있다. 서버를 다시 부르지 않고 손에서 결과를 만지작거릴 수 있다는 뜻이다.

〈도판 7.3〉 · 화면 갈무리

UI 왼쪽은 데이터베이스와 그 테이블·컬럼, 업로드된 파일을 나열한다. 트리 구조를 탐색할 때 맥락 메뉴가 나타나 데이터베이스를 사용(use)·공유(share)·삭제(drop)·분리(detach)하거나 이름을 복사할 수 있다. 원서의 이 도판은 그 맥락 메뉴의 화면 갈무리다.

질의를 실행하는 경험은 주피터 노트북과 비슷하게 마련되어 있다. 자동 완성과 함께 SQL 문을 쓰고, 실행하고, 셀 아래의 데이터 격자에 렌더링된 질의 결과를 본다. 출력 격자는 로컬 정렬, 출력 컬럼 선택, 컬럼 머리글에 히스토그램과 집계 표시, 데이터의 피벗과 필터링을 지원한다. 그리고 질의를 담은 UI의 상태가 세션을 넘어 유지되므로, 브라우저를 닫고 나중에 이어서 할 수 있다.

CSVParquet 파일을 UI에서 직접 업로드하는 것도 지원된다. 업로드한 파일은 어떤 질의에서든 접근할 수 있게 되고, CREATE TABLE AS SELECT 문을 활용하거나 필요에 맞게 변환하게 된다.

토큰 기반 인증으로 DuckDB를 MotherDuck에 연결하기 7.2.2

DuckDB는 공유 데이터베이스 인스턴스를 열려고 하는 순간에 MotherDuck과의 인증 과정을 촉발한다. 이름 붙은 데이터베이스일 수도 있고 기본 데이터베이스일 수도 있다.

NOTE — 기본 데이터베이스

데이터베이스를 지정하지 않고 MotherDuck에 접속하면 my_db라는 기본 데이터베이스에 접속한다. 그것이 현재 데이터베이스가 된다. 그러면 테이블 이름만 지정해 이 데이터베이스의 어떤 테이블이든 질의할 수 있다. USE 명령이 현재 데이터베이스를 바꾸게 해준다.

기기 인증 흐름 — 링크와 코드 7.2.2
D .open md:

Attempting to automatically open the SSO authorization page in your
default browser.
1. Please open this link to login into your account:
   https://auth.motherduck.com/activate
2. Enter the following code: XXXX-XXXX

# 인증 흐름을 끝까지 따라가면 터미널에 다음이 나온다.
Token successfully retrieved [√]
You can store it as an environment variable to avoid having to log in again:
  $ export motherduck_token='eyJhbGciOiJI..._Jfo'
브라우저에는 기기 확인 메시지가 열린다. 원서 〈도판 7.4〉가 그 화면 갈무리다. 앞으로의 세션에서 자동으로 로그인되기를 바란다면 MotherDuck 토큰을 환경 변수로 추가해야 한다.
# 또는 토큰을 md: 프로토콜의 매개변수로 쓴다.
D .open 'md:?motherduck_token=eyJhbGciOiJI..._Jfo'

# 이 URL 형식은 개별 언어 바인딩에도 그대로 적용된다.
# 제6장에서 논한 파이썬 연결 관리를 떠올려보라.
import duckdb

con = duckdb.connect('md:?motherduck_token=eyJhbGciOiJI..._Jfo')
제6장 6.1.2절에서 duckdb.connect':memory:'나 파일 이름을 넘겼던 그 자리에 URL 하나를 넣으면 클라우드가 된다. API가 바뀌지 않는다는 것이 이 설계의 요령이다. 웹 UI를 쓸지 CLI를 쓸지는 결국 각자의 선택이다.
7.3.1

데이터베이스를 MotherDuck에 올리기Uploading databases to MotherDuck

7.3절의 도입에서 저자들은 이 절의 목표를 정리해둔다. 데이터를 클라우드에 넣고, 동료와 협력자에게 데이터베이스를 공유하도록 도와주는 모든 기능이다. 그리고 첫 예제의 동기가 현실적이다. 제6장에서 파이썬 통합으로 나라 데이터를 담은 데이터베이스를 만들고 데이터셋의 문제를 정리했다. 이제 그 작업을 동료와 공유하고 싶다.

한 방법은 저자들이 "스프레드시트식 삶의 방식"이라 부르는 것이다. countries.duckdb 저장 파일을 이메일에 첨부하거나 네트워크 폴더에 복사하고 넘어가는 것. 더 낫고 덜 부서지기 쉬운 공유 방법이 MotherDuck이다.

잠금을 풀고 올리기 7.3.1
D .open countries.duckdb   # 그 저장 파일이 없어도 걱정할 것 없다. 이 명령이 만들어준다.

-- 갓 만든 데이터베이스라면 특히, cities 테이블을 더해둔다.
D CREATE TABLE cities AS
  SELECT *
  FROM (VALUES ('Amsterdam', 1), ('London', 2)) cities(Name, Id);

# 공유하기 전에 분리해서 모든 잠금을 풀어야 한다.
# 인수 없는 .open으로 인메모리 DB로 돌아간 뒤 LOAD motherduck; 를 하거나,
# 한 번에 .open md: 를 부른다. 후자는 CLI를 MotherDuck 기본 DB에 붙인다.
D .open md:
D CREATE DATABASE "countries" FROM 'countries.duckdb';
업로드 시간을 저자들이 실측해 적어두었다. 데이터베이스 크기와 인터넷 속도에 따라 다르지만, 실험에서 일반 노트북에서 40Mbps 업링크의 가정용 인터넷으로 16GB 데이터베이스를 올리는 데 약 40분이 걸렸다. 집필 시점에 MotherDuck이 활발히 개선 중인 주제이며 업로드 성능이 곧 나아질 것으로 기대된다.
당장의 우회로도 제시된다. 지금으로서는 데이터베이스를 Parquet 파일로 내보내 클라우드 저장소에 올리고, 그 파일들로 MotherDuck에서 데이터베이스를 만드는 편이 더 빠를 수 있다. 제5장에서 배운 COPY … TO … (FORMAT PARQUET)가 여기서 쓸모를 찾는다.
수치를 재보면 어림이 섞여 있다. 40Mbps로 40분이면 약 12GB가 지나간다. 16GB를 40분에 옮기려면 53Mbps가 필요하다. 전송 중 압축이 있었거나, 회선이 공칭보다 빨랐거나, 아니면 두 수치가 모두 어림이라는 뜻이다. 자기 환경의 소요 시간을 추정할 때는 업링크 속도로 직접 나눠보는 편이 안전하다.
접두사를 붙이거나 데이터베이스를 바꾸거나 7.3.1
-- 세션은 아직 MotherDuck 기본 DB에 붙어 있다. 접두사를 쓰거나 데이터베이스를 바꾼다.
D FROM countries.cities;    -- 접두사를 쓰는 방식

D USE countries;           -- USE는 원격 MotherDuck 카탈로그에서 이름을 찾는다
D FROM cities;              -- 그러면 접두사를 생략할 수 있다

# 이미 CLI를 나왔다면 새 데이터베이스에 직접 접속해도 된다.
$ duckdb
D .open md:countries
NameId
varcharint32
Amsterdam1
London2
USE 문이 원격 카탈로그를 조회한다는 점을 눈여겨볼 만하다. 제3장에서 로컬 스키마를 다룰 때 쓴 것과 같은 문장이 이제 네트워크 저편을 가리킨다.
NOTE — 기억해야 할 규칙

로컬 데이터베이스를 MotherDuck에 올릴 때 로컬 이름과 원격 이름은 언제나 달라야 한다. 이 규칙을 잊고 같은 이름을 주면 오류를 만날 위험이 있다.

같은 이름이 부르는 두 가지 오류 7.3.1
D create database "countries" from 'countries' ;
Error: Catalog Error: Database 'countries' has already been
created and attached

# 그리고 현재 데이터베이스를 올리려 하면 이 오류가 난다.
Error: Binder Error: Database "countries.duckdb" is already attached
with alias "countries"
예제에서 이 오류들을 피한 것은 .open md:를 불렀기 때문이다. 그 한 줄이 두 가지를 해주었다. MotherDuck 확장을 적재하고, MotherDuck 기본 데이터베이스에 접속한 것이다. 또 다른 선택은 인수 없는 .open이며, 인메모리 데이터베이스로 바꿔준다.
7.3.2

MotherDuck에서 데이터베이스 만들기Creating databases in MotherDuck

앞 절에서는 기존 데이터베이스를 올렸다. 대안으로 클라우드에서 직접 스키마를 처음부터 지을 수 있다. 그리고 그 편이 나은 경우를 저자들이 분명히 짚는다. 스키마의 내용이 대체로 다른 공용 클라우드에 저장된 파일에 의존한다면, 그것을 먼저 로컬 DuckDB 인스턴스로 내려받고 그 데이터베이스를 다시 MotherDuck에 올리는 것은 시간과 자원의 낭비다. 대부분의 경우 MotherDuck이 내 로컬 시스템보다 S3 버킷에 더 가까이서 돌아가므로, 클라우드에서 MotherDuck으로 직접 들이는 편이 시간과 돈을 아껴줄 수 있다.

만들고, 확인하고, 옮겨 앉고, 지우기 7.3.2
D CREATE DATABASE "my-test";

D SHOW DATABASES;      -- .databases CLI 명령으로도 된다
database_name
varchar
my-test
본문은 "데이터베이스 이름에는 특수 문자가 들어갈 수 없다. 영숫자와 밑줄만 허용된다"고 적어놓고, 바로 다음 줄에서 하이픈이 든 "my-test"를 만든다. 규칙과 예제가 어긋난다. 그리고 하이픈이 들어갔기 때문에 이름을 인용부호로 감싸야 하는데, CREATE·DROP에서는 겹인용부호 "my-test"를 쓰고 USE에서는 홑인용부호 'my-test'를 쓴다. 식별자를 감싸는 표준 표기는 겹인용부호이니, 따라 할 때는 한쪽으로 맞추는 편이 안전하다.
-- CREATE DATABASE는 데이터베이스만 만들고 세션을 그리로 바꾸지는 않는다.
D USE 'my-test';
D SELECT current_database();
current_database()
varchar
my-test
-- 테이블을 만들고 행을 넣어본다.
D CREATE TABLE people (name varchar, born date);
D INSERT INTO people VALUES ('Mark', '1989-01-01'), ('Hannes', '1990-01-01');

D SELECT count(*) FROM people;              -- → 2
D SELECT count(*) FROM "my-test".people;    -- 데이터베이스 이름을 접두사로 붙여도 같다

-- 감을 잡았으면 시험용 데이터베이스를 지운다.
-- 쓰고 있는 데이터베이스는 지울 수 없으므로 먼저 기본 DB로 옮겨 앉는다.
D USE my_db;
D DROP DATABASE "my-test";
쓰고 있는 것은 지울 수도 분리할 수도 없다는 규칙이 이 장에서 세 번 되풀이된다. 업로드 전에 분리할 때, 시험 데이터베이스를 지울 때, 그리고 뒤에서 공유를 분리할 때다. 서비스가 원격이라는 사실과 무관하게, 자기가 앉아 있는 의자는 치울 수 없다.
← 좌우로 밀어 보기 →
7.3.3

데이터베이스 공유하기Sharing databases

MotherDuck은 데이터베이스의 읽기 전용 스냅숏을 공유하는 능력을 제공한다. 데이터를 공유하는 데만 좋은 것이 아니라 협업 분석과 기능의 공유에도 좋다. 그 까닭이 이 절의 핵심이다. 스냅숏은 데이터만이 아니라 모든 뷰를 담으며, 그와 함께 그 뷰를 만드는 동안 들인 모든 노력을 담는다. 저자들의 표현으로는 "스테로이드를 맞은 스프레드시트"다.

제3·5장에서 뷰가 논리를 감싸두는 수단이라고 배웠다. 그 뷰가 그대로 건너간다는 것은, 정제 규칙과 단위 환산과 조인 논리까지 함께 건너간다는 뜻이다. 파일 하나를 이메일로 보내는 것과는 다른 종류의 공유다.

공유 링크 만들기, 붙이기, 서술하기 7.3.3
D CREATE SHARE shared_countries   -- 공유될 데이터베이스의 이름
  FROM countries;                  -- 공유할 데이터베이스의 이름
share_url
varchar
md:_share/countries/1acb80cf-d872-4fab-8077-64975cce0452
공유 데이터베이스를 만드는 데 걸리는 시간은 현재 원본 데이터베이스의 크기에 달려 있다. 저자들의 경우 16GB 데이터베이스가 공유되는 데 약 1분이 걸렸다.
같은 16GB인데 올리는 데 40분, 공유하는 데 1분이다. 사십 배 차이다. 공유는 이미 클라우드에 있는 데이터의 서버 쪽 스냅숏이고, 업로드는 가정용 업링크를 건너는 일이다. 두 수치를 나란히 놓으면, 이 서비스에서 무엇이 비싼 연산인지가 선명해진다. 병목은 계산이 아니라 이동이다.
-- 붙이려면 상대도 MotherDuck 계정이 있어야 한다.
D ATTACH 'md:_share/countries/1acb80cf-d872-4fab-8077-64975cce0452'
  AS shared_countries;

D .mode line
D DESCRIBE SHARE shared_countries;

   share_name = shared_countries
   share_link = md:_share/countries/1acb80cf-d872-4fab-8077-64975cce0452
database_name = countries
  database_id = 9d7586ac-add9-46dc-a4fb-def6b42f0f7c
  snapshot_id = 041a5ba9-8cf4-471b-af13-1bec75a0b3ce

D LIST SHARES;              -- 내가 만든 공유 목록
출력이 원본 링크와 원래 이름, 그리고 데이터베이스와 최신 스냅숏의 ID를 함께 비춰준다. snapshot_id가 따로 있다는 사실이 다음 문단의 제약을 예고한다.
-- 출간 시점에 공유는 원본이 바뀌어도 자동으로 갱신되지 않는다.
-- 스키마와 데이터의 변경은 공유하는 쪽에서 명시적으로 전파해야 한다.
D UPDATE SHARE shared_countries;

-- 더 이상 필요 없는 공유는 DETACH로 없앤다.
-- 그 공유에 접속해 있다면 먼저 다른 데이터베이스로 옮겨 앉아야 한다.
D USE my_db;
D DETACH shared_countries;
공유는 스냅숏이지 뷰가 아니다. 제5장에서 뷰와 테이블의 차이—매번 다시 계산하는가, 한 시점을 찍어두는가—를 배웠는데, MotherDuck의 공유는 명백히 후자다. 받는 쪽이 늘 최신 데이터를 볼 것이라고 가정하면 낭패를 본다.
← 좌우로 밀어 보기 →

저자들이 준비해둔 공유들

저자들은 독자를 위해 흥미로운 공유 몇 개를 준비해두었다. 제3·4장의 완전한 데이터베이스—그 장들에서 만든 모든 테이블과 뷰를 담은—와 스택 오버플로의 완전한 덤프, 그리고 표본 데이터 모음이다.

붙여서 바로 써볼 수 있는 공유 셋 7.3.3
-- ① 제3·4장의 데이터베이스
D ATTACH
    'md:_share/duckdb_in_action_ch3_4/d0c08584-1d33-491c-8db7-cf9c6910eceb'
  AS duckdb_book_ch3_and_4;
D USE duckdb_book_ch3_and_4;
D SHOW tables;

-- ② 스택 오버플로 덤프
D ATTACH
    'md:_share/stackoverflow/6c318917-6888-425a-bea1-5860c29947e5'
  AS stackoverflow_analysis;
D USE stackoverflow_analysis;
D SELECT count(*) FROM posts;
58,329,356

-- ③ 표본 데이터 모음. 테이블에 접두사가 붙어 있어 완전한 이름으로 접근한다.
D ATTACH 'md:_share/share_sample_data/23b0d623-1361-421d-ae77-62d701d471e6'
  AS sample_data;
스택 오버플로 공유는 5,832만 9,356건의 질문과 답변을 담고 있다. 제3·4장의 SQL을 실제 규모의 데이터에 대고 써볼 수 있는 좋은 말뭉치다. 제2장에서 227행짜리 CSV로 시작한 여정이 여기까지 왔다.
표 7.1  표본 데이터베이스의 테이블 목록. 완전한 이름으로 접근한다(예: sample_data.nyc.taxi).
이름테이블 이름행 수설명
Hacker Newshn.hacker_news3.9M해커 뉴스 댓글의 표본
NYC 311 민원 데이터nyc.service_requests32.5M전화와 웹을 통한 뉴욕시 311 민원 상담 전화 요청
대기질who.ambient_air_quality41k세계보건기구의 과거 대기질 데이터
택시 운행nyc.taxi3.3M2020년 11월 뉴욕 옐로캡 운행 데이터
승차 공유nyc.rideshare18.1M뉴욕의 승차 공유(Lyft, Uber 등) 운행
살펴본 김에

본문이 완전한 이름의 예로 든 것은 sample_data.nyc.yellow_cab_nyc_2022_11이다. 그런데 표 7.1에는 그 이름이 없다. 택시 데이터의 테이블 이름은 nyc.taxi로 적혀 있고, 설명은 2020년 11월이라고 되어 있다. 예시 이름에 박힌 2022_11과도 어긋난다.

예시가 옛 이름이었을 가능성이 있으니, 실제로 붙여본 뒤 SHOW tables로 확인하는 편이 확실하다. 이 문서의 표에는 표 7.1에 인쇄된 이름을 그대로 실었다.

저자들의 맺음말이 이 공유들의 목적을 말한다. 이 데이터셋들은 여러 다른 주제를 제공하니, SQL 실력을 단련하거나 DuckDB와 MotherDuck의 가능성을 탐색하는 데 쓸 수 있다.

7.3.4

S3 비밀 관리와 S3 버킷에서 데이터 적재하기Managing S3 secrets and loading Data from S3 buckets

앞 장들에서는 http://https:// 프로토콜로 직접 접근할 수 있는 파일에서 데이터를 들였다. 그러나 파일 저장에 아마존 S3를 쓰는 일이 잦고, 그것은 s3:// 프로토콜로 접근한다. MotherDuck과 DuckDB는 그 프로토콜도 말하지만, 여러분을 대신해 아마존 S3에 인증하려면 비밀이 필요하다.

여기에 MotherDuck을 쓸 때의 이점이 하나 붙는다. 비밀을 그들의 시스템에 저장해두면 MotherDuck에 접속된 모든 세션에서 쓸 수 있다. 웹 UI로 하거나 전용 문장으로 한다.

비밀을 두고, 쓰고, 치우기 7.3.4
D CREATE OR REPLACE SECRET (
     TYPE S3,
     KEY_ID 'access-key',
     SECRET 'secret-key',
     REGION 'us-east-1'
  );

-- 그러면 보안 S3 버킷의 데이터를, 앞서 http나 파일시스템으로 질의했던
-- 여느 CSV나 Parquet 파일과 똑같이 질의할 수 있다.
D CREATE OR REPLACE TABLE mytable AS
  FROM 's3://...';

-- 버킷 작업이 끝나면 비밀을 치운다.
D DROP SECRET (TYPE s3);
비밀을 만들 때 새겨둘 것이 둘이다. DuckDB 0.10.0 이전 판본에서는 SECRET 객체를 하나만 가질 수 있다. 그리고 영구 S3 비밀만 쓸 수 있으며, 세션 동안만 유효한 임시 S3 비밀은 현재 지원되지 않는다.
임시 자격 증명을 쓸 수 없다는 제약은 운영 환경에서 무겁게 다가온다. 수명이 짧은 토큰을 발급해 돌려쓰는 관행을 이 자리에서는 적용할 수 없다는 뜻이므로, DROP SECRET으로 손수 치우는 절차를 습관으로 삼아야 한다.
7.3.5

데이터 수집과 MotherDuck 사용 최적화Optimizing data ingestion and MotherDuck usage

클라우드 기반 해법을 돌리는 데는 여러 종류의 비용이 든다. 연산, 저장, 데이터 인그레스, 데이터 이그레스가 그중에 있다. 그리고 이 절의 핵심 능력이 여기서 나온다. MotherDuck 확장은 함수가 클라우드에서 실행될지 내 로컬 기계에서 실행될지에 대한 촘촘한 제어를 준다.

저자들이 든 반례가 명료하다. 이미 로컬에 있는 큰 Parquet 파일을 처리하고 싶다면, 그것을 S3에 먼저 올린 다음 MotherDuck에서 처리를 돌리는 것은 무의미하다. MotherDuck의 연산 비용과 S3의 이그레스 비용을 둘 다 물게 되기 때문이다. 인터넷이 빠르다면 로컬에서 처리하고 내 기계에서 MotherDuck으로 그냥 올리는 편이 아마 낫다.

확장은 read_ 접두사로 시작하는 모든 함수—read_json, read_csv_auto 같은 것—를 확장해 MD_RUN 매개변수를 지원하게 만든다. 아래에서 원천과 MD_RUN 값을 갈아 끼워, 함수가 어디서 실행되고 그것이 무엇을 뜻하는지 확인할 수 있다.

그렇게 되는 이유
새겨둘 것
← 좌우로 밀어 보기 →

차가운 저장과 뜨거운 저장

무료 등급을 넘어서면 MotherDuck은 표준 등급을 제공한다. 저자들이 적어둔 집필 시점의 값은 다음과 같다.

$25 월 기본료

100GB 저장과 100 연산 시간이 포함된다.

$0.08 GB당 추가 차가운 저장

표준 사용 할당량을 넘는 자원이 필요하면 추가로 구매할 수 있다.

$0.40 시간당 추가 연산

다만 이것은 클라우드 사용에만 해당하며 로컬 실행에는 해당하지 않는다.

두 저장의 뜻이 다르다. 차가운 저장(cold storage)은 데이터베이스와 파일을 위한 영속 저장을 가리킨다. 뜨거운 저장(hot storage)은 질의를 실행하는 데 쓰이며 메모리 사용에 해당하고, 필요에 맞게 미세 조정한 최대치로 제한할 수 있다. DuckDB에서 뜨거운 저장은 상한까지 자동으로 확장되고 초와 기가바이트 단위로 계량된다.

TIP — 왜 그 분리가 이치에 맞는가

저자들이 근거로 든 관찰이 인상적이다. 연구에 따르면 처리되는 데이터의 대단히 큰 비율이 24시간이 안 된 것이다. 데이터가 일주일 묵을 무렵이면 가장 최근 날의 데이터보다 질의될 가능성이 약 20배 낮아진다.

여기에 하나가 더 붙는다. 워크로드의 크기가 전체 데이터 크기보다 작은 일이 잦다. 예컨대 대시보드는 통상 집계된 데이터로 만들어진다. 지난 한 시간의 데이터는 새로 집계하면 최신 변경을 놓치지 않고, 일주일보다 더 거슬러 올라가는 것은 미리 집계해 별도 테이블로 저장할 수 있다. 집계는 통상 행이 훨씬 적고 저장을 훨씬 덜 잡아먹는다.

그래서 비용을 통제하려면 불필요한 데이터를 MotherDuck에 직접 저장하지 말고 필요할 때 적재하거나 처리하며, 한 번에 유지할 뜨거운 저장의 최대량을 합리적으로 설정해야 한다. 다만 쓸 수 있는 뜨거운 저장의 양이 질의 성능에 영향을 준다는 저울을 잊지 말아야 한다.

7.3.6

AI로 데이터 질의하기Querying your data with AI

SQL이 영어와 어느 정도 닮았다고는 하나 익히기가 늘 쉽지는 않고, 처음 접하는 사람에게는 어떤 구성물은 노골적으로 겁을 준다.

NOTE — SQL의 옛 이름

SQL의 이전 이름은 SEQUEL(Structured English QUEry Language)이었다. 관계형 모델에 기반한 또 다른 질의 언어 QUEL을 두고 만든 말장난이었다. 그 이름은 결국 상표권 문제로 폐기되었다.

그런데 구조화된 언어로 된 질의는 곧잘 생성될 수 있다. MotherDuck은 자연어로 데이터를 질의하게 해주는 생성 AI 기능을 제공한다. 데이터를 서술할 수 있고, SQL 문을 생성해주거나 기존 문장을 고쳐준다. 작동 원리를 저자들이 분명히 밝혀둔다. 데이터베이스 스키마를 상세한 프롬프트와 여러분의 질문과 함께 대규모 언어 모델(LLM)로 보내면, 그것이 요청된 SQL 문을 생성하고 선택적으로 실행한다.

두 번 부르면 두 번 다르게 답한다 7.3.6
D .mode line
D CALL prompt_schema();

summary = The database contains tables for storing data related to votes,
tags, posts, post links, badges, users, and comments.
Run Time (s): real 3.672 user 0.007355 sys 0.002674

-- 한 번 더 부르면 대개 다른 응답을 얻는다.
D CALL prompt_schema();

summary = The data in the database is about votes, tags, posts, post links,
badges, users, and comments.
Run Time (s): real 3.054 user 0.007354 sys 0.003175
저자들의 평가가 담백하다. 몇 초가 걸리는데 결과가 그리 인상적이지 않다. 테이블 목록만 봐도 같은 출력을 얻을 수 있었다. 같은 호출에 다른 답이 오는 것이 처음에는 놀라워 보일 수 있는데, LLM은 확률적 모델이며 매번 같은 응답을 돌려준다고 보장되지 않는다는 사실을 감안하면 그렇지 않다.
이 책이 AI 기능을 다루는 태도가 여기서 드러난다. 홍보하지 않고 재보고, 되는 것과 안 되는 것을 나누어 적는다. 기능 소개 절에서 "인상적이지 않다"고 쓰는 교재는 흔치 않다.
자연어로 묻고, 어떤 SQL이 쓰였는지 되묻기 7.3.6
-- "가장 인기 있는 태그는 무엇인가?"는 유효한 SQL이 아니므로
-- prompt_query라는 특별한 pragma로 그 사실을 알려야 한다.
-- pragma는 컴파일러나 질의 파서에게 입력을 어떻게 처리해야 하는지 알려주는 특별한 지시다.
D .mode duckbox
D pragma prompt_query('What are the most popular tags?');
TagNameCount
varcharint64
javascript2479947
python2113196
java1889767
c#1583879
php1456271
android1400026
html1167742
jquery1033113
c++789699
css787138
10 rows    2 columns  ·  real 3.763
어떻게 했는지는 보여주지 않은 채 열 행이 돌아오고, 실제로 의미가 있는 값이다. 그러면 어떤 SQL이 쓰였는지 궁금해지는데, prompt_sql 프로시저로 알아낼 수 있다. 다만 확률적으로 조금 달랐을 수도 있다는 점은 염두에 두어야 한다.
D .mode line
D call prompt_sql('What are the most popular tags?');

query = SELECT TagName, Count
FROM tags
ORDER BY Count DESC;
Run Time (s): real 5.425 user 0.010331 sys 0.005074
본문은 이 생성 결과를 "테이블 컬럼을 정렬과 limit과 함께 쓸 만큼 똑똑하다"고 평했다. 그런데 인쇄된 질의에는 LIMIT이 없다. 그러면서도 앞의 prompt_query는 정확히 열 행을 돌려주었다. 생성된 SQL과 실제 실행된 SQL이 달랐거나, 평이 과했거나, 인쇄에서 한 줄이 빠진 것이다. LLM이 만든 질의를 그대로 믿지 말고 눈으로 확인하라는 이 절의 교훈이, 뜻하지 않게 이 자리에서 증명된다.
← 좌우로 밀어 보기 →
프롬프트를 손질해 조인을 없애기 7.3.6
-- 더 복잡한 질문을 던져본다.
D pragma prompt_query("Which 5 questions have the most comments, what is the
  post title and comment count");
-- → 5행. real 19.695

D call prompt_sql("Which 5 questions have the most comments, ...");

query = SELECT p.Title, COUNT(c.Id) AS comment_count
FROM posts p
  JOIN comments c ON p.Id = c.PostId AND p.PostTypeId = 1
GROUP BY p.Title
ORDER BY comment_count DESC
LIMIT 5;
Run Time (s): real 4.795
저자들이 흥미롭다고 짚은 대목. posts 테이블에서 PostTypeId = 1인 항목이 모두 답변이 아니라 질문임을 모델이 감지하거나 알고 있다. 그리고 그 이유를 조심스럽게 추측한다. "아마 스택 오버플로 데이터셋으로 훈련되었기 때문일 것이다."
-- 댓글 수는 posts 테이블의 컬럼이므로 comments와의 조인이 필요하지 않다.
-- 프롬프트를 손질해 posts 테이블만 쓰는 질의를 만들게 해본다.
D call prompt_sql("System: No joins! User: Which 5 questions have the most
  comments, what is the post title and comment count");

query = SELECT Title, CommentCount
FROM posts
WHERE PostTypeId = 1
ORDER BY CommentCount DESC
LIMIT 5;
Run Time (s): real 3.587
훨씬 낫다. 스키마를 아는 사람이 프롬프트에 한 줄을 더해 불필요한 조인을 없앤 것이다. 도메인 지식이 없으면 프롬프트도 손질할 수 없다는 사실이 이 짧은 왕복에 담겨 있다.
-- prompt_fixup으로 SQL을 고칠 수도 있다. 악명 높은 "GROUP BY를 잊었다" 같은 것.
D call prompt_fixup("select postTypeId, count(*) from posts");
query = SELECT postTypeId, COUNT(*) FROM posts GROUP BY postTypeId
Run Time (s): real 12.006

-- 또는 잘못된 조인 컬럼 이름을 고치는 데 쓴다.
D call prompt_fixup("select count(*) from posts join users on
  posts.userId = users.userId");
query = SELECT COUNT(*) FROM posts JOIN users ON
  posts.OwnerUserId = users.Id
Run Time (s): real 2.378
두 번째 예가 더 인상적이다. 존재하지 않는 컬럼 이름(userId)을 실제 스키마의 이름(OwnerUserId, Id)으로 바꿔놓았다. 스키마를 프롬프트에 함께 보내는 설계가 여기서 값을 한다.
← 좌우로 밀어 보기 →
덧붙인 표  7.3.6절에 등장하는 네 가지 프롬프트 기능과 원서에 기재된 실행 시간. 원서에는 이런 표가 없다.
기능형태하는 일기재된 시간
prompt_schema()CALL데이터베이스 스키마를 요약해 서술한다3.672 · 3.054
prompt_query(…)pragma자연어 질문을 SQL로 바꿔 실행하고 결과를 돌려준다3.763 · 19.695
prompt_sql(…)CALL자연어 질문에 대해 생성된 SQL 문만 돌려준다5.425 · 4.795 · 3.587
prompt_fixup(…)CALL잘못된 SQL 문을 고쳐 돌려준다12.006 · 2.378
살펴본 김에

본문은 "이 AI 프롬프트의 실행 시간은 2초에서 10초 사이이며, 그 시간의 대부분이 LLM 안에서 소비된다"고 적었다. 그런데 같은 절에 인쇄된 아홉 개 측정값 가운데 둘이 그 범위를 벗어난다. prompt_query의 댓글 질문이 19.695초, prompt_fixup의 GROUP BY 예제가 12.006초다.

범위를 넘긴 둘이 우연히 가장 복잡한 질문가장 짧은 입력이라는 점도 눈에 걸린다. LLM 호출의 지연은 입력 길이만으로 예측되지 않으니, 대화형 대시보드의 응답 시간을 이 기능에 걸어두는 설계는 피하는 편이 좋다.

저자들의 총평은 균형이 잡혀 있다. 데이터베이스에 대한 질의를 생성하는 데 LLM을 쓰는 것에는 잠재력이 많고, 특히 모델이 데이터베이스 스키마로 증강될 수 있을 때 그렇다. 다만 보고서와 애플리케이션을 위해 전문가가 쓰고 조율한 질의를 대체하지는 못할 가능성이 크다. 그럼에도 데이터베이스 시스템을 훨씬 넓은 청중에게 접근할 수 있게 만들어줄 것이다. 무엇을 대체하고 무엇을 넓히는지를 나누어 적는 이 절제가 미덥다.

덧붙여, 2024년 초에 MotherDuck은 FixIt을 도입했다. 같은 기술에 기반한 UI용 빠른 SQL 오류 수정기이며, 구문이 잘못된 SQL 문이나 UI에 직접 인라인으로 있는 문장을 고쳐준다.

7.3.7

통합Integrations

MotherDuck은 다양한 데이터 전송, 비즈니스 인텔리전스, 데이터 시각화 도구를 지원한다. 저자들이 그린 그림의 구도는 이렇다. 왼쪽에서 오른쪽으로 가면서 중간에 선택적인 변환을 두면, MotherDuck이 파이프라인의 가운데에 앉을 수 있다.

원천은 직접 수집되거나 추가 서비스를 거쳐 수집되어 MotherDuck의 저장소에 놓이고, 그다음 비즈니스 인텔리전스 쓰임새나 특정 데이터 과학 도구가 그것을 질의한다. 그리고 한 가지가 더 붙는다. LLM 모델을 위한 검색 증강 생성(RAG)의 정보 검색 구성 요소로도 쓸 수 있다.

〈도판 7.6〉 · 로고 모음

원서의 이 도판은 지원 도구들의 로고를 왼쪽(원천)에서 오른쪽(소비)으로 배치한 그림이다. 상표가 걸린 로고 모음이므로 이 문서에서 재현하지 않고 구도만 서술로 대신한다. 요지는 MotherDuck이 파이프라인의 어느 한쪽 끝이 아니라 가운데에 앉는다는 것이다.

기존의 어떤 DuckDB 통합과 드라이버도 MotherDuck과 함께 작동한다. 그러므로 DuckDB를 쓸 수 있는 모든 곳에서 MotherDuck을 쓸 수 있다. 7.3.7절을 맺는 문장

그리고 그것에 필요한 것이 무엇인지를 저자들이 한 문장으로 적어둔다. 데이터베이스 접속 문자열에 md: 접두사를 끼워 넣고 ?motherduck_token=<token> 매개변수를 덧붙이는 것뿐이다. 제6장에서 duckdb.connect(':memory:')였던 자리가 duckdb.connect('md:?motherduck_token=…')으로 바뀌는 것 하나로 로컬이 클라우드가 된다. API를 바꾸지 않고 URL만 바꾸는 이 설계가 이 장 전체를 관통하는 요령이다.

SUMMARY

제7장이 남긴 일곱 문장Summary

  1. MotherDuck은 브라우저에서 클라우드 저장소의 데이터를 질의하고 분석하기 쉽게 만들어주는 서버리스 데이터 분석 플랫폼이다.
  2. md: 프로토콜을 써서 DuckDB CLI와 파이썬 및 다른 언어 통합에 매끄럽게 통합되며, 그 프로토콜이 MotherDuck 확장을 자동으로 적재한다.
  3. MotherDuck은 구조화된 데이터를 저장하고, SQL로 질의하고, 다른 사람과 공유하게 해준다.
  4. 이 서비스의 핵심 원칙 하나는 사용의 편의다. 인스턴스나 클러스터, 웨어하우스를 설정하거나 띄울 필요가 없다. 로컬에서 작업할 때와 같은 도구, 같은 생태계 안에서 SQL을 쓰고 제출하면 된다.
  5. 많은 경우 데이터 원천에 더 가까이 있기 때문에 로컬보다 MotherDuck에서 데이터를 훨씬 빠르게 수집할 수 있다.
  6. 로컬·원격·공유 데이터셋을 스키마와 관계의 완전한 이름을 써서 손쉽게 조인할 수 있다.
  7. 이 플랫폼은 자연어로 데이터셋을 질의하는 지원도 제공해, SQL을 배우지 않은 사람도 분석 데이터베이스의 이득을 볼 수 있게 한다.