2023-08-04
DuckDB ADBC – Zero-Copy-Datentransfer über Arrow Database Connectivity
Pedro Holanda

Datenbankschnittstellen-Standards erlauben es, Anwendungscode unabhängig vom darunterliegenden Datenbankmanagementsystem (DBMS) zu schreiben. DuckDB hat zwei Standards unterstützt, die in den letzten Jahrzehnten populär wurden: die Kernschnittstelle von ODBC und Java Database Connectivity (JDBC). Beide Schnittstellen zielen auf volle Datenbankkonnektivität und -verwaltung, JDBC speziell für die Java-Umgebung. Über diese APIs können Entwickler DBMS-agnostisch abfragen, Ergebnisse abrufen, Prepared Statements ausführen und Verbindungen verwalten.
Diese Schnittstellen entstanden Anfang der 90er, als zeilenorientierte Datenbanksysteme den Ton angaben. Entsprechend waren sie vor allem für den Transfer im zeilenweisen Format gedacht. Mitte der 2000er gewannen spaltenorientierte Systeme wegen ihrer drastischen Leistungsvorteile in der Datenanalyse stark an Bedeutung (eine kurze Veranschaulichung dieses Unterschieds finden Sie auf der EuroPython). Diese APIs bieten daher keine Unterstützung für spaltenweisen Transfer (oder bei ODBC eine gewisse Unterstützung mit deutlich mehr Komplexität). In der Praxis wird bei analytischen, spaltenorientierten Systemen wie DuckDB die Umwandlung zwischen diesen Darstellungsformaten zum großen Flaschenhals.
Die Abbildung unten zeigt, wie sich über diese APIs eine DuckDB-Datenbank abfragen lässt. Entwickler können SQL-Abfragen über die API schicken; ein DuckDB-Treiber ruft intern die passenden Funktionen auf. Das Abfrageergebnis entsteht in DuckDBs internem spaltenorientierten Format, und der Treiber wandelt es in das zeilenweise JDBC- oder ODBC-Ergebnisformat um. Diese Umwandlung kostet durch Umsortieren und Kopieren erheblich und wird schnell zum Flaschenhals.

Um diese Umwandlungskosten zu umgehen, wurde ADBC vorgeschlagen: eine generische API für Datenbankoperationen, die das Apache-Arrow-Speicherformat nutzt, um Daten ins DBMS hinein und wieder heraus zu schicken. DuckDB unterstützt jetzt die ADBC-Spezifikation. Dank DuckDBs Zero-Copy-Integration mit dem Arrow-Format ist ADBC als Schnittstelle recht effizient: Es entsteht nur ein kleiner konstanter Aufwand, um DuckDB-Abfrageergebnisse ins Arrow-Format zu bringen.
Die Abbildung unten zeigt den Abfrageablauf mit ADBC. Der wesentliche Unterschied zu ODBC/JDBC: Das Ergebnis muss nicht ins zeilenweise Format umgewandelt werden.

Kurze Tour
In der kurzen Tour zeigen wir einen Roundtrip mit DuckDB-ADBC über Python. DuckDB-ADBC lässt sich auch mit anderen Sprachen nutzen. C++-Beispiele und Tests liegen im DuckDB-GitHub-Repository, zusammen mit Nutzungsbeispielen in C++. Eine lauffähige Version dieser Tour finden Sie in einem Colab-Notebook. Eine ausführlichere Erklärung der DuckDB-ADBC-API und ein C++-Beispiel stehen auf unserer Dokumentationsseite.
Setup
Für dieses Beispiel brauchen Sie eine dynamische Bibliothek der neuesten Bleeding-Edge-Version von DuckDB, pyarrow und den adbc-driver-manager. Der ADBC Driver Manager ist ein von Voltron Data entwickeltes Python-Paket. Er ist kompatibel mit DB-API 2.0 und umhüllt ADBC, sodass die Nutzung einfacher wird. Details zu ADBC-Treibern stehen in der Dokumentation des ADBC Driver Manager.
DuckDB ist in Python bereits DB-API-kompatibel. ADBC unterscheidet sich dadurch, dass kein DuckDB-Modul installiert und geladen sein muss. Außerdem nutzt ADBC im Gegensatz zur DB-API nicht das zeilenweise Format für den Datentransfer.
pip install pyarrowpip install adbc-driver-managerDaten einfügen
Zuerst die nötigen Bibliotheken: vor allem PyArrow und die DBAPI aus dem ADBC Driver Manager.
import pyarrowfrom adbc_driver_manager import dbapiAls Nächstes eine Verbindung über ADBC zu DuckDB. Dafür braucht es den Pfad zum DuckDB-Treiber und den Namen der Entrypoint-Funktion. DuckDBs Entrypoint ist duckdb_adbc_init.
Standardmäßig wird eine In-Memory-Datenbank verbunden. Sie können aber die Variable path setzen und eine lokale DuckDB-Instanz nutzen, um die Daten auf der Platte zu speichern.
Das sind die einzigen ADBC-Variablen, die nicht DBMS-agnostisch sind; sie setzt der Nutzer, oft über eine Konfigurationsdatei.
con = dbapi.connect(driver="path/to/duckdb.lib", entrypoint="duckdb_adbc_init", db_kwargs={"path": "test.db"})Zum Einfügen der Daten rufen wir mit einem Cursor unserer Verbindung einfach adbc_ingest auf. Nötig sind der Name der Zieltabelle und das Arrow-Python-Objekt. Die Funktion hat zwei Modi: append, wobei Daten an eine bestehende Tabelle angehängt werden, und create, wobei die Tabelle noch nicht existiert und mit den Eingabedaten angelegt wird. Standard ist create, wir müssen das hier also nicht setzen.
table = pyarrow.table( [ ["Tenacious D", "Backstreet Boys", "Wu Tang Clan"], [4, 10, 7]
], names=["Name", "Albums"],)
with con.cursor() as cursor: cursor.adbc_ingest("Bands", table)Nach adbc_ingest ist die Tabelle in der DuckDB-Verbindung angelegt und die Daten sind vollständig eingefügt.
Daten lesen
Zum Lesen aus DuckDB genügt execute mit einer SQL-Abfrage; das Cursor-Ergebnis wird dann ins gewünschte Arrow-Format gebracht, hier eine PyArrow Table.
with con.cursor() as cursor: cursor.execute("SELECT * FROM Bands") cursor.fetch_arrow_table()Benchmark ADBC vs. ODBC
Im Benchmark-Teil vergleichen wir das Lesen aus DuckDB über ADBC und ODBC. Der Benchmark lief auf einem Apple M1 Max mit 32 GB RAM und umfasst Ausgabe und Einfügen der Tabelle lineitem von TPC-H SF1. Das Repository mit dem Code liegt auf GitHub.
| Name | Time (s) |
|---|---|
| ODBC | 28.149 |
| ADBC | 0.724 |
Der Zeitunterschied zwischen ODBC und ADBC beträgt das 38-Fache. Dieser Kontrast entsteht durch die zusätzlichen Allokationen und Kopien in ODBC.
Fazit
DuckDB unterstützt jetzt den ADBC-Standard für Datenbankverbindungen. ADBC ist zusammen mit DuckDB besonders effizient, dank der Arrow-Zero-Copy-Integration.
ADBC ist interessant, weil es die Interaktion zwischen analytischen Systemen gegenüber ODBC deutlich reduzieren kann. Würde Software, die bereits ODBC unterstützt – etwa MS-Excel – ADBC umsetzen, könnten Integrationen mit spaltenorientierten Systemen wie DuckDB von diesem Leistungsunterschied profitieren.
DuckDB-ADBC wird derzeit über die C-Schnittstelle und den Python ADBC Driver Manager unterstützt. Wir werden ausführlichere Tutorials für weitere Sprachen auf unsere Dokumentationsseite stellen. Sagen Sie uns gern, in welcher Sprache Sie DuckDB über ADBC am liebsten nutzen möchten!
Wenn Sie Probleme mit ADBC haben, öffnen Sie bitte ein Issue im DuckDB Issue Tracker.