지금까지는 모두 가까운 데이터였다
DUCKDB'S HTTPFS EXTENSION
앞선 모든 장에서 DuckDB로 다룬 것은 지역 데이터였다. MySQL 데이터베이스에 있든 CSV·JSON·Parquet 파일에 있든 모두 내 컴퓨터의 것이었다. 그러나 실제 상황에서 다루는 데이터는 통상 원격 서버에 놓이며, 여러 곳에서 오는 일이 잦다. 다행히 DuckDB는 원격 데이터셋에 접근하게 해 주는 httpfs 확장을 제공한다. 게다가 DuckDB는 Hugging Face가 호스팅하는 데이터셋에 접근하는 지원까지 제공한다.
httpfs 확장은 자동 적재 가능한(autoloadable) 확장이며, 원격 파일의 읽기와 쓰기를 허용하는 파일 시스템을 구현한다. 이 확장으로 DuckDB는 HTTP와 HTTPS 프로토콜을 통해 파일을 직접 읽고 쓸 수 있으며, 먼저 지역에 내려받을 필요가 없다. 다음 상황에서 특히 도움이 된다.
- 용량을 넘길 때 지역 저장 공간을 넘어서는 큰 데이터셋을 다룰 때.
- 실시간일 때 실시간이거나 자주 갱신되는 데이터에 접근할 때.
- 여러 곳일 때 여러 원격 원천에 분산된 데이터에 질의할 때.
- 클라우드일 때 클라우드 저장과 매끄럽게 통합할 때.
이는 효율적인 원격 데이터 분석을 가능하게 하며, 클라우드 기반 데이터 레이크, 웹 API, 분산 파일 시스템이 얽힌 상황에 이상적이다. httpfs 확장은 CSV, Parquet을 비롯해 DuckDB가 자체적으로 지원하는 여러 파일 형식을 지원한다.
httpfs 확장은 Amazon S3(Simple Storage Service) API를 써서 객체 저장에 대한 읽기와 쓰기, 그리고 파일 글로빙도 지원한다.
import duckdb conn = duckdb.connect() conn.execute(''' INSTALL httpfs; LOAD httpfs; ''')
제2장에서 엑셀을 읽기 위해 spatial을, MySQL에 붙기 위해 mysql을 설치했던 그 자리에 이번에는 httpfs가 놓인다. DuckDB의 확장 목록을 처음 소개한 대목에서 httpfs가 맨 앞에 있었던 것을 떠올려도 좋다. 여섯 장을 지나 그 첫 항목의 차례가 온 셈이다.
이 장은 결과가 스크린숏 도판이고, 절차의 절반이 Hugging Face 웹사이트를 누르는 순서다. 그래서 이 페이지는 다음을 지켰다. 도판의 표 값은 옮기지 않고, URL·명령·절차의 순서를 정확히 옮기는 데 힘을 쏟았다. URL은 원서 지면에서 대부분 오른쪽이 잘려 있으므로, 다른 곳의 인쇄분과 대조해 복원할 수 있는 것만 복원했다.
표기 규칙은 앞 일곱 장과 같다. 복원한 부분은 점선 밑줄, 복원하지 않은 부분은 …다. 이 권의 구조색은 금석(金石)에 낀 청동 녹빛, 동록(銅綠)이다.
원격 CSV — 결국 전부 온다
ACCESSING CSV FILES
httpfs 확장이 있으면 원격에 놓인 파일에 접근할 수 있다. 웹 서버에 파일이 있다면 읽거나 쓰려는 파일을 곧바로 가리키는 URL을 그냥 쓰면 되고, DuckDB가 먼저 지역에 내려받지 않고 처리한다. 그런데 GitHub 같은 사이트에 저장된 파일이라면 원시 파일(raw file)을 담은 URL을 얻어야 한다.
표본 CSV 파일의 URL을 웹 브라우저에 띄우고 Raw 버튼을 누른다. 원시 CSV 파일을 적재하는 페이지로 옮겨지며, 그 페이지의 URL을 복사한다. 이 예에서는 Amazon.com의 과거 주가를 담은 파일이다.
https://raw.githubusercontent.com/weimenglee/DuckDB_Book/main/AMZN.csv# 파일 전체를 질의해 pandas DataFrame으로 바꾼다 conn.execute(''' SELECT * FROM 'https://raw.githubusercontent.com/weimenglee/DuckDB_Book/main/AMZN.csv' ''').df() # 원격 데이터에 여과도 걸 수 있다 — 2018년의 모든 행 conn.execute(''' SELECT * FROM 'https://raw.githubusercontent.com/weimenglee/DuckDB_Book/main/AMZN.csv' WHERE year(Date) = 2018; ''').df()
여러 CSV 파일을 한꺼번에 읽으려면 read_csv() 함수에 목록을 넘긴다.
conn.execute(''' SELECT * FROM read_csv([ 'https://raw.githubusercontent.com/weimenglee… 'https://raw.githubusercontent.com/weimenglee… ]); ''').df()
두 CSV 파일의 스키마가 다르면 내용이 행 단위로 이어 붙지 않는다. 제6장에서 여러 JSON 파일을 합칠 때 키 이름으로 열을 맞추고 빈 자리를 NaN·None으로 채웠던 그 관용은 여기에 없다.
그리고 이 절의 결론이 뜻밖이다. CSV 파일은 대부분의 경우 지역 기계로 전부 내려온다. CSV가 데이터를 행 기반 형식으로 저장하기 때문이다. 큰 파일을 다룰 때 특히 시간이 걸린다. 원격 데이터를 훨씬 효율적으로 가져오는 길은 Parquet 형식을 쓰는 것이다.
제1장의 첫 그림이 여기서 다시 필요해진다. 행 기반 저장에서는 한 행을 읽으려면 그 행의 모든 열이 함께 온다. 이제 그 파일이 통신선 저쪽에 있다면, 같은 사정이 대역폭의 문제로 바뀐다. 기둥으로 세워 두지 않은 데이터는 원격에서도 쪼갤 수 없다.
원격 Parquet — 필요한 기둥만 뜬다
ACCESSING PARQUET FILES
Parquet 파일에 대해서 DuckDB는 Parquet 메타데이터와 HTTP 범위 요청(range request)을 조합해, 질의가 실제로 요구하는 파일의 부분만 부분적으로 내려받는다. 이 한 문장이 이 장에서 가장 값진 문장이다.
CSV 파일에서 변환한 Parquet 파일이 GitHub에 놓여 있는 경우를 본다. 원서는 같은 파일을 놓고 질의문만 바꾸어 가며, 내려받는 양이 어떻게 줄어드는지를 네 걸음으로 보인다.
# ① 파일 전체를 내려받아 DataFrame으로 바꾼다 conn.execute(''' SELECT * FROM 'https://github.com/weimenglee/DuckDB_Book/raw/… ''').df() # ② 내려받지 않고 원격 파일의 스키마만 얻는다 conn.execute(''' DESCRIBE TABLE 'https://github.com/weimenglee/DuckDB_Book/raw/ma… ''').df()
순서가 중요하다. 대개는 파일의 모든 열이 필요하지 않다. 더 나은 접근은 먼저 내려받지 않고 원격 파일의 스키마를 얻고 그다음 어떤 열을 내려받을지 정하는 것이다. 답사자가 비석 앞에서 먼저 전체 도면을 그리고, 어느 면을 떠 올지 정하는 순서와 같다.
# ③ Agency와 Agency Type 두 열만 내려받는다 conn.execute(''' SELECT Agency, "Agency Type" FROM 'https://github.com/weimenglee/DuckDB_Book/ra… ''').df() # ④ age 열만 내려받아 평균을 계산한다 conn.execute(''' SELECT avg(age) FROM 'https://github.com/weimenglee/DuckDB_Book/ra… ''').df() # ⑤ 데이터를 전혀 내려받지 않는다 — 메타데이터에서 읽는다 conn.execute(''' SELECT count(*) FROM 'https://github.com/weimenglee/DuckDB_Book/ra… ''').df()
| 질의 | 실제로 통신선을 타고 오는 것 | 내려받는 양 |
|---|---|---|
| CSV · SELECT * | 파일 전부. 행 기반이므로 쪼갤 수 없다 | |
| Parquet · SELECT * | 파일 전부. 모든 열을 달라 했으므로 | |
| Parquet · DESCRIBE TABLE | 스키마. 데이터는 오지 않는다 | |
| Parquet · 두 열 | Agency와 "Agency Type"에 해당하는 부분 | |
| Parquet · avg(age) | age 열에 해당하는 부분 | |
| Parquet · count(*) | 없음. 메타데이터에 적힌 값을 읽는다 |
눈금의 길이는 정확한 비율이 아니다. 원서는 각 질의의 통신량을 수치로 밝히지 않으므로, 이 눈금은 “전부 · 두 열 · 한 열 · 없음”이라는 서술의 순서를 눈에 보이게 한 것이다. 열 개수만으로도 비율이 정해지지 않는다. Parquet은 열마다 압축이 걸리므로 열의 실제 크기가 서로 다르다.
다섯째 줄이 이 장의 정점이다. count(*)에는 데이터가 필요하지 않다. 행 수는 이미 파일 머리의 메타데이터에 적혀 있다. 비문의 글자 수를 알아내려고 굳이 종이와 먹을 챙겨 가지 않아도 되는 것과 같다. 그 수는 비석을 세울 때 이미 기록되었다.
이 절이 제1장·제2장·제4장의 이야기를 한자리에 모은다는 점도 짚어 둘 만하다. 열 기반 저장이라는 한 가지 결정이 제1장에서는 디스크 읽기를 줄였고, 제4장에서는 메모리 적재를 줄였고, 여기서는 네트워크 통신을 줄인다. 같은 구조가 세 층위에서 같은 방식으로 값을 한다.
Hugging Face라는 큰 서고
QUERYING HUGGING FACE DATASETS
httpfs 확장으로 HTTP(S) 프로토콜을 통해 원격 데이터셋에 질의하는 것 말고도, DuckDB는 Hugging Face가 호스팅하는 데이터셋에 대한 질의를 지원한다.
Hugging Face Datasets는 기계학습과 자연어 처리(NLP) 과제를 위한 대규모 데이터셋의 접근을 간단하게 만드는 라이브러리다. 모형의 훈련과 평가, 성능 견주기에 곧바로 쓸 수 있는 방대한 데이터셋 모음을 제공해, 손으로 데이터를 모으고 전처리할 필요를 없앤다. 아울러 데이터셋의 효율적인 적재·변환·분할을 위한 내장 함수로 사용자 정의 데이터 처리도 지원한다.
또한 Hugging Face Datasets는 분산되고 효율적인 데이터 적재에 최적화되어 있어 대규모 프로젝트에 이상적이다. Hugging Face Transformers 라이브러리와의 매끄러운 통합이 작업 흐름을 더욱 간결하게 만들며, NLP와 기계학습 과제에 종사하는 연구자와 개발자에게 값진 도구가 된다.
Hugging Face는 NLP와 기계학습에 집중하는 회사이자 오픈소스 공동체다. 다양한 사전 훈련 기계학습 모형을 호스팅하며, 기계학습 프로젝트의 개발과 배포를 돕는 폭넓은 도구와 라이브러리와 자원을 제공한다.
DuckDB에서 Hugging Face 데이터셋을 쓰려면 먼저 그것이 어떻게 구성되어 있는지 이해해야 한다. 원서는 scikit-learn 라이브러리와 함께 배포되는 Tips 데이터셋을 예로 삼아, 웹 화면에서 URL을 조립하는 순서를 다섯 걸음으로 보인다.
- Hugging Face 데이터셋 페이지에서 쓰려는 데이터셋을 찾고 걸러 낸다.
- 그 데이터셋 페이지에서 사용자 이름과 데이터셋 이름을 얻는다.
- 같은 페이지에서 Files 탭을 눌러 이 데이터셋에서 쓸 수 있는 파일을 드러낸다.
- 내려받으려는 CSV·JSON·Parquet 파일을 찾는다. 이 예에서는 tips.csv다.
- 얻은 정보로 hf:// 경로를 조립한다.
원서는 “도판 8-14가 이 정보로 URL을 조립하는 방법을 보인다”고 적은 다음, 이어지는 절의 첫 문장에서 “도판 8-13에 보인 URL 형식을 써서”라고 적는다. 그런데 8-13은 내려받을 파일을 찾는 화면이고 URL을 조립하는 것은 8-14다. 뒤의 “8-13”은 8-14의 오기로 보인다.
import duckdb conn = duckdb.connect() conn.execute(''' SELECT * FROM 'hf://datasets/scikit-learn/tips/tips.csv'; ''').df()
원저자는 이어서 한 가지를 권한다. 데이터를 DuckDB 표에 저장해 두어, 뒤이은 질의마다 원격 종점에 접근하지 않도록 하라는 것이다.
conn.execute(''' CREATE TABLE Tips AS FROM 'hf://datasets/scikit-learn/tips/tips.csv'; ''') # 이제 표에 질의한다 conn.execute(''' SELECT * FROM Tips ''').df()
이 권고에 이 장의 절충이 담겨 있다. 원격 질의는 편하지만 매번 통신한다. 앞 절에서 통신량을 아끼는 기법을 배웠는데, 여기서는 통신 횟수를 아끼는 기법이 나온다. 답사자가 같은 비석을 열 번 찾아가지 않고 탁본 한 장을 떠 와 책상에서 열 번 들여다보는 것과 같다.
폴더와 별표 · 경로를 늘려 잡는다
FOLDERS AND THE GLOB SYNTAX
Hugging Face 데이터셋의 파일이 특정 폴더에 저장된 경우가 있다. Adult Census Income 데이터셋이 그 예다. Files 탭을 누르면 data 폴더가 보이며, 그 안에 내려받으려는 파일이 있다. 파일 이름 옆에 표시된 아이콘을 누르고 파일 이름을 복사한다.
이 예에서 내려받으려는 것은 Parquet 파일이다. 온전한 URL은 다음과 같다. data 폴더 이름이 더해진 것을 눈여겨본다.
hf://datasets/AiresPucrs/adult-census-income/data/
train-00000-of-00001-7e70ed54d8cbb057.parquetconn.execute(''' SELECT * FROM 'hf://datasets/AiresPucrs/adult-census-income' '/data/train-00000-of-00001-7e70ed54d8cbb057.parquet' ''').df()
여러 파일을 별표 하나로
제6장에서 다룬 glob 구문으로 여러 파일에 질의할 수 있다. 상기하는 뜻에서 glob 양상의 목록을 다시 옮긴다.
| 와일드카드 | 하는 일 |
|---|---|
| * | 임의 개수의 임의 문자와 일치한다. 없는 경우도 포함한다. |
| ** | 임의 개수의 하위 디렉터리와 일치한다. 없는 경우도 포함한다. |
| ? | 임의의 한 문자와 일치한다. |
| [abc] | 대괄호 안에 주어진 문자 가운데 하나와 일치한다. |
| [a-z] | 대괄호 안에 주어진 범위에서 한 문자와 일치한다. |
어떤 데이터셋에 geo_test.csv와 geo_train.csv 두 파일이 있다고 하자. 모든 CSV 파일을 적재하려면 별표를 쓴다.
# 두 CSV 파일을 한꺼번에 적재한다 conn.execute(''' SELECT * FROM 'hf://datasets/gabrielwu/city_country/*.csv' ''').df() # question 열에 'Huaibei'가 든 행을 찾는다 conn.execute(''' SELECT * FROM 'hf://datasets/gabrielwu/city_country/*.csv' WHERE question LIKE '%Huaibei%'; ''').df()
glob 양상으로 적재하는 모든 파일은 같은 스키마를 가져야 한다. 그렇지 않으면 예외가 발생한다.
그리고 glob 양상이 가장 값을 하는 자리는 Parquet 파일이다. 파일 집합 전체를 내려받을 필요가 흔히 없기 때문이다. 두 개의 Parquet 파일을 가진 데이터셋에서 question 열만 내려받고, 그 가운데 “happened”가 든 행만 표시한다.
conn.execute(''' SELECT question FROM 'hf://datasets/Stanford/web_questions/data/*… WHERE question LIKE '%happened%'; ''').df()
여기서 앞 절의 눈금이 여러 파일로 확장된다. 별표 하나가 파일의 수를 늘리고, 열 이름 하나가 통신량을 줄인다. 두 방향이 서로 어긋나지 않는다는 점이 이 기법의 힘이다. 백 개의 비석을 훑으면서도 각 비석에서 한 줄만 떠 온다.
잠긴 서고 · 비공개 데이터셋
WORKING WITH PRIVATE HUGGING FACE DATASETS
지금까지는 공개 Hugging Face 데이터셋에 접근하는 법이었다. 비공개 데이터셋은 어떤가. 공개와 비공개의 주된 차이는, 비공개 데이터셋에는 접근하기 전에 해당 자격 증명을 제공해야 한다는 것이다. 익힐 것은 셋이다.
- 만든다 CSV 파일을 Hugging Face에 올려 비공개 데이터셋을 만든다.
- 열쇠를 얻는다 그 비공개 데이터셋에 접근할 액세스 토큰을 만든다.
- 보인다 액세스 토큰을 제공해 DuckDB로 비공개 데이터셋에 원격 접근한다.
이 모든 일을 하기 전에 Hugging Face에 계정을 만들어야 한다. https://huggingface.co로 가서 Sign Up 버튼을 누르고, 전자우편 주소와 쓰려는 비밀번호를 넣고 Next를 누른다.
비공개 데이터셋을 올린다
- 사용자 아이콘을 누르고 New Dataset을 누른다.
- 새 데이터셋의 상세를 채운다. Owner 필드는 자신의 사용자 이름이어야 하며, 데이터셋이 Private으로 설정되었는지 확인한다. 그리고 Create dataset 버튼을 누른다.
- 다음 페이지에서 Files 탭을 누르고, Add file 버튼을 누르고, Upload files 항목을 누른다.
- 올리려는 파일을 끌어다 놓는다. 원서의 예에서는 Amazon.com의 과거 주가를 담은 CSV 파일이다.
- 같은 페이지 아래쪽에서 “Commit directly to the main branch”를 고른다. 올린 파일이 Files 탭에 나타난다.
액세스 토큰을 만든다
- 사용자 아이콘을 누르고 Settings를 누른다.
- Access Tokens를 누른다.
- New Token 버튼을 누른다.
- Read 버튼을 누르고, 이 토큰의 쓰임을 설명하는 이름을 넣는다.
- Create token 버튼을 누른다. 액세스 토큰이 생성된다.
- 복사 아이콘을 눌러 액세스 토큰을 복사하고 안전한 곳에 붙여 둔다.
이 토큰은 나중에 다시 와도 표시되지 않는다. 액세스 토큰을 잃으면 그것을 무효화하고 갱신해 새 토큰을 생성해야 한다.
인증하는 두 가지 길
액세스 토큰이 생성되었으므로 그것으로 DuckDB에서 비공개 데이터셋에 접근할 수 있다. DuckDB가 지원하는 주된 제공자(provider)는 둘이다.
| 제공자 | 토큰이 놓이는 자리 | 알맞은 상황 |
|---|---|---|
| CONFIG | CREATE SECRET 문에 액세스 토큰을 손으로 적는다. 곧 코드에 토큰이 노출된다. | 특정하고 이미 아는 토큰으로 인증해야 할 때 이상적이다. 토큰이 자주 교체되는 환경, 또는 서비스마다 자격 증명을 명시적으로 지정해야 하는 환경이다. |
| CREDENTIAL_CHAIN | 지역 컴퓨터의 디렉터리에서 토큰을 자동으로 찾아온다. 기본 위치, 환경 변수, 자격 증명 파일 등을 순서대로 확인하고, 유효한 인증 정보를 찾으면 멈춘다. | 매끄러운 인증을 원할 때 유익하다. 클라우드 기반 가상 기계나, 자격 증명이 자동으로 저장되고 관리되는 지역 개발 환경이다. |
import duckdb conn = duckdb.connect() conn.execute(''' CREATE SECRET hf_token ( TYPE HUGGINGFACE, TOKEN '<HuggingFace_Token>' ); ''') # 이제 공개 데이터셋의 파일에 접근하듯 쓴다 conn.execute(''' SELECT * FROM 'hf://datasets/Wei-Meng/StockPrices/AMZN.csv' ''').df()
비공개 데이터셋에 접근하는 CONFIG 제공자 방법은 코드에 액세스 토큰을 노출한다. 액세스 토큰을 파일에 저장하는 CREDENTIAL_CHAIN 방법이 훨씬 안전하다. 먼저 Hugging Face Hub 파이썬 꾸러미를 설치하고 로그인 유틸리티를 실행한다.
$ pip install huggingface_hub $ huggingface-cli login Token: <HuggingFace_Token> # 액세스 토큰을 붙여 넣는다. 화면에 되비침이 없다. # git 자격 증명으로 추가할지 묻는다. n을 넣고 Enter를 누른다. Add token as git credential? (Y/n) n Token is valid (permission: read). Your token has been saved to /Users/weimenglee/.cache/huggingface/token Login successful
import duckdb conn = duckdb.connect() conn.execute(''' CREATE SECRET hf_token ( TYPE HUGGINGFACE, PROVIDER CREDENTIAL_CHAIN ); ''') conn.execute(''' SELECT * FROM 'hf://datasets/Wei-Meng/StockPrices/AMZN.csv' ''').df()
토큰이 무효가 되면, 예컨대 Hugging Face에서 삭제했다면 ~/.cache/huggingface/ 디렉터리의 토큰 파일을 그냥 지우면 된다. 토큰이 바뀌면 그 토큰 파일에서 갱신할 수 있다.
제7장의 마지막 절이 여기서 되돌아온다. 그때도 세 가지 방법의 차이가 비밀번호를 어디에 두는가에 있었고, 운영체제나 파일에 맡기는 쪽이 권장되었다. 여기서도 같다. CREATE SECRET의 본문 한 줄이 TOKEN '…'에서 PROVIDER CREDENTIAL_CHAIN으로 바뀌는 것뿐인데, 그 한 줄이 코드에 열쇠를 박아 두는 일과 열쇠를 밖에 맡기는 일을 가른다. 열쇠를 문설주 밑에 두지 말라는 오래된 말과 다르지 않다.