1
 
 
Profil
In deinem persönlichen Profilbereich kannst du den Status deiner Bewerbung einsehen, unvollständige Bewerbungen zwischenspeichern und aktuelle News und Events einsehen
17. Dezember 2025

Learning to Rank ist Rocket Science: Wie Clojure unser Machine Learning mit Deep Neural Networks beschleunigt

Worum geht es in diesem Artikel?

Im E-Commerce entscheidet die Suche in Sekunden, ob Kund*innen bleiben oder abspringen. Bei OTTO setzen wir dafür auf Learning to Rank: Ein Deep Neural Network lernt aus Millionen echter Interaktionen, welche Produkte für eine Suchanfrage wirklich relevant sind.

In diesem Artikel zeigen wir, warum wir für unser produktives Learning to Rank auf Clojure setzen – und wie Clojure und Polylith uns dabei unterstützen, komplexe Machine Learning Pipelines stabil, effizient und gut wartbar zu betreiben.

Von der Deep Space 1 zu Learning to Rank bei OTTO

Jahrelang waren Gradient Boosted Decision Trees (GBDTs) die unangefochtenen Champions, wenn es darum ging, präzise Rankings für unsere Kund:innen zu erstellen. Sie sind zuverlässig, interpretierbar und erstaunlich leistungsfähig. Doch mit unserem Deep Neural Network konnten wir zeigen, dass diese Performance noch übertroffen werden kann.

Unser Ziel dabei: Das Sucherlebnis unserer Kund*innen auf die nächste Stufe zu heben, effizient und kostengünstig. Wir nutzen hierzu Clojure, eine Programmiersprache, die es den Entwicklern einfach macht, den Code zu warten und zu erweitern. Eine Programmiersprache, die Spaß macht.

Was ist Clojure?: Eine Programmiersprache, basierend auf Lisp. Lisp (kurz für "List Processor") ist eine Programmiersprache, die 1958 von John McCarthy am MIT entwickelt wurde. Sie ist bekannt für ihre einzigartige Syntax, ihre mächtigen Metaprogrammierungsfähigkeiten und ihren starken Einfluss auf die Entwicklung anderer Programmiersprachen.

Lisp wurde bereits im Weltraum eingesetzt. Genauer gesagt, war Lisp ein wichtiger Teil der Software, die auf der Raumsonde Deep Space 1 (DS1) zum Einsatz kam. Das System zur autonomen Steuerung der Sonde (Remote Agent) wurde in Lisp geschrieben.

Raumsonde Deep Space 1 im Weltall/ Quelle: NASA
Raumsonde Deep Space 1 im Weltall/ Quelle: NASA

Ein bemerkenswerter Aspekt der Verwendung von Lisp auf DS1 war die Möglichkeit, eine Read-Eval-Print Loop (REPL) auf der Raumsonde zu betreiben. Dies erwies sich als äußerst wertvoll beim Debuggen von Problemen im All, da die Ingenieure in der Lage waren, den Code auf dem Raumschiff zu inspizieren und anzupassen. So konnten sogar Probleme “im Flug” gelöst werden.

Die Programmiersprache Python gilt als die erste Wahl für Machine Learning (ML). Trotzdem haben wir uns für die Implementierung unseres auf einem Deep Neural Network basierenden Learning-to-Rank-Service bewusst für den Lisp-Dialekt Clojure entschieden. Das klingt auf den ersten Blick ungewöhnlich. Doch die Fähigkeit, komplexe, fehlertolerante Systeme in einer der anspruchsvollsten Umgebungen zu betreiben, hat Lisp bereits auf der Deep Space 1 unter Beweis gestellt und macht es zum idealen Werkzeug für KI und autonome Systeme. Grund genug, uns damit zu beschäftigen.

Clojure im Überblick: Sprache, Prinzipien und Praxis

Clojure ist eine dynamische, funktionale Programmiersprache auf der Java Virtual Machine (JVM). Sie verbindet die Vorteile funktionaler Programmierung mit der Robustheit und dem Ökosystem der JVM.

Die Idee hinter Clojure basiert auf Lisp, eine der wichtigsten Programmiersprachen in der Geschichte der Informatik. Seine innovativen Ideen und Konzepte haben die Entwicklung vieler Programmiersprachen grundlegend beeinflusst und prägen sie bis heute. Lisp ist ein Beweis dafür, dass gute Ideen zeitlos sind und auch nach Jahrzehnten noch relevant und wertvoll sein können.

Comic-Sketch zu Lisp/ Quelle: xkcd
Comic-Sketch zu Lisp/ Quelle: xkcd

Clojure wurde von Rich Hickey entwickelt und 2007 veröffentlicht. Dabei legte er bei der Entwicklung großen Wert auf Einfachheit, Klarheit und Pragmatismus. Er betonte Konzepte wie Immutability, Higher-Order Functions und Metaprogramming, immer mit dem Ziel, eine praxisnahe und gut erlernbare Sprache zu schaffen.
Auch über die Eleganz der Sprache hinaus bietet Clojure für uns viele Vorteile:

• Concurrency: Clojure wurde von Grund auf für die Handhabung von Parallelität und Nebenläufigkeit entwickelt und hilft uns, moderne Hardware voll auszulasten.

• Immutability: Standardmäßig sind alle Datenstrukturen in Clojure unveränderlich. Der Zustand der Anwendung ist somit jederzeit eindeutig und nachvollziehbar.

• JVM Interop: Clojure läuft auf der Java Virtual Machine und greift direkt auf das große, ausgereifte Ökosystem der Java-Bibliotheken zu. So können wir bewährte Lösungen für Datenbankzugriff, Netzwerkkommunikation etc. einfach nutzen.

• REPL-basierte Entwicklung: Clojure unterstützt eine interaktive und dynamische Entwicklungsumgebung (Read-Eval-Print Loop - REPL). Entwickler können Code inkrementell schreiben, testen und debuggen, während die Anwendung läuft, das sorgt für schnelle Feedback-Zyklen und effizientes Prototyping.

• Funktionale Programmierung: Im Kern setzt Clojure auf Pure Functions ohne Seiteneffekte. Das erhöht Testbarkeit und Parallelisierbarkeit und unterstützt ein klareres Softwaredesign.

Warum Clojure für Machine Learning?

In der Welt des Machine Learning sind Python und R die führenden Sprachen. Sie bieten einen einfachen Einstieg, viele Tools und umfangreiche Dokumentation, ideal für Exploration und Prototyping. Doch in unserem wirtschaftlichen Umfeld müssen wir einen größeren Fokus auf den operativen Betrieb und die Kosteneffizienz der Lösung legen. Deshalb setzen wir auf Clojure und profitieren von den folgenden Vorteilen:

• (Kosten-)effizente Datenpipelines und modulares Feature Engineering: Transformationen in unseren Datenpipelines sind von Natur aus funktional. Es müssen große Datensätze manipuliert, gefiltert und aggregiert werden, um neue Daten zu erhalten. Die meisten Operationen sind voneinander unabhängig und können als Stream verarbeitet werden. Das führt zu besserer Parallelisierung, besserer Testbarkeit und weniger Speicherbedarf.

• Robuster Live-Betrieb: Die JVM bietet uns dabei gute Performance bei Laufzeit und Speicherverbrauch. Ihre Monitoring-Tools helfen uns, Performance- und Speicherprobleme gezielt zu analysieren und zu beheben.

So betreiben wir unser Deep Neural Network

Im Zentrum steht eine PostgreSQL-Datenbank, die Relevanzscores für Produkte einer Suchanfrage speichert. Clojure-Microservices greifen im Serving auf diese Scores zu, um die eingehenden Suchergebnisse (Retrievals) anhand ihrer Relevanz zu bewerten und entsprechend zu ranken.

Die Scores stammen aus den Predictions unseres Deep Neural Networks. Grundlage sind Relevanzsignale und Bewegungsdaten unserer Kund*innen sowie die Produktdaten. Clojure-Jobs in AWS bereiten die Daten für das Modelltraining und die Vorhersagen auf.

Wir orientieren uns beim Aufbau der ETL-Pipelines am UNIX-Pipe-Konzept: Kleine, spezialisierte Jobs übernehmen Teilaufgaben, wie das Lesen von Rohdaten aus einem Kafka-Topic oder das Transformieren der benötigten Daten. Jobs legen ihre Resultate bzw. Zwischenergebnisse als Dateien in S3 ab. Darauffolgende Jobs bauen auf diesen Ergebnissen auf, lesen die Daten erneut ein und berechnen dann beispielsweise das Ranking für einen Suchbegriff.

Datenströme statt Datenblöcke: Streaming-Pipelines mit core.async

Unsere Jobs folgen dem Streaming-Pattern. Daten werden unabhängig voneinander gelesen, gefiltert, gemappt und schließlich geschrieben. Den Datenfluss zwischen den Verarbeitungsschritten realisieren wir dabei mithilfe von core.async Channels.

Streaming-Architektur eines Clojure-Jobs
Streaming-Architektur eines Clojure-Jobs

core.async-Channels ermöglichen die parallele Datenverarbeitung, indem mehrere Threads gleichzeitig auf die Channels zugreifen. So nutzen wir mehrere CPU-Kerne pro Verarbeitungsschritt effizient aus.

Wir verwenden dabei das Threading-Macro ->> in Clojure, um mehrere Funktionen übersichtlich zu verketten. Das macht den Code leichter verständlich und vereinfacht die Nachverfolgung der Datenflüsse.

Clojure Code mit Pipeline-Funktionen
Clojure Code mit Pipeline-Funktionen

Schema-basierte Datenablage mit Protobuf

Unsere Jobs verarbeiten Dateien im Protobuf-Format und komprimieren sie mit lz4 oder zstandard. Folgende Vorteile haben wir dadurch erreicht:

• Dokumentation der Daten: Protobuf ist ein schema-basiertes Format und dokumentiert den Inhalt eines Datensatzes. Ändern sich Anforderungen, passen wir das Schema an und halten die Beschreibung so aktuell.

• Schema-basierte Validierung: Das Schema stellt sicher, dass alle Datentypen eingehalten werden und die erforderlichen Daten vorhanden sind. Unsere Jobs validieren diese Anforderungen automatisch beim Lesen und Schreiben der Dateien.

• Höhere Effizienz: Durch das binäre Datenformat sind unsere Jobs deutlich weniger mit String-Verarbeitung beschäftigt und benötigen insgesamt weniger Zeit für das Lesen und Schreiben von Dateien.

• Stream-Verarbeitung: Unsere Protobuf-Files werden in Clojure als Stream verarbeitet, um den Speicherverbrauch der Jobs zu reduzieren.

Einfaches Code-Sharing mit Polylith

In der Vergangenheit haben wir gemeinsam genutzten Code über Libraries geteilt und dadurch ein kompliziertes Abhängigkeitsgeflecht geschaffen. In diesem Projekt wollten wir die Arbeit mit Abhängigkeiten deutlich vereinfachen und setzen deshalb auf den Monorepo-Ansatz. Polylith hilft uns bei der Verwaltung und Organisation der Abhängigkeiten.

Grafik zu Code-Sharing mit Polylith
Grafik zu Code-Sharing mit Polylith

In Summe erhalten wir damit diese Vorteile:

• Projekte und Libraries sind Verzeichnisse: Libraries, Jobs und Services organisieren wir als „Building Blocks“ in separaten Verzeichnissen innerhalb eines einzigen Repositories. Abhängigkeiten sind nur Verweise auf diese Verzeichnisse. Ein übergreifendes Management mehrerer Repositories entfällt.

• Alle Abhängigkeiten sind sichtbar: Alle Applikationen nutzen immer den aktuellen Stand des gemeinsamen Codes. Ein separates Versionieren wie bei Libraries entfällt, und veraltete Abhängigkeiten sind ausgeschlossen. Entwicklungsumgebungen zeigen alle Abhängigkeiten und Refactorings erwischen alle beeinflussten Stellen im Code.

• Sicherheit bei Umbauten: Ändern wir etwas an dem Verhalten der Software, führen wir alle Tests im Monorepo aus und prüfen so direkt alle Zugriffe.

• GitHub-Actions-Pipeline managed CI/CD: Polylith erkennt selbstständig, welche Projekte verändert wurden und neugebaut werden müssen. Eine Pipeline führt alle Tests aus, erzeugt die Artefakte und deployed sie anschließend in die Entwicklungs- und Produktionsumgebung.

Fazit

Zugegeben, keine Raketenwissenschaft, aber die Vorteile von Lisp und Clojure sind auch heute noch aktuell und helfen uns dabei, effiziente ML- Pipelines zu erstellen, neue Ansätze schnell zu verproben und im Budget zu bleiben.
Wenn du einen ähnlichen Weg gehen willst, lohnt sich ein Blick auf diese Themen:

Clojure, als eine datenzentrierte, simple und performante Sprache
Monorepo, für übersichtliche Quellcodeorganisation
Protobuf, für Dokumentation, Validierung und schnelle Verarbeitung
Solltest du tiefer in die Grundlagen von Learning to Rank einsteigen wollen, findest du in unseren vorherigen Artikeln den passenden Einstieg:

Teil 1: Einführung in Learning to Rank für die E-Commerce-Suche
Teil 2: Learning to Rank – To the Moon and Back (-propagation): Wie Deep Neural Networks lernen, was du liebst
Teil 3: Datenerhebung für Learning to Rank

Auch für uns geht die Reise weiter 🚀. Wir planen personalisierte Rankings, die in Echtzeit berechnet werden müssen und sich nicht mehr im Voraus vorberechnen lassen. Wir sind überzeugt, dass wir auch diese Herausforderung mit Clojure meistern werden.

Möchtest du Teil des Teams werden?

1 Person gefällt das

0Noch keine Kommentare

Dein Kommentar
Antwort auf:  Direkt auf das Thema antworten

Geschrieben von

Team Jarvis
Team Jarvis
Developer Team @ OTTO

Ähnliche Beiträge

Gespeichert!

We want to improve out content with your feedback.

How interesting is this blogpost?

We have received your feedback.