DuckDB, baza analityczna działająca w procesie
DuckDB to silnik SQL do analityki, który uruchamia się wewnątrz Twojego procesu, bez serwera i bez demona w tle. Czyta pliki CSV, Parquet i JSON prosto z dysku albo z S3, podłącza się do Postgresa i liczy agregaty na kolumnach. Bieżąca wersja to 1.5.5 z 22 lipca 2026 roku, a licencja to MIT.
Czym DuckDB różni się od bazy z serwerem
Porównanie do SQLite jest trafne co do modelu uruchomienia i mylące co do zastosowania, więc rozłóżmy je na części.
Model uruchomienia jest ten sam. Instalujesz bibliotekę, otwierasz połączenie i baza żyje w pamięci Twojego procesu. Nie ma portu, nie ma pliku konfiguracyjnego, nie ma użytkownika, któremu trzeba nadać uprawnienia. Cała baza to jeden plik na dysku albo, jeśli nie podasz nazwy, struktura istniejąca wyłącznie w pamięci i znikająca razem z procesem. Ten brak warstwy sieciowej jest głównym powodem, dla którego zapytanie zwraca wynik natychmiast: dane nie przechodzą przez gniazdo, nie są serializowane do protokołu i nie czekają w kolejce planisty po drugiej stronie.
Zastosowanie jest inne. SQLite składuje dane wierszami i jest zoptymalizowany pod pojedyncze odczyty i zapisy rekordów, czyli pod obciążenie transakcyjne. DuckDB składuje dane kolumnami i wykonuje zapytania wektorowo, czyli przetwarza naraz porcje po kilka tysięcy wartości z jednej kolumny. Dla zapytania typu „policz sumę sprzedaży w podziale na miesiąc i region z tabeli o stu milionach wierszy" różnica nie jest kilkuprocentowa, tylko rzędowa, bo DuckDB czyta wyłącznie trzy potrzebne kolumny i nigdy nie dotyka pozostałych czterdziestu.
Z tego wynika prosta reguła doboru. Jeśli aplikacja zapisuje pojedyncze rekordy z wielu wątków naraz, potrzebujesz PostgreSQL albo SQLite. Jeśli czytasz duże zbiory i liczysz na nich agregaty, DuckDB zrobi to szybciej i bez utrzymywania czegokolwiek.
Jest jeszcze trzecia właściwość, często pomijana. DuckDB traktuje pliki jako tabele. Ścieżka do pliku Parquet może stać w klauzuli FROM bez żadnego wcześniejszego ładowania, importu ani deklaracji schematu. To zmienia sposób pracy z danymi bardziej niż sama szybkość, bo znika krok, w którym coś trzeba najpierw gdzieś wgrać.
Wersja, licencja i kto za tym stoi
Bieżące wydanie to 1.5.5 z 22 lipca 2026 roku. Pakiet w rejestrze PyPI wymaga Pythona w wersji co najmniej 3.10. Równolegle utrzymywana jest linia z długim wsparciem oznaczona jako LTS: gałąź 1.4 pod nazwą Andium, której ostatnie wydanie 1.4.5 ukazało się 17 czerwca 2026 roku, a pierwsze, 1.4.0, we wrześniu 2025 roku. Jeśli budujesz coś, co ma stać niezmienione przez rok, linia LTS jest sensowniejszym wyborem niż gałąź bieżąca, która w pierwszej połowie 2026 roku wydała pięć wersji mniejszych w cztery miesiące.
Licencja wymaga sprawdzenia w trzech miejscach, bo każde mówi coś trochę innego.
Plik LICENSE w repozytorium zawiera tekst MIT z linią „Copyright 2018-2026 Stichting DuckDB Foundation". To jest odpowiedź podstawowa i jest ona prawdziwa dla samego silnika.
Metadane w rejestrze PyPI są uboższe, niż się wydaje. Pole license oraz nowsze license_expression są puste, a jedyną informacją o licencji jest klasyfikator License :: OSI Approved :: MIT License. Narzędzie audytowe, które czyta wyłącznie pole SPDX, nie zobaczy tam nic i zgłosi zależność jako pozbawioną licencji. Narzędzie czytające klasyfikatory zobaczy MIT. Ta sama paczka, dwa różne wyniki, zależnie od skanera.
Zawartość opublikowanej paczki jest najciekawsza. W kole binarnym dla Pythona 3.12 na architekturze arm64 siedzą dwa pliki licencyjne: duckdb-1.5.5.dist-info/licenses/LICENSE liczący 1072 bajty z tekstem MIT oraz duckdb/experimental/spark/LICENSE z pełnym tekstem Apache License 2.0. Ten drugi należy do eksperymentalnej warstwy zgodności z interfejsem Sparka, która dziedziczy licencję po PySparku. W archiwum źródłowym jest ich trzydzieści dziewięć, bo dochodzą licencje bibliotek wbudowanych w drzewo źródeł. Wśród nich znajduje się external/duckdb/extension/tpch/dbgen/LICENSE, czyli End User License Agreement w wersji 2.2 należący do konsorcjum TPC. To nie jest licencja otwarta i nie jest to MIT. Dotyczy generatora danych testowych używanego przez rozszerzenie tpch, więc w normalnym użyciu nie ma znaczenia, ale jeśli redystrybuujesz własną kompilację DuckDB razem z tym rozszerzeniem, jest to warunek, którego etykieta „MIT" na paczce nie opisuje.
W rejestrze npm pole license ma wartość MIT zarówno dla nowego klienta @duckdb/node-api w wersji 1.5.5-r.4, jak i dla starszego pakietu duckdb w wersji 1.4.4. Warto pilnować, który z nich instalujesz, bo numeracja tych dwóch pakietów rozjechała się o całe wydanie mniejsze.
Prawa do projektu trzyma Stichting DuckDB Foundation, fundacja niekomercyjna zarejestrowana w Holandii. Zarząd tworzą Hannes Mühleisen, Mark Raasveldt i Peter Boncz. Finansowanie idzie z darowizn, przy czym poziom Silver zaczyna się od 10 000 euro, a Gold od 100 000 euro. Układ jest przejrzysty i odporniejszy niż projekt kontrolowany przez jedną spółkę, ale nadal oznacza brak umowy wsparcia, którą można wyegzekwować terminem.
Pierwsze zapytanie na pliku
Instalacja to jedna komenda, a pierwsze zapytanie nie wymaga tworzenia bazy.
pip install duckdb
# zapytanie prosto z terminala, bez tworzenia pliku bazy
duckdb -c "SELECT count(*) FROM 'sprzedaz.csv'"
# wbudowany interfejs graficzny w przeglądarce
duckdb -uiPo stronie SQL plik jest tabelą i nic więcej nie trzeba deklarować.
-- cały katalog plików jako jedna tabela
SELECT region, date_trunc('month', order_date) AS miesiac, sum(net_amount) AS obrot
FROM 'dane/sprzedaz-*.parquet'
GROUP BY ALL
ORDER BY miesiac;
-- gdy wykrywanie typów zawodzi, podajesz je jawnie
SELECT *
FROM read_csv('flights.csv',
delim = '|',
header = true,
sample_size = 20_000,
columns = {
'FlightDate': 'DATE',
'UniqueCarrier': 'VARCHAR',
'OriginCityName': 'VARCHAR',
'DestCityName': 'VARCHAR'
});
-- materializacja wyniku do pliku
COPY (SELECT * FROM 'sprzedaz-*.csv' WHERE net_amount > 0)
TO 'sprzedaz.parquet' (FORMAT parquet, COMPRESSION zstd);Dwie rzeczy z tego przykładu zasługują na komentarz. GROUP BY ALL grupuje po wszystkich kolumnach spoza agregatów i oszczędza przepisywania listy przy każdej zmianie zapytania. Parametr sample_size sterujący wykrywaniem typów w CSV domyślnie patrzy na ograniczoną próbkę, więc kolumna, która przez pierwsze dwadzieścia tysięcy wierszy wygląda na liczbę całkowitą, a potem zawiera tekst, wywali zapytanie. Wtedy albo podnosisz próbkę, albo podajesz columns jawnie, albo ustawiasz auto_type_candidates, żeby zawęzić listę rozważanych typów.
Interfejs duckdb -ui uruchamia lokalny serwer i otwiera edytor zapytań w przeglądarce. Ma jedną cechę, o której trzeba wiedzieć w firmie z restrykcyjną siecią: warstwa graficzna pobierana jest domyślnie ze zdalnego adresu wskazanego przez ustawienie ui_remote_url, a port lokalny zmienia się przez ui_local_port. Same dane nie wychodzą na zewnątrz, ale samo połączenie sieciowe istnieje i bywa blokowane.
Podłączanie Postgresa i plików w chmurze
Rozszerzenia instaluje się z poziomu SQL, jednorazowo na maszynę, i ładuje na sesję.
INSTALL postgres;
LOAD postgres;
-- baza produkcyjna widoczna jako schemat, tylko do odczytu
ATTACH 'dbname=analytics user=readonly host=10.0.0.4' AS pg (TYPE postgres, READ_ONLY);
-- złączenie tabeli z Postgresa z plikiem Parquet leżącym lokalnie
SELECT c.country, sum(o.total) AS obrot
FROM pg.public.customers AS c
JOIN 'zamowienia-2026-*.parquet' AS o ON o.customer_id = c.id
GROUP BY ALL;
-- przeniesienie tabeli do lokalnej bazy DuckDB
CREATE TABLE customers_local AS FROM pg.public.customers;Flaga READ_ONLY przy ATTACH nie jest ozdobnikiem. Bez niej połączenie może zapisywać do produkcyjnego Postgresa, a analityk pracujący w notatniku prędzej czy później wpisze DELETE w niewłaściwym oknie. Jeśli pominiesz łańcuch połączenia i podasz pusty ciąg, rozszerzenie skorzysta ze standardowych zmiennych środowiskowych PGHOST, PGUSER, PGPASSWORD i PGDATABASE.
Dostęp do obiektów w chmurze idzie przez mechanizm sekretów, a nie przez zmienne globalne.
INSTALL httpfs;
LOAD httpfs;
-- klucze podane wprost
CREATE OR REPLACE SECRET s3_raw (
TYPE s3,
PROVIDER config,
KEY_ID 'AKIAIOSFODNN7EXAMPLE',
SECRET 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
REGION 'eu-central-1'
);
-- albo poświadczenia z łańcucha dostawcy, zawężone do jednej ścieżki
CREATE OR REPLACE SECRET s3_scoped (
TYPE s3,
PROVIDER credential_chain,
REGION 'eu-central-1',
SCOPE 's3://raporty/2026/'
);
SELECT * FROM 's3://raporty/2026/zdarzenia-*.parquet' LIMIT 10;Zawężenie przez SCOPE pozwala trzymać kilka sekretów naraz i przypisać je do różnych prefiksów, co jest wygodniejsze niż przełączanie profilu przed każdym zapytaniem. Przy odczycie z S3 DuckDB pobiera wyłącznie te fragmenty plików Parquet, które są potrzebne do zapytania, więc rachunek za transfer bywa dużo niższy niż przy pobraniu całego zbioru.
Zamiast Pandas, czyli gdzie leży realny zysk
Największa różnica w codziennej pracy pojawia się tam, gdzie do tej pory ładowano cały zbiór do ramki danych i czekano. DuckDB w Pythonie widzi zmienne z lokalnego zakresu jako tabele, więc nie musisz niczego przenosić.
import duckdb
import pandas as pd
zamowienia = pd.read_parquet("zamowienia.parquet")
# ramka z lokalnego zakresu jest widoczna jako tabela
raport = duckdb.sql("""
SELECT country, count(*) AS liczba, median(total) AS mediana
FROM zamowienia
WHERE status = 'paid'
GROUP BY ALL
ORDER BY liczba DESC
""").df()
# wynik można oddać także jako Polars albo Arrow
tabela_arrow = duckdb.sql("SELECT * FROM zamowienia LIMIT 1000").arrow()
# trwałe połączenie z plikiem bazy i ustawienia zasobów
con = duckdb.connect("analytics.duckdb", config={"threads": 4})
con.sql("SET memory_limit = '8GB'")
con.sql("SET temp_directory = '/var/tmp/duckdb'")
con.sql("SET max_temp_directory_size = '100GB'")
con.sql("SET preserve_insertion_order = false")Ten układ działa w obie strony i przenoszenie danych między nimi jest tanie, bo obie strony rozumieją format Arrow. Zapytanie zwraca ramkę Pandas przez df(), ramkę Polars przez pl(), tabelę Arrow przez arrow(), a listę krotek przez fetchall(). Zapis idzie przez write_parquet() albo write_csv().
Zysk bierze się z trzech rzeczy naraz. Po pierwsze, DuckDB wykonuje agregaty i złączenia na wielu rdzeniach, podczas gdy Pandas robi to na jednym. Po drugie, przy odczycie pliku Parquet czyta tylko kolumny występujące w zapytaniu, a nie cały plik. Po trzecie, potrafi pracować na zbiorach większych niż pamięć: gdy wynik pośredni nie mieści się w limicie ustawionym przez memory_limit, zrzuca go do katalogu wskazanego przez temp_directory, zamiast przerwać pracę.
Ostatni punkt jest tym, który najczęściej rozstrzyga. Ramka danych, która nie mieści się w pamięci, kończy się awarią procesu i utratą całej sesji. To samo zapytanie w DuckDB dobiega do końca, tylko wolniej. Ustawienie preserve_insertion_order na wartość fałsz zwalnia silnik z obowiązku zachowania kolejności wierszy i wyraźnie zmniejsza zużycie pamięci przy dużych odczytach, o ile Twoje zapytanie i tak kończy się klauzulą ORDER BY.
Czego DuckDB nie zastępuje: reszty Pandas. Przekształcenia wymagające pętli po wierszach, integracja z bibliotekami uczenia maszynowego, rysowanie wykresów, praca na indeksie czasowym pozostają po tamtej stronie. Sensowny podział to filtrowanie, złączenia i agregacja w SQL, a dopiero wynik, zwykle mały, jako ramka. Podobnie wygląda to w potokach zarządzanych przez Apache Airflow, gdzie DuckDB bywa krokiem przetwarzającym pliki między zadaniami, a nie docelowym magazynem. Przy przygotowywaniu tekstu do osadzeń dobrze łączy się z Unstructured, które wyciąga treść z dokumentów, i z pgvector, które przechowuje gotowe wektory po stronie Postgresa.
MotherDuck, czyli osobna firma i osobny cennik
MotherDuck to komercyjna usługa chmurowa zbudowana wokół DuckDB, prowadzona przez odrębną spółkę. Nie jest to płatny wariant projektu ani produkt fundacji. Rozróżnienie ma znaczenie praktyczne: sam DuckDB nie ma i nie zapowiada wersji płatnej, a wszystko, co poniżej, dotyczy wyłącznie usługi MotherDuck.
Połączenie wygląda jak zwykłe połączenie DuckDB z innym przedrostkiem.
import duckdb
# uwierzytelnienie przez przeglądarkę
con = duckdb.connect("md:my_db")
# albo tokenem, na przykład w zadaniu wsadowym
con = duckdb.connect("md:?motherduck_token=<token>")
con.sql("SHOW DATABASES").show()
# baza lokalna i chmurowa jednocześnie, w jednym zapytaniu
local_con = duckdb.connect("analytics.duckdb")
local_con.sql("ATTACH 'md:my_db'")Dokumentacja usługi podaje, że wspierane są wersje klienta DuckDB od 1.4.1 do 1.5.5. To jest realne ograniczenie tempa aktualizacji, bo nowe wydanie DuckDB nie działa z MotherDuck od pierwszego dnia.
| Plan | Cena platformy | Użytkownicy wewnętrzni | Typy instancji | Retencja migawek |
|---|---|---|---|---|
| Lite | 0 USD za organizację miesięcznie | do 3 aktywnych | tylko Pulse | do 1 dnia |
| Business | 250 USD za organizację miesięcznie plus zużycie | do 10 aktywnych | pięć typów plus repliki odczytu | do 90 dni |
| Enterprise | cena indywidualna | bez limitu | pięć typów plus repliki odczytu | do 90 dni |
Do opłaty za platformę dochodzi zużycie rozliczane co sekundę. Składowanie kosztuje 0,04 USD za gigabajt miesięcznie w obu planach. Instancja Pulse to 0,60 USD za godzinę, Standard 2,40 USD, Jumbo 4,80 USD, Mega 12,00 USD, a Giga 24,00 USD. Funkcje sztucznej inteligencji, w tym prompt() i embedding(), rozliczane są po 1,00 USD za jednostkę AI. Plan Lite obejmuje 10 gigabajtów składowania i 10 godzin czasu Pulse miesięcznie bez opłaty, dwa konta usługowe i wsparcie wyłącznie społecznościowe. Plan Business dokłada nieograniczone konta usługowe, do szesnastu replik odczytu, historię zapytań i deklarowaną dostępność na poziomie 99,9 procent, z siedmiodniowym okresem próbnym. Plan Enterprise dorzuca łączność przez AWS PrivateLink, listę dozwolonych adresów, role definiowane samodzielnie i umowę HIPAA BAA.
Ryzyko przywiązania do dostawcy jest tu umiarkowane, ale niezerowe. Zapytania są zwykłym SQL-em DuckDB i przeniosą się gdzie indziej, natomiast funkcje takie jak wykonanie rozproszone między maszyną lokalną a chmurą, zarządzany DuckLake czy potoki nazwane Flights istnieją tylko w usłudze. Jeśli oprzesz na nich logikę produktu, wyjście przestaje być zamianą łańcucha połączenia.
DuckDB a alternatywy
| Cecha | DuckDB | SQLite | PostgreSQL | Pandas | Hurtownia chmurowa |
|---|---|---|---|---|---|
| Model uruchomienia | w procesie | w procesie | serwer | w procesie | usługa zdalna |
| Profil obciążenia | analityczny | transakcyjny | transakcyjny | analityczny | analityczny |
| Układ składowania | kolumnowy | wierszowy | wierszowy | kolumnowy w pamięci | kolumnowy |
| Zapytanie wprost na Parquet w S3 | tak | nie | przez rozszerzenie | przez bibliotekę | tak |
| Praca na zbiorze większym niż pamięć | tak | tak | tak | nie | tak |
| Wielu jednoczesnych piszących | nie | ograniczony | tak | nie | tak |
| Licencja | MIT | domena publiczna | PostgreSQL License | BSD 3-Clause | zamknięta |
Wybór rozstrzyga się na dwóch pytaniach. Pierwsze brzmi: czy dane muszą być zapisywane jednocześnie przez wiele procesów. Jeśli tak, DuckDB odpada jako magazyn główny, bo plik bazy przyjmuje w danej chwili jeden proces zapisujący. Zostaje wtedy Postgres, ewentualnie w wariancie zarządzanym u dostawcy pokroju Neon, a DuckDB może nadal służyć jako warstwa raportowa czytająca kopię danych. Drugie pytanie brzmi: czy zbiór mieści się na jednej maszynie. Pojedynczy serwer z kilkuset gigabajtami pamięci i dyskiem NVMe obsługuje dziś zbiory, które dziesięć lat temu wymagały klastra, więc próg opłacalności hurtowni rozproszonej przesunął się wyraźnie w górę.
Osobno stoi porównanie z Turso, które rozwiązuje inny problem: jest rozproszoną odmianą SQLite nastawioną na odczyty transakcyjne blisko użytkownika, a nie na agregaty. Te dwa narzędzia sąsiadują ze sobą w opisach, bo oba wyrastają z modelu bazy w procesie, ale nie konkurują o to samo zadanie.
Typowe błędy
Pierwszy to traktowanie pliku bazy jak współdzielonego magazynu. Plik .duckdb obsługuje jednego piszącego naraz i nie jest przeznaczony do umieszczania na dysku sieciowym współdzielonym przez kilka maszyn. Współbieżność uzyskuje się przez rozdzielenie ról: jeden proces zapisuje, pozostałe czytają kopię albo pliki Parquet.
Drugi to poleganie na automatycznym wykrywaniu typów w CSV przy danych produkcyjnych. Próbka jest ograniczona, więc kolumna z identyfikatorem złożonym z samych cyfr zostanie odczytana jako liczba i straci wiodące zera. Podaj columns jawnie wszędzie tam, gdzie schemat jest znany.
Trzeci to brak limitu pamięci na maszynie współdzielonej. Domyślnie DuckDB bierze znaczącą część dostępnej pamięci i przy dwóch zadaniach uruchomionych naraz jedno z nich zostanie zabite przez system. Ustaw memory_limit i threads jawnie w każdym zadaniu wsadowym.
Czwarty to zapominanie o temp_directory przy zbiorach większych niż pamięć. Bez wskazanego katalogu roboczego zapytanie, które musiałoby zrzucić dane pośrednie na dysk, zakończy się błędem zamiast zwolnić. Przy okazji sprawdź max_temp_directory_size, bo domyślnie jest to dziewięćdziesiąt procent wolnego miejsca.
Piąty to podłączanie Postgresa bez READ_ONLY. Rozszerzenie potrafi pisać do przyłączonej bazy i nic nie ostrzeże analityka, że wykonuje UPDATE na produkcji z poziomu notatnika.
Szósty to mieszanie pakietów Node. Pakiety duckdb i @duckdb/node-api to dwa różne klienty o rozjeżdżających się numerach wersji, a przykłady z sieci milcząco zakładają jeden z nich.
Siódmy to przyjęcie, że etykieta MIT opisuje całe archiwum. Silnik jest na MIT, ale warstwa zgodności ze Sparkiem idzie na Apache 2.0, a generator danych rozszerzenia tpch na licencję konsorcjum TPC, która nie jest licencją otwartą. Przy redystrybucji własnej kompilacji sprawdź, co dokładnie w niej siedzi.
FAQ
Czy DuckDB zastąpi mi Postgresa?
Nie w roli bazy aplikacji. DuckDB nie obsługuje wielu jednoczesnych piszących, nie ma warstwy sieciowej ani systemu uprawnień na poziomie użytkowników. Zastępuje natomiast tę część pracy, w której z Postgresa wyciągało się dane, żeby policzyć na nich raport, bo potrafi przyłączyć bazę i policzyć agregat u siebie.
Czy DuckDB poradzi sobie ze zbiorem większym niż pamięć?
Tak, pod warunkiem że ustawisz temp_directory. Silnik zrzuca wtedy wyniki pośrednie na dysk i kończy zapytanie wolniej, zamiast przerwać pracę. Limit rozmiaru katalogu roboczego kontroluje ustawienie max_temp_directory_size.
Czym MotherDuck różni się od DuckDB?
MotherDuck to komercyjna usługa chmurowa prowadzona przez osobną spółkę, a nie płatny wariant projektu. Sam DuckDB jest w całości otwarty i bezpłatny. MotherDuck dokłada składowanie w chmurze, instancje obliczeniowe rozliczane co sekundę oraz funkcje niedostępne lokalnie, i zaczyna się od planu Lite za 0 USD z limitem trzech aktywnych użytkowników.
Czy DuckDB jest darmowy do zastosowań komercyjnych?
Tak. Silnik jest na licencji MIT, bez opłat i bez ograniczeń w użyciu komercyjnym. Przy redystrybucji własnej kompilacji sprawdź jednak licencje składników wbudowanych w drzewo źródeł, bo nie wszystkie są na MIT.
Którą wersję wybrać do produkcji?
Jeśli aktualizujesz regularnie, bierz gałąź bieżącą, obecnie 1.5.5. Jeśli wdrożenie ma stać niezmienione przez dłuższy czas, bierz linię LTS, czyli gałąź 1.4 pod nazwą Andium. Przy pracy z MotherDuck sprawdź dodatkowo listę wspieranych wersji klienta, bo usługa nie przyjmuje najnowszego wydania natychmiast.
Czy da się odpytywać pliki bez ich pobierania?
Tak, przez rozszerzenie httpfs i sekret typu s3. DuckDB pobiera wtedy wyłącznie te fragmenty plików Parquet, które są potrzebne do zapytania, więc transfer bywa ułamkiem rozmiaru zbioru.
Dokumentację znajdziesz na stronie DuckDB, informacje o fundacji na stronie DuckDB Foundation, a cennik usługi chmurowej na stronie MotherDuck.