Data Engine: Unterschied zwischen den Versionen

Aus Dokument
Zur Navigation springen Zur Suche springen
Keine Bearbeitungszusammenfassung
 
(8 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
= Rust-Datenmodell für Drupal-Entities =
Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).
== Was ist eine Data Engine? ==


== Übersicht ==
Eine '''Data Engine''' (auch ''Query Engine'' oder ''Data Processing Engine'' genannt) ist die Softwareschicht, die dafür zuständig ist, Daten effizient zu '''speichern, zu lesen, zu transformieren und abzufragen'''. Sie bildet das Herzstück von Datenbanken, Analytics-Tools und Big-Data-Frameworks – von klassischen SQL-Datenbanken (PostgreSQL, SQLite) über analytische Systeme (ClickHouse, DuckDB) bis hin zu verteilten Big-Data-Plattformen (Apache Spark).


Dieser Artikel dokumentiert ein Rust-Datenmodell, das die zentralen
Typischerweise besteht eine Data Engine aus folgenden Bausteinen:
Entity-Konzepte von Drupal (Node, User, Taxonomy Term, Media, Paragraph)
als <pre>struct</pre>/<pre>enum</pre>-Typen abbildet. Das Modell enthält bewusst '''keine
Logik''' (keine <pre>impl</pre>-Methoden) – es dient als reine Datenrepräsentation.
Eine (De-)Serialisierung (z. B. mit [https://serde.rs/ serde] für
Drupal-JSON:API-Antworten) ist bewusst noch nicht Teil dieses Modells
und folgt in einem späteren Schritt.


== Motivation ==
# '''Storage Layer''' – wie Daten physisch abgelegt werden (Row-basiert vs. Columnar, Kompression, Indizes)
# '''Query Parser & Planner''' – wandelt eine Anfrage (z. B. SQL oder eine DataFrame-API) in einen Ausführungsplan um
# '''Optimizer''' – verbessert den Plan (Predicate Pushdown, Projection Pushdown, Join-Reihenfolge, etc.)
# '''Execution Engine''' – führt den Plan aus und liefert Ergebnisse, oft vektorisiert oder parallelisiert
# '''Memory Management''' – effiziente Nutzung von RAM, oft mit Zero-Copy-Techniken


Drupal modelliert Inhalte über ein generisches Entity-System:
== Warum eignet sich Rust besonders gut für Data Engines? ==
* '''Entity-Typen''' (<pre>node</pre>, <pre>user</pre>, <pre>taxonomy_term</pre>, <pre>media</pre>, …) definieren die grundlegende Art eines Objekts.
* '''Bundles''' (bei Nodes „Content Types" genannt, z. B. <pre>article</pre>, <pre>page</pre>) spezialisieren einen Entity-Typ.
* '''Felder''' (<pre>field_*</pre>) sind pro Bundle konfigurierbar, typisiert und können einfach- oder mehrwertig (Kardinalität) sein.


Um mit einem Drupal-Backend aus Rust heraus zu arbeiten, braucht man
In den letzten Jahren hat sich Rust als bevorzugte Sprache für neue Data-Engine-Projekte etabliert (Polars, Apache DataFusion, InfluxDB IOx, Qdrant, Meilisearch u. v. m.). Die Gründe dafür:
typisierte Gegenstücke zu diesen Konzepten. Das hier beschriebene
Modell bildet diese Struktur 1:1 in Rust ab.


== Grundtypen ==
* '''Performance auf C/C++-Niveau''': Kein Garbage Collector, keine Laufzeit-Overheads – wichtig, wenn Millionen von Zeilen pro Sekunde verarbeitet werden.
* '''Speichersicherheit ohne GC''': Das Ownership- und Borrow-Checker-Modell verhindert Data Races und Speicherfehler zur Compile-Zeit – entscheidend bei hochgradig parallelem Code.
* '''Fearless Concurrency''': Multi-Threading (z. B. über [https://docs.rs/rayon rayon]) lässt sich sicher und ohne Angst vor Race Conditions umsetzen – essenziell für parallele Query-Ausführung.
* '''Exzellentes FFI (Foreign Function Interface)''': Rust-Bibliotheken lassen sich leicht aus Python, Java, C++ etc. aufrufen (z. B. Polars als Python-Paket via PyO3), was die Integration in bestehende Data-Science-Stacks erleichtert.
* '''Starkes Ökosystem für Datenformate''': Mit dem [https://arrow.apache.org/ Apache Arrow]-Ökosystem (<code>arrow-rs</code>) existiert eine gemeinsame, spaltenorientierte In-Memory-Repräsentation, auf der viele Rust-Data-Engines aufbauen.
* '''Zero-Cost Abstractions''': Hochlevel-Code (Iteratoren, Traits, Generics) wird zu genauso schnellem Maschinencode kompiliert wie handgeschriebenes Low-Level-C.


<pre>
== Apache Arrow: Das gemeinsame Fundament ==
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EntityType {
    Node,
    User,
    TaxonomyTerm,
    Media,
    Paragraph,
    File,
    Menu,
    Block,
    Custom(String),
}
 
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PublishStatus {
    Published,
    Unpublished,
}
 
pub type LangCode = String;
</pre>
 
<pre>EntityType</pre> bildet die von Drupal vordefinierten Entity-Typen ab; der
<pre>Custom</pre>-Zweig deckt eigene, per Modul definierte Entity-Typen ab, ohne
das Enum ändern zu müssen.


== Feldwerte: <pre>FieldValue</pre> ==
Viele Rust-Data-Engines nutzen '''Apache Arrow''' als In-Memory-Format. Arrow definiert ein spaltenorientiertes (''columnar'') Speicherlayout, das:


Drupal-Felder sind stark typisiert (Text, Zahl, Datum, Referenz, Bild, …).
* '''Cache-freundlich''' ist, da gleichartige Werte hintereinander im Speicher liegen
Dies wird über ein Enum mit Payload pro Variante abgebildet:
* '''SIMD-Vektorisierung''' ermöglicht (moderne CPUs können mehrere Werte gleichzeitig verarbeiten)
* '''Zero-Copy-Datenaustausch''' zwischen Prozessen/Sprachen erlaubt (z. B. zwischen Rust und Python ohne Serialisierung)


<pre>
<pre>
#[derive(Debug, Clone)]
Row-basiert (klassisch):        Columnar (Arrow):
pub enum FieldValue {
┌─────┬─────┬─────┐            ┌───────────────┐
    Text(String),
│ id  │name │price│            │ id: [1,2,3]  │
    TextLong { value: String, format: Option<String> },
├─────┼─────┼─────┤            ├───────────────┤
    Integer(i64),
│  1  │ A  │ 9.9 │            │ name:[A,B,C]  │
    Decimal(f64),
│  2  │ B  │ 4.5 │            ├───────────────┤
    Boolean(bool),
│  3  │ C  │ 7.2 │            │ price:       │
    DateTime(DateTime<Utc>),
└─────┴─────┴─────┘            │  [9.9,4.5,7.2]│
    Email(String),
                                └───────────────┘
    Link { uri: String, title: Option<String> },
    EntityReference { target_id: u64, target_type: EntityType },
    Image {
        target_id: u64,
        alt: Option<String>,
        title: Option<String>,
        width: Option<u32>,
        height: Option<u32>,
    },
    List(Vec<FieldValue>),
}
 
pub type FieldMap = HashMap<String, FieldValue>;
</pre>
</pre>


* <pre>List(Vec<FieldValue>)</pre> bildet mehrwertige Felder ab (Kardinalität > 1).
Für analytische Abfragen (z. B. <code>SUM(price) WHERE id > 1</code>) ist das columnar-Layout deutlich effizienter, weil nur die relevanten Spalten aus dem Speicher gelesen werden müssen.
* <pre>FieldMap</pre> ist die generische Ablage für alle <pre>field_*</pre>-Werte eines Bundles, adressiert über den Feldmaschinennamen (z. B. <pre>field_tags</pre>).


== Entity-Typen im Detail ==
== Bekannte Data Engines in Rust ==


=== Gemeinsame Basis ===
{| class="wikitable"
 
|-
<pre>
! Projekt !! Zweck !! Kurzbeschreibung
#[derive(Debug, Clone)]
|-
pub struct EntityBase {
| '''[https://pola.rs/ Polars]''' || DataFrame-Engine || Pandas-ähnliche API, extrem schnell dank Lazy Evaluation, Query-Optimizer und Multi-Threading. Nutzt Arrow als Backend.
    pub id: u64,
|-
    pub uuid: Uuid,
| '''[https://datafusion.apache.org/ Apache DataFusion]''' || SQL-Query-Engine || Erweiterbares SQL-Framework, das als Baustein für eigene Datenbanken/Analytics-Tools genutzt werden kann.
    pub entity_type: EntityType,
|-
    pub bundle: String,
| '''[https://github.com/influxdata/influxdb_iox InfluxDB IOx]''' || Zeitreihen-Datenbank || Neue Storage-Engine von InfluxDB, komplett in Rust, basiert auf Arrow & DataFusion.
    pub langcode: LangCode,
|-
    pub created: DateTime<Utc>,
| '''[https://duckdb.org/ DuckDB (Rust-Bindings)]''' || Embedded Analytics-DB || DuckDB selbst ist in C++, hat aber exzellente Rust-Bindings; oft als „SQLite für Analytics" bezeichnet.
    pub changed: DateTime<Utc>,
|-
}
| '''[https://qdrant.tech/ Qdrant]''' || Vektor-Datenbank || Data Engine spezialisiert auf Vektor-Suche/Embeddings, z. B. für KI/RAG-Anwendungen.
</pre>
|}
 
<pre>EntityBase</pre> fasst Eigenschaften zusammen, die praktisch jede
Content-Entity in Drupal besitzt, und wird per Komposition in konkrete
Entity-Structs eingebettet (siehe <pre>Node</pre>, <pre>Media</pre>). Beim späteren
Ergänzen von serde bietet sich dafür <pre>#[serde(flatten)]</pre> an, damit die
Felder von <pre>EntityBase</pre> beim (De-)Serialisieren auf derselben Ebene
wie die restlichen Felder erscheinen.
 
=== Node ===
 
<pre>
#[derive(Debug, Clone)]
pub struct Node {
    pub base: EntityBase,
    pub title: String,
    pub status: PublishStatus,
    pub author_id: u64,
    pub fields: FieldMap,
}
</pre>
 
=== User ===
 
<pre>
#[derive(Debug, Clone)]
pub struct User {
    pub id: u64,
    pub uuid: Uuid,
    pub name: String,
    pub mail: String,
    pub status: bool,
    pub roles: Vec<String>,
    pub created: DateTime<Utc>,
    pub fields: FieldMap,
}
</pre>


<pre>User</pre> nutzt bewusst kein <pre>EntityBase</pre>, da User-Entities in Drupal kein
== Architektur einer einfachen Data Engine (Konzept) ==
<pre>bundle</pre>/<pre>langcode</pre> im klassischen Sinn besitzen und stattdessen
<pre>roles</pre> und einen einfachen <pre>status: bool</pre> (aktiv/blockiert) haben.


=== TaxonomyTerm ===
Um das Zusammenspiel der Komponenten zu verstehen, hier ein vereinfachter Ablauf, wie eine Query verarbeitet wird – exemplarisch angelehnt an DataFusion/Polars:


<pre>
<pre>
#[derive(Debug, Clone)]
  SQL / DataFrame-API
pub struct TaxonomyTerm {
          │
    pub id: u64,
          ▼
     pub uuid: Uuid,
  ┌─────────────┐
     pub vocabulary: String,
  │  Parser    │  → erzeugt einen logischen Plan (AST)
    pub name: String,
  └─────────────┘
     pub description: Option<String>,
          │
    pub parent_id: Option<u64>,
          ▼
    pub weight: i32,
  ┌─────────────┐
    pub fields: FieldMap,
  │  Optimizer  │  → Predicate/Projection Pushdown,
}
  └─────────────┘     Constant Folding, Join-Reordering
          │
          ▼
  ┌─────────────┐
  │  Physical  │  → wählt konkrete Ausführungsoperatoren
  │  Planner  │     (Hash-Join, Sort-Merge-Join, ...)
  └─────────────┘
          │
          ▼
  ┌─────────────┐
  │  Execution  │  → vektorisierte, parallele Ausführung
  │  Engine    │     über Arrow-RecordBatches
  └─────────────┘
          │
          ▼
      Ergebnis
</pre>
</pre>


Bildet Taxonomiebegriffe inkl. hierarchischer Eltern-Referenz
=== Minimalbeispiel mit Polars ===
(<pre>parent_id</pre>) und Sortiergewicht (<pre>weight</pre>) ab.


=== Media & Paragraph ===
<syntaxhighlight lang="rust">
use polars::prelude::*;


<pre>
fn main() -> PolarsResult<()> {
#[derive(Debug, Clone)]
    // Lazy Query: wird erst beim .collect() tatsächlich ausgeführt
pub struct Media {
    let df = LazyCsvReader::new("daten.csv")
    pub base: EntityBase,
        .finish()?
    pub name: String,
        .filter(col("price").gt(lit(5.0)))
    pub fields: FieldMap,
        .group_by([col("category")])
}
        .agg([col("price").sum().alias("gesamt_umsatz")])
        .sort("gesamt_umsatz", Default::default())
        .collect()?; // Optimizer plant & führt hier erst aus


#[derive(Debug, Clone)]
    println!("{df}");
pub struct Paragraph {
     Ok(())
     pub id: u64,
    pub uuid: Uuid,
    pub bundle: String,
    pub fields: FieldMap,
}
}
</pre>
</syntaxhighlight>
 
<pre>Paragraph</pre> ist bewusst schlank gehalten, da Paragraphs in Drupal keine
eigenständigen, direkt aufrufbaren Entities mit URL/Alias sind, sondern
stets über eine Host-Entity referenziert werden.
 
== Schema-Introspektion (optional) ==
 
Für Anwendungsfälle, in denen Feld- und Bundle-Definitionen selbst
(nicht nur Werte) abgebildet werden müssen – etwa um ein Drupal-Schema
zu spiegeln oder Formulare dynamisch zu generieren:
 
<pre>
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cardinality {
    Single,
    Multiple,
    Unlimited,
}
 
#[derive(Debug, Clone)]
pub struct FieldDefinition {
    pub field_name: String,
    pub field_type: String,
    pub cardinality: Cardinality,
    pub required: bool,
}
 
#[derive(Debug, Clone)]
pub struct BundleDefinition {
    pub entity_type: EntityType,
    pub bundle: String,
    pub label: String,
    pub fields: Vec<FieldDefinition>,
}
</pre>
 
== Design-Entscheidungen ==
 
{| class="wikitable"
|-
! Entscheidung !! Begründung
|-
| <pre>FieldValue</pre> als Enum mit Payload pro Variante || Bildet Drupals starke Feldtypisierung typsicher ab, statt alles als <pre>String</pre> zu behandeln.
|-
| <pre>FieldMap = HashMap<String, FieldValue></pre> || Bundles sind zur Compile-Zeit nicht bekannt; generische Map bleibt flexibel für beliebige <pre>field_*</pre>-Namen.
|-
| <pre>EntityBase</pre> als Kompositionsfeld || Vermeidet Duplikation gemeinsamer Properties zwischen Node/Media, ohne Vererbung (die es in Rust nicht gibt).
|-
| Kein <pre>impl</pre>-Block || Modell bleibt reine Datenschicht; Geschäftslogik (Validierung, API-Calls) gehört in separate Module.
|-
| <pre>EntityType::Custom(String)</pre> || Deckt durch Contrib-/Custom-Module definierte Entity-Typen ab, ohne das Enum erweitern zu müssen.
|}
 
== Offene Erweiterungspunkte ==


* Weitere <pre>FieldValue</pre>-Varianten je nach genutzten Feldtypen (z. B. <pre>Geolocation</pre>, <pre>Address</pre>, <pre>File</pre> losgelöst von <pre>Image</pre>).
Wichtig ist hier das Prinzip der '''Lazy Evaluation''': Statt jeden Schritt sofort auszuführen, baut Polars zunächst einen logischen Plan auf. Erst beim Aufruf von <code>.collect()</code> wird dieser optimiert (z. B. wird der Filter so früh wie möglich angewendet, um weniger Daten zu verarbeiten) und dann parallel ausgeführt.
* Übersetzungen (<pre>translations: HashMap<LangCode, FieldMap></pre>), falls Drupal-Mehrsprachigkeit (Content Translation) abgebildet werden soll.
* Konkretes Mapping auf die JSON:API-Hüllstruktur (<pre>data</pre>, <pre>attributes</pre>, <pre>relationships</pre>, <pre>included</pre>), falls direkt gegen die Drupal-JSON:API deserialisiert werden soll, statt ein eigenes flaches Modell zu pflegen.


== Verwendete Crates ==
== Zusammenfassung ==


* [https://crates.io/crates/chrono chrono] – Zeitstempel (<pre>created</pre>, <pre>changed</pre>)
* Eine '''Data Engine''' verarbeitet Anfragen über Storage-, Planungs- und Ausführungsschichten hinweg.
* [https://crates.io/crates/uuid uuid] – Entity-UUIDs
* '''Rust''' bietet durch Speichersicherheit ohne GC, sichere Nebenläufigkeit und Zero-Cost-Abstraktionen ideale Voraussetzungen für performante, robuste Data Engines.
* '''Apache Arrow''' ist das gemeinsame columnar In-Memory-Format, auf dem viele moderne Rust-Data-Engines (Polars, DataFusion, InfluxDB IOx) aufbauen.
* Wer selbst experimentieren möchte, findet mit '''DataFusion''' einen guten Baukasten für eigene SQL-Engines und mit '''Polars''' eine ausgereifte, sofort nutzbare DataFrame-Engine.


Hinweis: [https://serde.rs/ serde] (für die (De-)Serialisierung) ist
== Weiterführende Links ==
bewusst noch nicht Teil dieses Modells und wird in einem späteren
Schritt ergänzt.


== Siehe auch ==
* [https://arrow.apache.org/ Apache Arrow]
* [https://docs.pola.rs/ Polars Dokumentation]
* [https://datafusion.apache.org/ Apache DataFusion]
* [https://doc.rust-lang.org/book/ The Rust Programming Language Book]


* Drupal-Dokumentation: [https://www.drupal.org/docs/drupal-apis/entity-api Entity API]
* [[XWiki Datenmodell (Rust) ]]
* Drupal-Dokumentation: [https://www.drupal.org/docs/core-modules-and-themes/core-modules/jsonapi-module JSON:API-Modul]
* [[MediaWiki – Datenmodell (Rust)]]


[[Kategorie:Rust]]
==Hinweis==
[[Kategorie:Drupal]]
Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).

Aktuelle Version vom 12. August 2026, 14:20 Uhr

Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).

Was ist eine Data Engine?

Eine Data Engine (auch Query Engine oder Data Processing Engine genannt) ist die Softwareschicht, die dafür zuständig ist, Daten effizient zu speichern, zu lesen, zu transformieren und abzufragen. Sie bildet das Herzstück von Datenbanken, Analytics-Tools und Big-Data-Frameworks – von klassischen SQL-Datenbanken (PostgreSQL, SQLite) über analytische Systeme (ClickHouse, DuckDB) bis hin zu verteilten Big-Data-Plattformen (Apache Spark).

Typischerweise besteht eine Data Engine aus folgenden Bausteinen:

  1. Storage Layer – wie Daten physisch abgelegt werden (Row-basiert vs. Columnar, Kompression, Indizes)
  2. Query Parser & Planner – wandelt eine Anfrage (z. B. SQL oder eine DataFrame-API) in einen Ausführungsplan um
  3. Optimizer – verbessert den Plan (Predicate Pushdown, Projection Pushdown, Join-Reihenfolge, etc.)
  4. Execution Engine – führt den Plan aus und liefert Ergebnisse, oft vektorisiert oder parallelisiert
  5. Memory Management – effiziente Nutzung von RAM, oft mit Zero-Copy-Techniken

Warum eignet sich Rust besonders gut für Data Engines?

In den letzten Jahren hat sich Rust als bevorzugte Sprache für neue Data-Engine-Projekte etabliert (Polars, Apache DataFusion, InfluxDB IOx, Qdrant, Meilisearch u. v. m.). Die Gründe dafür:

  • Performance auf C/C++-Niveau: Kein Garbage Collector, keine Laufzeit-Overheads – wichtig, wenn Millionen von Zeilen pro Sekunde verarbeitet werden.
  • Speichersicherheit ohne GC: Das Ownership- und Borrow-Checker-Modell verhindert Data Races und Speicherfehler zur Compile-Zeit – entscheidend bei hochgradig parallelem Code.
  • Fearless Concurrency: Multi-Threading (z. B. über rayon) lässt sich sicher und ohne Angst vor Race Conditions umsetzen – essenziell für parallele Query-Ausführung.
  • Exzellentes FFI (Foreign Function Interface): Rust-Bibliotheken lassen sich leicht aus Python, Java, C++ etc. aufrufen (z. B. Polars als Python-Paket via PyO3), was die Integration in bestehende Data-Science-Stacks erleichtert.
  • Starkes Ökosystem für Datenformate: Mit dem Apache Arrow-Ökosystem (arrow-rs) existiert eine gemeinsame, spaltenorientierte In-Memory-Repräsentation, auf der viele Rust-Data-Engines aufbauen.
  • Zero-Cost Abstractions: Hochlevel-Code (Iteratoren, Traits, Generics) wird zu genauso schnellem Maschinencode kompiliert wie handgeschriebenes Low-Level-C.

Apache Arrow: Das gemeinsame Fundament

Viele Rust-Data-Engines nutzen Apache Arrow als In-Memory-Format. Arrow definiert ein spaltenorientiertes (columnar) Speicherlayout, das:

  • Cache-freundlich ist, da gleichartige Werte hintereinander im Speicher liegen
  • SIMD-Vektorisierung ermöglicht (moderne CPUs können mehrere Werte gleichzeitig verarbeiten)
  • Zero-Copy-Datenaustausch zwischen Prozessen/Sprachen erlaubt (z. B. zwischen Rust und Python ohne Serialisierung)
Row-basiert (klassisch):        Columnar (Arrow):
┌─────┬─────┬─────┐             ┌───────────────┐
│ id  │name │price│             │ id: [1,2,3]   │
├─────┼─────┼─────┤             ├───────────────┤
│  1  │ A   │ 9.9 │             │ name:[A,B,C]  │
│  2  │ B   │ 4.5 │             ├───────────────┤
│  3  │ C   │ 7.2 │             │ price:        │
└─────┴─────┴─────┘             │  [9.9,4.5,7.2]│
                                 └───────────────┘

Für analytische Abfragen (z. B. SUM(price) WHERE id > 1) ist das columnar-Layout deutlich effizienter, weil nur die relevanten Spalten aus dem Speicher gelesen werden müssen.

Bekannte Data Engines in Rust

Projekt Zweck Kurzbeschreibung
Polars DataFrame-Engine Pandas-ähnliche API, extrem schnell dank Lazy Evaluation, Query-Optimizer und Multi-Threading. Nutzt Arrow als Backend.
Apache DataFusion SQL-Query-Engine Erweiterbares SQL-Framework, das als Baustein für eigene Datenbanken/Analytics-Tools genutzt werden kann.
InfluxDB IOx Zeitreihen-Datenbank Neue Storage-Engine von InfluxDB, komplett in Rust, basiert auf Arrow & DataFusion.
DuckDB (Rust-Bindings) Embedded Analytics-DB DuckDB selbst ist in C++, hat aber exzellente Rust-Bindings; oft als „SQLite für Analytics" bezeichnet.
Qdrant Vektor-Datenbank Data Engine spezialisiert auf Vektor-Suche/Embeddings, z. B. für KI/RAG-Anwendungen.

Architektur einer einfachen Data Engine (Konzept)

Um das Zusammenspiel der Komponenten zu verstehen, hier ein vereinfachter Ablauf, wie eine Query verarbeitet wird – exemplarisch angelehnt an DataFusion/Polars:

   SQL / DataFrame-API
          │
          ▼
   ┌─────────────┐
   │   Parser    │  → erzeugt einen logischen Plan (AST)
   └─────────────┘
          │
          ▼
   ┌─────────────┐
   │  Optimizer  │  → Predicate/Projection Pushdown,
   └─────────────┘     Constant Folding, Join-Reordering
          │
          ▼
   ┌─────────────┐
   │  Physical   │  → wählt konkrete Ausführungsoperatoren
   │   Planner   │     (Hash-Join, Sort-Merge-Join, ...)
   └─────────────┘
          │
          ▼
   ┌─────────────┐
   │  Execution  │  → vektorisierte, parallele Ausführung
   │   Engine    │     über Arrow-RecordBatches
   └─────────────┘
          │
          ▼
       Ergebnis

Minimalbeispiel mit Polars

use polars::prelude::*;

fn main() -> PolarsResult<()> {
    // Lazy Query: wird erst beim .collect() tatsächlich ausgeführt
    let df = LazyCsvReader::new("daten.csv")
        .finish()?
        .filter(col("price").gt(lit(5.0)))
        .group_by([col("category")])
        .agg([col("price").sum().alias("gesamt_umsatz")])
        .sort("gesamt_umsatz", Default::default())
        .collect()?; // Optimizer plant & führt hier erst aus

    println!("{df}");
    Ok(())
}

Wichtig ist hier das Prinzip der Lazy Evaluation: Statt jeden Schritt sofort auszuführen, baut Polars zunächst einen logischen Plan auf. Erst beim Aufruf von .collect() wird dieser optimiert (z. B. wird der Filter so früh wie möglich angewendet, um weniger Daten zu verarbeiten) und dann parallel ausgeführt.

Zusammenfassung

  • Eine Data Engine verarbeitet Anfragen über Storage-, Planungs- und Ausführungsschichten hinweg.
  • Rust bietet durch Speichersicherheit ohne GC, sichere Nebenläufigkeit und Zero-Cost-Abstraktionen ideale Voraussetzungen für performante, robuste Data Engines.
  • Apache Arrow ist das gemeinsame columnar In-Memory-Format, auf dem viele moderne Rust-Data-Engines (Polars, DataFusion, InfluxDB IOx) aufbauen.
  • Wer selbst experimentieren möchte, findet mit DataFusion einen guten Baukasten für eigene SQL-Engines und mit Polars eine ausgereifte, sofort nutzbare DataFrame-Engine.

Hinweis

Hinweis: Diese Inhalte wurden mit Unterstützung von Künstlicher Intelligenz erstellt und redaktionell überprüft (Transparenzhinweis gemäß Art. 50 EU AI Act).