Categories
AI Development Java

Llama3.java

Llama3.java

Practical Llama 3, 3.1 and 3.2 inference implemented in a single Java file.


This project from Alfonso² Peterssen is on GitHub.

Installation and test

Download Llama3.java:

git clone https://github.com/mukel/llama3.java.git

Download Llama:

cd llama3.java
# Llama 3.2 (3B)
curl -L -O https://huggingface.co/mukel/Llama-3.2-3B-Instruct-GGUF/resolve/main/Llama-3.2-3B-Instruct-Q4_0.gguf

Execute:

java --enable-preview --source 21 --add-modules jdk.incubator.vector Llama3.java -i --model Llama-3.2-3B-Instruct-Q4_0.gguf

For more options see documentation on GitHub. For more modells see Alfonso² Peterssen page on HuggingFace.

Qwen 2.5-Coder

Download Qwen:

cd llama3.java
# Llama 3.2 (3B)
curl -L -O https://huggingface.co/mukel/Qwen2.5-Coder-3B-Instruct-GGUF/resolve/main/Qwen2.5-Coder-3B-Instruct-Q4_0.gguf

Execute:

java --enable-preview --source 21 --add-modules jdk.incubator.vector Llama3.java -i --model Qwen2.5-Coder-3B-Instruct-Q4_0.gguf

Unfortunatly it failed:

java --enable-preview --source 21 --add-modules jdk.incubator.vector Llama3.java -i --model Qwen2.5-Coder-3B-Instruct-Q4_0.gguf
WARNING: Using incubator modules: jdk.incubator.vector
Note: Llama3.java uses preview features of Java SE 21.
Note: Recompile with -Xlint:preview for details.
Parse Qwen2.5-Coder-3B-Instruct-Q4_0.gguf: 318 millis
Load LlaMa model: 258 millis
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
        at com.llama4j.ModelLoader.loadModel(Llama3.java:696)
        at com.llama4j.ModelLoader.loadModel(Llama3.java:686)
        at com.llama4j.Llama3.main(Llama3.java:274)
Categories
AI Development Linux

OpenWebUI

Architektur von OpenWebUI

OpenWebUI ist eine selbstgehostete Benutzeroberfläche zur Interaktion mit LLMs (Large Language Models). Es kombiniert Frontend und API-Layer in einer einzigen Webanwendung und bietet Unterstützung für verschiedene Sprachmodelle – lokal oder über APIs (z. B. OpenAI, Claude, Ollama, LM Studio).

Komponentenübersicht

Ein typisches OpenWebUI-System besteht aus folgenden Hauptkomponenten:

  • Benutzeroberfläche (Client)

    • Darstellung der Chats
    • Eingabefeld für Nutzereingaben
    • Darstellung von Modellantworten
    • Chat-Historie und Session-Management
  • API-Layer (integriert in OpenWebUI)

    • Verarbeitung und Weiterleitung von Anfragen
    • Token-Management (z. B. API-Keys)
    • Sicherheit (z. B. Authentifizierung)
    • Kontextverwaltung (z. B. System-Prompts, Rollen, Modelleinstellungen)
  • Sprachmodell(e)

    • Externe APIs: OpenAI (GPT-4), Anthropic (Claude), etc.
    • Lokale Modelle über Ollama, LM Studio, OpenRouter u.a.
    • Mehrere Modelle gleichzeitig konfigurierbar

Architekturdiagramm

flowchart LR
    subgraph OpenWebUI ["OpenWebUI (Self-Hosted Webapp)"]
        A[Benutzeroberfläche<br>• Eingabe & Anzeige<br>• Session-Verwaltung]
        B[API-Layer<br>• Anfrageverarbeitung<br>• Sicherheit & Token<br>• Modellrouting]
    end

    C[Sprachmodell<br>• GPT-4 / OpenAI API<br>• LLaMA / Ollama<br>• Claude / API]

    A -->|Anfrage| B
    B -->|Verarbeitete Anfrage| C
    C -->|Antwort| B
    B -->|Formatierte Antwort| A

Besonderheiten

  • Self-Contained: OpenWebUI enthält sowohl die Benutzeroberfläche als auch den API-Layer – keine zusätzliche Middleware nötig.
  • Multimodellfähig: Nutzer können zwischen mehreren Modellen und Providern wählen.
  • Privatsphäre: Lokale Modellanbindung (z. B. Ollama) möglich – keine Datenübertragung an Dritte.
  • Plugins & Funktionen:
    • Dateien hochladen und durchsuchen
    • Konversationsverwaltung
    • Rollenbasierte Prompts
    • Unterstützung für Bildmodelle (optional)

Hosting-Optionen

  • Docker (empfohlen)
  • Manuelle Installation (Node.js + Backend)
  • Unterstützung für Authentifizierung, mehrere Benutzer und persistente Einstellungen

Fazit

OpenWebUI ist eine leistungsstarke, modulare Lösung für den selbstgehostenen Einsatz von LLMs. Durch die Integration von Frontend und API eignet es sich besonders für Nutzer, die ohne Cloud-Dienste arbeiten wollen oder mehrere Modelle flexibel einbinden möchten.

OpenWebUI mit Docker und Traefik installieren

Diese Anleitung beschreibt die Installation von OpenWebUI auf einem Webserver mithilfe von Docker und Traefik als Reverse Proxy.

Voraussetzungen

  • Ein Linux-Server mit root-Zugriff
  • Installiertes Docker und Docker Compose
  • Domain (z. B. webui.example.com)
  • Docker-Netzwerk für Traefik, z. B. traefik-net
  • Bereits laufender Traefik-Container

1. Verzeichnisstruktur anlegen

mkdir -p /data/docker/openwebui
cd /data/docker/openwebui

2. Docker-Compose-Datei erstellen

Erstelle eine Datei namens docker-compose.yml mit folgendem Inhalt:

services:
  openwebui:
    image: ghcr.io/open-webui/open-webui:main
    container_name: openwebui
    restart: unless-stopped
    networks:
      - traefik-net
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.openwebui.rule=Host(`webui.kaulbach.de`)"
      - "traefik.http.routers.openwebui.entrypoints=websecure"
      - "traefik.http.routers.openwebui.tls.certresolver=letsencrypt"
      - openwebui_data:/app/backend/data

volumes:
  openwebui_data:

networks:
  traefik-net:
    external: true

3. Container starten

docker compose up -d

4. Zugriff auf die Weboberfläche

Rufe OpenWebUI im Browser auf:

https://webui.kaulbach.de

5. Logs und Verwaltung

  • Logs anzeigen: docker logs -f openwebui
  • Stoppen: docker compose down

6. Updates

docker compose pull
docker compose up -d

Weitere Infos

https://github.com/open-webui/open-webui

Einrichten

Admin

Beim ersten Login wird automatisch ein Admin-Benutzer angelegt.

ChatGPT

Im Administrationsbereich über Einstellungen -> Verbindungen kann der OpenAI-API-Key eingetragen werden:

OpenWebUISettings

Groq

Im Administrationsbereich über Einstellungen -> Verbindungen über das Plus-Zeichen eine neue Verbindung anlegen und die Groq-URL und API-Key eintragen: URL: https://api.groq.com/openai/v1

Modelle

Im Administrationsbereich über Einstellungen -> Modelle kann ausgewählt werden, welche Modelle den Benutzern zur Auswahl angezeigt werden sollen.

Categories
Database Development Linux

PostgreSQL: Backup

Ich musste grade von meiner Postgres-DB einen Dump ziehen und auf eine zweite DB wieder einspielen.
Problem dabei: ich habe Postgres (pg_dump, psql) nicht auf meinem Host System installiert und möchte daher Docker verwenden.
Zuerst habe ich alle relevanten Parameter in eine Datei (.env) gespeichert:

POSTGRES_USER=myuser
POSTGRES_PASSWORD=mypassword
POSTGRES_DB=mydatabase
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_BACKUPFILE=backup.sql

Dann habe ich mir source .env die Parameter als Umgebungsvariablen in meiner Shell gesetzt und konnte dann ein Backup meiner Datenbank erstellen:

docker run --rm \
  --network=host \
  -e PGPASSWORD=$POSTGRES_PASSWORD \
  postgres:17.2 \
  pg_dump -h $POSTGRES_HOST -U $POSTGRES_USER $POSTGRES_DB > $POSTGRES_BACKUPFILE

Anschließend habe ich die Parameter (zB Hostname) für das Zielsystem angepasst und wieder source .env ausgeführt um dann das Backup auf der Ziel-DB einspielen zu können:

docker run --rm \
  --network=host \
  -v ./$POSTGRES_BACKUPFILE:/script.sql \
  -e PGPASSWORD=$POSTGRES_PASSWORD \
  postgres:17.2 \
  psql -h $POSTGRES_HOS -p $POSTGRES_PORT -U $POSTGRES_USER -d $POSTGRES_DB -f script.sql
Categories
AI Development

OpenAI Whisper

Ich habe Speech to Text getestet und mir dazu auf YouTube ein Video einer Haushaltsrede gesucht und mittels Onlinedienst als MP3 heruntergeladen.
Anschließend mit OpenAI Whisper in Text umgewandelt. Die Umwandlung hat ungefähr so lange gedauert wie die Audiodatei. Das Ergebnis ist recht gut geworden, muss aber auf jeden Fall kontrolliert und korrigiert werden.

Installation

cd ~/whispertest
python3 -m venv pyvenv
source pyvenv/bin/activate
pip install openai-whisper

Transkription (Satzweise)

Man kann Whisper so konfigurieren, dass es Satz für Satz transkribiert, anstatt den gesamten Speicher zu belegen:

whisper lange-datei.mp3 --model medium --temperature 0 --best_of 1 --output_dir transkripte

Automatische Zeitsynchronisation

Man kann Whisper mit Time-Stamps laufen lassen:

whisper lange-datei.mp3 --model medium --temperature 0 --best_of 1 --output_format srt --output_dir transkripte
Categories
Development

React Projekt aufsetzen

Ausgangslage

Ubuntu 22.04.5 LTS mit node v20.17.0 und npm 10.8.2

React-Projekt einrichten

  1. Terminal öffnen und in das Verzeichnis navigieren, in dem das Verzeichnis für das Projekt erstellt werden soll.
  2. Erstelle ein neues Vite-Projekt mit React (Vite ist ein moderner Build-Tool, der schneller als Create React App ist):
npm create vite@latest my-react-app -- --template react
  1. Wechsle in das neue Projektverzeichnis und installiere die Abhängigkeiten:
cd my-react-app && npm install

4: Starte den Entwicklungsserver:

npm run dev

Jetzt kanns im Browser unter http://localhost:5173 die React-Anwendung aufgerufen werden.

Projektstruktur

my-react-app/
├── node_modules/
├── public/
├── src/
│   ├── assets/
│   ├── App.jsx
│   ├── App.css
│   ├── index.css
│   └── main.jsx
├── .gitignore
├── index.html
├── package.json
├── package-lock.json
└── vite.config.js

Wichtige Dateien:

  • src/App.jsx: Deine Hauptkomponente
  • src/main.jsx: Der Einstiegspunkt deiner Anwendung
  • index.html: Die HTML-Vorlage
  • package.json: Projektkonfiguration und Abhängigkeiten

Nützliche npm Befehle:

  • npm run dev: Startet den Entwicklungsserver
  • npm run build: Erstellt eine optimierte Produktionsversion
  • npm run preview: Zeigt die Produktionsversion lokal an

Wichtige Tools

Tailwind CSS

Tailwind CSS ist ein Utility-First-CSS-Framework, das eine Sammlung von vordefinierten CSS-Klassen bereitstellt, die man direkt in deinem HTML (oder JSX) verwenden kannst. Anstatt vordefinierte Komponenten (wie Buttons oder Karten) anzubieten, bietet Tailwind kleine, atomare Utility-Klassen, die man kombinieren kann, um benutzerdefinierte Designs zu erstellen.

Utility-First-Ansatz

Der Utility-First-Ansatz bedeutet, dass man Stile durch das Kombinieren von kleinen, spezifischen Klassen anwendest. Jede Klasse entspricht einer CSS-Eigenschaft.

Beispiel:

<button class="bg-blue-500 text-white font-bold py-2 px-4 rounded">
  Klick mich
</button>
  • bg-blue-500: Hintergrundfarbe (blau, 500er Stufe).
  • text-white: Textfarbe (weiß).
  • font-bold:Schriftstärke (fett).
  • py-2: Padding oben und unten (2 Einheiten).
  • px-4: Padding links und rechts (4 Einheiten).
  • rounded: Abgerundete Ecken.

Warum werden PostCSS und Autoprefixer mit Tailwind CSS verwendet?

Tailwind CSS ist ein Utility-First-Framework, das stark auf PostCSS angewiesen ist, um:

  • Utility-Klassen zu generieren: Tailwind verwendet PostCSS, um die Utility-Klassen (z. B. mt-4, text-center) in normales CSS umzuwandeln.
  • CSS zu optimieren: PostCSS entfernt ungenutzte CSS-Klassen (mit dem purge-Feature), um die Bundle-Größe zu reduzieren.
  • Browser-Präfixe hinzuzufügen: Autoprefixer stellt sicher, dass das generierte CSS in allen Browsern funktioniert.

PostCSS

PostCSS ist ein Tool zur Transformation von CSS mit JavaScript. Es fungiert als eine Art "CSS-Compiler" und ermöglicht es, CSS durch Plugins zu erweitern und zu optimieren.

Wofür braucht man PostCSS?

  • Tailwind CSS: Tailwind verwendet PostCSS, um seine Utility-Klassen in normales CSS umzuwandeln.
  • CSS-Modernisierung: PostCSS kann modernes CSS (z. B. CSS-Variablen, verschachtelte Regeln) in browserkompatibles CSS umwandeln.
  • Plugin-Ökosystem: Es gibt zahlreiche PostCSS-Plugins, die zusätzliche Funktionen bieten, z. B.:
    • Autoprefixer (für Browser-Präfixe)
    • CSS-Nano (für CSS-Minifizierung)
    • CSS-Modules (für lokale Scope-CSS)

Autoprefixer

Autoprefixer ist ein PostCSS-Plugin, das automatisch Browser-Präfixe (Vendor-Prefixes) zu deinem CSS hinzufügt. Dies stellt sicher, dass dein CSS in allen Browsern korrekt funktioniert.

Wofür braucht man Autoprefixer?

  • Browser-Kompatibilität: Einige CSS-Eigenschaften (z. B. flexbox, grid, transform) benötigen browser-spezifische Präfixe wie -webkit-, -moz-, -ms- usw.
  • Automatisierung: Autoprefixer fügt diese Präfixe automatisch hinzu, basierend auf den Browsern, die du unterstützen möchtest. Du musst dich nicht mehr manuell um Präfixe kümmern.
  • Zukunftssicherheit: Autoprefixer entfernt auch veraltete Präfixe, die nicht mehr benötigt werden.

Lucide

Lucide ist eine Sammlung von einfachen, eleganten und konsistenten Icons, die als Open Source verfügbar sind. Es ist der Nachfolger von Feather Icons und bietet eine große Auswahl an Icons für Web- und Mobile-Anwendungen. lucide-react ist das offizielle React-Paket, das die Lucide-Icons für die Verwendung in React-Projekten bereitstellt.

  • Icon-Sammlung: Lucide bietet über 1.000 Icons in einem einheitlichen Designstil.
  • Open Source: Die Icons sind kostenlos und unter der MIT-Lizenz verfügbar.
  • Einfach und minimalistisch: Die Icons sind schlicht und eignen sich gut für moderne Benutzeroberflächen.
  • Skalierbar: Die Icons sind als SVG verfügbar und können in jeder Größe verwendet werden.

Installation und Einrichtung

Installation und Einrichtung:

# Installation
npm install -D tailwindcss postcss autoprefixer lucide-react

# Tailwind und PostCSS Config erstellen
npx tailwindcss init -p

Die erzeugte tailwind.config.jsanpassen:

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

Tailwind in das Projekt einbinden: In der Datei src/App.css folgenden Inhalt hinzufügen:

@tailwind base;
@tailwind components;
@tailwind utilities;

Den Entwicklungsserver starten:

npm run dev
Categories
Development Linux

Apache HTTP in den Container

Aufgabe

Ich möchte die nativen Dienste auf meinem Server zur besseren Verwaltung und als Vorbereitung für eine kommende Migration auf Docker umstellen.
Als Vorbereitung für diese Aufgabe habe ich in Lokaler virtueller Server bereits ein grundlegendes Setup lokal evaluiert.
Heute möchte ich den Apache HTTP Server, der auch als Reverse Proxy dient, in einen Container stecken.

Vorbereitung

Docker deinstallieren

Auf dem Server ist bereits eine alte Docker Installation vorhanden. Diese habe ich als erstes rückstandslos entfernt.

Docker installieren

Hier nur kurz die Befehle, aus Lokaler virtueller Server übernommen:

sudo apt update
sudo apt upgrade -y

# Docker-Repository hinzufügen
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Docker installieren:
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo systemctl enable docker
sudo systemctl start docker

# Rechte für den aktuellen Benutzer konfigurieren
sudo usermod -aG docker $USER
newgrp docker

Ordnerstrukur

Die Dateien der Docker Dienste sollen in /data/docker/ liegen.
Ein symbolischer Link von /home/docker soll auf das Verzeichnis zeigen.

sudo mkdir -p /data/docker
sudo ln -s /data/docker /home/docker

sudo chown :docker /data/docker
sudo chmod g+w /data/docker

Apache HTTP Container

Ordnerstruktur

mkdir /data/docker/apache
mkdir /data/docker/apache/config \
    /data/docker/apache/html \
    /data/docker/apache/logs

Daten kopieren

sudo cp -r /etc/apache2/* /data/docker/apache/config
sudo cp -r /var/www/html/* /data/docker/apache/html
sudo cp -r /var/log/apache2 /data/docker/apache

sudo chown -R :docker /data/docker/apache
sudo chmod -R g+w /data/docker/apache

mv /data/docker/apache/apache2/* /data/docker/apache/logs
rm -rf /data/docker/apache/apache2

Docker Compose Datei

docker-compose.yml für Apache HTTP im Verzeichnis /data/docker/apache:

services:
  apache:
    image: httpd:2.4
    container_name: apache
    restart: always
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./config:/usr/local/apache2/conf
      - ./html:/usr/local/apache2/htdocs
      - ./logs:/usr/local/apache2/logs

erster Start

Auf dem Server Apache HTTP stoppen:

# Service finden
systemctl list-unit-files --type service
# Service stoppen
sudo systemctl stop apache2

Container-Apache starten:

cd /data/docker/apache
docker compose up

Ausgabe:

[+] Building 0.0s (0/0)
Attaching to apache
apache  | httpd: Could not open configuration file /usr/local/apache2/conf/httpd.conf: No such file or directory
apache exited with code 0

Das hat also schon mal so gar nicht geklappt. Woran kann es liegen? Zur Analyse interaktiv in dem Container agieren:

docker compose run -it --entrypoint /bin/bash apache

Ich kann im Container sehen, dass die Konfigurations-Dateien vorhanden sind, d.h. die Docker-Compose-Konfig ist an der Stelle korrekt.
Allerdings fehlt die geforderte httpd.conf.
Bei Ubuntu heißt die Datei apache2.conf, der Docker Container erwartet aber eine httpd.conf. Als Workaround lege ich eine httpd.confan, die auf die apache2.conf verweist:

Include /usr/local/apache2/conf/apache2.conf


Jetzt bekomme ich beim Starten des Containers andere Fehlermeldungen.

Aufräumen

Das entwickelt sich nicht wie gewünscht, ich breche ab und räume auf:

docker compose down -v
sudo rm -rf /data/docker/apache

Kleiner Apache

Um einen minimalen Teilerfolg feiern zu können, setzte ich einen Apache im Container auf, der die HTML-Seiten auf Port 9080 ausliefert.

mkdir /data/docker/apache
mkdir /data/docker/apache/logs

cd /data/docker/apache
vim docker-compose.yml

docker compose up -d
docker logs apache
services:
  apache:
    image: httpd:2.4
    container_name: apache
    restart: always
    ports:
      - 9080:80
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /var/www/html:/usr/local/apache2/htdocs
      - ./logs:/usr/local/apache2/logs

Fazit

Der naive "Lift and Shift" Ansatz hat mal wieder nicht funktioniert.
Die Pfade sind bei der nativen Ubuntu Installation und dem Container Apache unterschiedlich. Der simple Workaround mit der httpd.conf-Datei war ein erster Schritt, danach hätten noch Umgebungsvariablen wie APACHE_RUN_DIR gesetzt werden müssen.
Dann hätte ich noch einige Pfade vom Server in den Container mappen müssen.
Und dann ist da noch der Let's encrypt Certbot, der doch mehr mit der Apache Installation verdrahtet ist, als ich am Anfang dachte. Den hätte ich auch noch im Container installieren müssen.
Sicherlich alles machbar, aber für eine Interimslösung zu aufwändig. Am Ende soll ja Traefik SSL und Reverse Proxy übernehmen. Daher belasse ich es hier erstmal.

Categories
AI Development

LoRA von meinem Gesicht

Ich arbeite mich grade durch einen Kurs "ChatGPT, Machine learning, DeepLearning, Neuronale Netze, OpenAI API, GPTs, Assistant API, Gemini, LLMs u. deine Modelle" auf Udemy Business durch und einen Abschnitt wollte ich mir nicht nur anhören, sondern auch nach arbeiten.
Es geht um den Abschnitt "67. Der einfachste & schnellste Weg zu deinem Lora & Bilder mit deinem Gesicht" und das Video ist nur 10 Minuten lang.
Es hörte sich so einfach an: Ein paar Selfies aufnehmen, zurechtschneiden und ein Jupyter Notebook auf Google Colab ausführen. Fertig ist das LoRA des eigenen Gesichts.

Was ist ein LoRA?

LoRAs (Low-Rank Adaptations) sind kleinere Modelle, die man mit bestehenden Stable Diffusion Modellen kombinieren kannst. Sie erlauben es ein Bestehendes Modell «fine zu tunen». So kann man neue "Konzepte", wie mein Gesicht, den generierten Bildern hinzufügen.

Selfies machen

Zuerst also ein paar Selfies aufnehmen, dabei darauf achten, dass im Hintergrund nicht zu viele Gegenstände ablenken, ich wählte eine weiße Wand und ein paar nahm ich noch vor einem schwarzen Hintergrund auf.
Zum Trainieren an den Daten müssen die Bilder auf das Format 512x512 Pixel zurechtgeschnitten werden.
Da war auch schon das erste Problem: Die Bilder meines iPhones legen im HEIC-Format vor:

Was eine HEIC-Datei ist

Die Abkürzung ist auch als HEIF bekannt und steht für High Efficiency Image Format.

  • Es handelt sich dabei um ein Bild-Format zum platzsparenden Speichern von Bildern auf Mobil-Geräten.
  • iOS-Geräte speichern Bilder in HEIC-Dateien, auf Android-Geräten finden Sie in der Regel HEIF-Bilder.
  • Der Vorteil dieses Bild-Formats ist eine kleine Dateigröße bei JPEG-Qualität oder sogar noch besserer Qualität.

Das Problem mit HEIC-Dateien

Es ist ein propritäres Format und kann nicht ohne weiteres verwendet werden.
Ich verwende Windows 11 und konnte die Bilder betrachten, allerdings nicht bearbeiten. Vermutlich hätte ich eine Erweiterung im MS Store kaufen müssen, dann hätte ich es mit Programmen wie IrfanView bearbeiten können.
Gelöst habe ich es statt dessen mit einem Python-Programm, dass die Bilder von HEIC nach JPEG umwandelt und auf 512x512 zurecht schneidet.

LoRA trainieren

Das Training des LoRAs soll in einem Jupyther Notebook auf Google Colab erfolgen. Link
Leider konnte ich es nicht ausführen, der Python Code funktioniert nicht. Kleinere Anpassungen halfen nicht weiter, daher kopierte ich das Jupyther Notebook und die erforderlichen Scripte in ein eigenes GitHub Repository und passte so lange Notebook und Scripte an, bis sie liefen.
Das Stable Diffusion DreamBooth Notebook kann unter diesem Link in Google Colab geöffnet werden.

Categories
Database Development

MongoDB

Was ist MongoDB?

MongoDB ist eine dokumentenbasierte NoSQL-Datenbank, die JSON-ähnliche Datenstrukturen (BSON) verwendet. Sie wurde entwickelt, um eine hohe Flexibilität und Skalierbarkeit zu bieten, und ist besonders geeignet für Anwendungen mit dynamischen oder unstrukturierten Daten.

Vorteile von MongoDB

  • Schemaflexibilität: Dokumente in einer Collection können unterschiedliche Felder und Strukturen aufweisen.
  • Hohe Skalierbarkeit: Unterstützt horizontales Sharding zur Verteilung von Daten über mehrere Server.
  • JSON-ähnliches Format: BSON erleichtert die Integration mit modernen Programmiersprachen.
  • Eingebaute Replikation: Daten werden automatisch über Replikatsets gesichert.
  • Leistungsfähige Abfragen: Unterstützung für Indexe, Aggregationen und komplexe Suchanfragen.
  • Open Source: Große Community und kostenlose Nutzung (mit kostenpflichtigen Enterprise-Optionen).

Typische Einsatzgebiete

  • Web- und Mobilanwendungen: Speicherung von Benutzerdaten, Sitzungsinformationen oder dynamischen Inhalten.
  • IoT: Speicherung und Verarbeitung von Sensordaten.
  • Content-Management-Systeme (CMS): Flexible Datenmodelle für Inhalte und Metadaten.
  • Echtzeit-Analysen: Verarbeitung von Ereignisdaten für Dashboards oder Monitoring.
  • Geodaten-Anwendungen: Speicherung und Abfragen von Standortdaten.

Mit MongoDB lassen sich schnell und effizient Anwendungen entwickeln, die mit dynamischen Datenstrukturen und wachsendem Datenvolumen umgehen können.

Installation von MongoDB mit Docker

Schritte zur Installation von MongoDB mit Docker

  1. MongoDB-Image herunterladen:

    docker pull mongo:latest

    Dies lädt das neueste MongoDB-Docker-Image aus dem offiziellen Docker Hub.

  2. MongoDB-Container starten:

    docker run -d \
     --name mongodb \
     -p 27017:27017 \
     -v mongodb_data:/data/db \
     mongo:latest
    • -d: Startet den Container im Hintergrund.
    • --name mongodb: Gibt dem Container den Namen mongodb.
    • -p 27017:27017: Bindet den MongoDB-Port (Standard: 27017) an den Host.
    • -v mongodb_data:/data/db: Erstellt ein Docker-Volume für die persistente Speicherung der Daten unter /data/db.
  3. Überprüfung des Containers:

    docker ps

    Dies zeigt eine Liste der laufenden Container. Der mongodb-Container sollte in der Liste erscheinen.

  4. Logs überprüfen (optional): Um sicherzustellen, dass der Container ordnungsgemäß läuft, kannst du die Logs abrufen:

    docker logs mongodb
  5. Container stoppen und entfernen (bei Bedarf):

    • Stoppen:
      docker stop mongodb
    • Entfernen:
      docker rm mongodb

Hinweis

Die Konfiguration verwendet keine Authentifizierung. Für produktive Umgebungen sollte ein Benutzer mit Passwort eingerichtet werden, und der Zugriff auf die Datenbank sollte über eine Firewall geschützt sein.

Clients

1. MongoDB Compass

  • Beschreibung: Das offizielle GUI-Tool von MongoDB.
  • Vorteile:
    • Einfache Installation und Nutzung.
    • Intuitive Benutzeroberfläche für Abfragen und Datenvisualisierung.
    • Unterstützt erweiterte Funktionen wie Aggregations-Pipelines.
  • Nachteile:
    • Kann ressourcenintensiv sein.
    • Nicht optimal für sehr große Datenmengen.
  • Link: MongoDB Compass herunterladen

2. DBeaver (Community Edition)

  • Beschreibung: Open-Source-Datenbank-Tool mit Unterstützung für viele Datenbanken.
  • Vorteile:
    • Multi-Datenbank-Support in einer einzigen Anwendung.
    • Solide SQL-Editor-Funktionen.
  • Nachteile:
    • Die Community Edition unterstützt MongoDB nicht direkt (Pro-Version erforderlich).
    • Keine native JSON-Visualisierung.
  • Link: DBeaver herunterladen

3. MongoSH

 docker exec -it mongodb mongosh

MongoDB in der Cloud

kostenlose MongoDB im Atlas-Clusters

  1. Registrierung bei MongoDB Atlas:

    • Besuche die MongoDB Atlas Website.
    • Erstelle ein kostenloses Konto oder melde dich mit deinem bestehenden Konto an.
  2. Erstellen eines neuen Clusters:

    • Klicke auf "Create a Cluster".
    • Wähle die Option Shared Cluster (kostenlos) und klicke auf "Create".
    • Konfiguriere dein Cluster:
      • Cloud-Provider: AWS, Google Cloud oder Azure (Standard ist AWS).
      • Region: Wähle die nächstgelegene Region, um Latenzzeiten zu minimieren.
      • Cluster Name: Gib einen Namen für deinen Cluster ein oder verwende den Standardnamen.
    • Klicke auf "Create Cluster". Der Vorgang kann ein paar Minuten dauern.
  3. Einrichten von Zugriff und Netzwerk:

    • Gehe nach der Cluster-Erstellung zu Network Access:
      • Klicke auf "Add IP Address".
      • Wähle "Allow Access from Anywhere" (für Testzwecke) oder gib eine spezifische IP-Adresse an.
    • Gehe zu Database Access:
      • Erstelle einen neuen Benutzer, indem du auf "Add New Database User" klickst.
      • Gib einen Benutzernamen und ein Passwort ein.
      • Setze die Rolle auf "Read and Write to Any Database" (für Testumgebungen).
  4. Verbindung herstellen:

    • Gehe zu Connect im Cluster-Dashboard.
    • Wähle "Connect Your Application".
    • Kopiere die bereitgestellte Verbindung-URI, z. B.:
      mongodb+srv://<username>:<password>@<cluster-name>.mongodb.net/<database-name>?retryWrites=true&w=majority
    • Ersetze <username> und <password> mit den von dir erstellten Anmeldedaten.
  5. Testen der Verbindung (Beispiel mit Python):

    • Installiere die offizielle Python-Bibliothek pymongo:
      pip install pymongo
    • Beispielcode:

      from pymongo import MongoClient
      
      # Verbindung zu Atlas-Cluster herstellen
      uri = "mongodb+srv://<username>:<password>@<cluster-name>.mongodb.net/?retryWrites=true&w=majority"
      client = MongoClient(uri)
      
      # Test der Verbindung
      try:
       print("MongoDB-Version:", client.server_info()["version"])
      except Exception as e:
       print("Verbindung fehlgeschlagen:", e)
  6. Cluster-Verwaltung:

    • Über das Atlas-Dashboard kannst du:
      • Datenbanken erstellen und Collections verwalten.
      • Aggregationen und Abfragen durchführen.
      • Performance überwachen (beschränkt in der Free-Tier-Version).

Vorteile von Atlas-Free-Tier:

  • Kostenlos: Bis zu 512 MB Speicherplatz.
  • Hohe Verfügbarkeit: Automatische Replikation auf mehrere Nodes.
  • Einfache Verwaltung: Intuitive Oberfläche zur Verwaltung von Clustern und Benutzern.
  • Schnelle Skalierbarkeit: Möglichkeit, bei Bedarf auf kostenpflichtige Pläne zu upgraden.

Einschränkungen:

  • Speicherplatz und Performance sind begrenzt.
  • Eingeschränkte Konfigurationsmöglichkeiten (kein vollständiger Root-Zugriff).
  • Free-Tier-Datenbanken können nach 30 Minuten Inaktivität schlafen gehen.

Lokale DB in die Cloud kopieren

  1. Dump erstellen

    docker exec -it mongodb mongodump
  2. Dump in die Cloud kopieren

    docker exec -it mongodb mongorestore --uri="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/"
  3. Index erstellen Mit Cloud DB verbinden:

    docker exec -it mongodb mongosh "mongodb+srv://<cluster>.mongodb.net/" --apiVersion 1 --username <username>

    Index erstellen:

    use <database>
    db.fullsite.createIndex(
    { cleaned_text: "text" },  // Das Feld, das durchsucht werden soll
    { default_language: "german" }  // Sprache für den Textindex
    );

Beispiel

Ich möchte die Datenbank aus dem letzten Artikel Webseitendaten Assistent KI in die Cloud bringen. Die Datenmenge ist allerdings zu groß, so dass ich zuerste die Rohdaten löschen muss. Anschließend kann ich den Dump erstellen, in die Cloud hoch laden und abschließend den Index erstellen.

Datenmenge verkleinern

docker exec -it CompanyDataAI-mongo mongosh
use firmendaten

show dbs
db.stats()

db.fullsite.updateMany(
  {},
  { $unset: { fieldName: "raw_html" } }
)

show dbs
db.stats()

Dump erstellen

docker exec -it CompanyDataAI-mongo mongodump --db firmendaten

Datenbank importieren

docker exec -it CompanyDataAI-mongo mongorestore --db firmendaten --uri="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/"

Problem

Laut show dbs ist die Datenbank 335 MB groß, der Dump ist allerdings 1,3 GB groß. Die Erklärung ist vermutlich, dass die Daten in der Datenbank komprimiert sind, im Dump hingegen nicht. Die freie Cloud Datenbank hat aber nur einen Speicher von 512 MB. Als Lösungsansatz versuchen wir einen komprimierten Dump (190 MB):

docker exec -it CompanyDataAI-mongo mongodump --db firmendaten --gzip
docker exec -it CompanyDataAI-mongo mongorestore --gzip --db firmendaten --uri="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/" /dump/firmendaten

Leider wird auch dadurch das Problem nicht gelöst:

Failed: firmendaten.fullsite: error restoring from /dump/firmendaten/fullsite.bson.gz: (AtlasError) you are over your space quota, using 526 MB of 512 MB

Daher habe ich den nicht komplett importierten Dump gelöscht und dann das Scraping-Tool mit der Cloud-DB verbunden und neu durchlaufen lassen. Das hat dann auch funktioniert, allerdings war das setzen des Index dann zu groß. Vielleicht hätte der Dump/Restore funktioniert, wenn ich vorher den Index in der lokalen DB gelöscht hätte?

Index setzen

docker exec -it mongodb mongosh "mongodb+srv://<cluster>.mongodb.net/" --apiVersion 1 --username <username>

Index erstellen:

use firmendaten
db.fullsite.createIndex(
  { cleaned_text: "text" },  // Das Feld, das durchsucht werden soll
  { default_language: "german" }  // Sprache für den Textindex
);

Leider bricht der Vorgang ab: you are over your space quota.

Fazit

Die Datenbankgröße von 512MB im Free Tier ist nicht ausreichend. Denn leider ist damit nicht der Speicher auf dem Filesystem gemeint (storageSize), den man mit show dbs sehen kann, sondern die Größe der Daten (dataSize) die man mit db.stats() sehen kann. Damit ist die Datenbank leider nicht für mein Projekt zu gebrauchen.

Workaround

Um mit meinem PoC weiter zu kommen, lösche ich irgendwelche Daten und überlege mir später eine andere Lösung:

docker exec -it mongodb mongosh "mongodb+srv://<cluster>.mongodb.net/" --apiVersion 1 --username <username>
use firmendaten;

# Index löschen
db.fullsite.dropIndexes();

# Dokumente ohne Inhalt löschen
db.fullsite.deleteMany({
    $or: [
        { cleaned_text: { $exists: false } }, // Optional
        { cleaned_text: "" },
        { cleaned_text: null }
    ]
});

# Die letzten 50 Dokument löschen
db.fullsite.find()
    .sort({ _id: -1 })
    .limit(50)
    .forEach(doc => db.fullsite.deleteOne({ _id: doc._id }));

# Rohdaten löschen
db.fullsite.updateMany({}, { $unset: { fieldName: "raw_html" } });

# Index setzen
db.fullsite.createIndex(
  { cleaned_text: "text" },  // Das Feld, das durchsucht werden soll
  { default_language: "german" }  // Sprache für den Textindex
);
Categories
AI Database Development

Webseitendaten Assistent KI

GitHub

Das Projekt ist in GitHub gespeichert.

Ausgangssituation

Ich möchte einen virtuellen Assistenten erstellen, der auf Informationen von mehreren Webseiten basiert. Ziel ist es, aus den Daten relevante Informationen bereitzustellen und auf Fragen der Benutzer zu antworten.

Da ich keine klaren Informationen über die Struktur der Webseiteninhalte hatte, wollte ich zunächst alle Seiten vollständig speichern und später bereinigen. Die Lösung soll dynamisch erweiterbar sein und folgende Schwerpunkte abdecken:

  1. Web Scraping: Automatisches Sammeln von Rohdaten von verschiedenen Webseiten.
  2. Speicherung: Daten in MongoDB speichern, sowohl Rohdaten als auch bereinigte Daten.
  3. Durchsuchbarkeit: Daten mit einem Full-Text-Index durchsuchbar machen.
  4. KI-Integration: Eine lokale KI-Instanz (Teuken-7B von OpenGPT-X) verwenden, die mit allen 24 europäischen Amtssprachen trainiert wurde, um Benutzerfragen in natürlicher Sprache zu beantworten.
  5. Benutzeroberfläche: Ein Web-Interface für eine einfache und intuitive Nutzung der Lösung.

Lösungsansatz

  1. Web Scraping mit Scrapy:

    • Automatisches Sammeln von HTML-Rohdaten von mehreren Webseiten.
    • Dynamisches Einlesen von Start-URLs.
    • Bereinigung der Daten während des Scrapings (HTML-Tags entfernen, Boilerplate entfernen, Texte kürzen).
  2. Datenhaltung mit MongoDB:

    • Rohdaten und bereinigte Texte wurden parallel gespeichert, um flexibel zu bleiben.
    • Full-Text-Index mit deutscher Spracheinstellung eingerichtet, um die bereinigten Texte effizient zu durchsuchen.
  3. KI-Integration mit Teuken-7B:

    • Übergabe der MongoDB-Ergebnisse als Kontext an das Sprachmodell Teuken-7B.
    • Das Modell generiert eine präzise Antwort auf die Benutzerfrage, basierend auf den bereitgestellten Daten.
  4. Web-App mit Flask:

    • Einfache Benutzeroberfläche, um Fragen zu stellen und KI-Antworten anzuzeigen.
    • Verbindung von Flask mit MongoDB und der KI für dynamische Abfragen.

Architektur

1. Datensammlung

  • Tool: Scrapy.
  • Datenquellen: Liste von Start-URLs (mehrere Domains).
  • Prozess:
    1. Besuch der Startseiten.
    2. Rekursive Erfassung aller Links innerhalb der erlaubten Domains.
    3. Speicherung der Rohdaten (HTML) und bereinigten Daten (Text).

2. Datenhaltung

  • Datenbank: MongoDB.
  • Struktur:
    {
    "url": "https://www.example.com/about",
    "raw_html": "<html>...</html>",
    "cleaned_text": "This is an example text.",
    "timestamp": "2024-11-26T12:00:00Z"
    }
  • Full-Text-Index:
    • Feld: cleaned_text.
    • Sprache: Deutsch.

3. Datenanalyse

  • Abfragen:
    • MongoDB-Textsuche mit Unterstützung für Wortstämme (z. B. „Dienstleistung“ und „Dienstleistungen“).
    • Priorisierung der Ergebnisse nach Relevanz (score).

4. KI-Integration

  • KI-Tool: Teuken-7B (OpenGPT-X).
  • Prozess:
    1. Übergabe der MongoDB-Ergebnisse als Kontext an die KI.
    2. Generierung einer präzisen Antwort basierend auf der Benutzerfrage.

5. Benutzeroberfläche

  • Framework: Flask.
  • Funktionen:
    • Eingabeformular für Benutzerfragen.
    • Anzeige der KI-Antwort und der relevanten Daten.
    • Einfache und intuitive Navigation.

Implementierung

1. Überblick über die Implementierungsschritte

Wir setzen die zuvor beschriebenen Schritte um:

  1. Web Scraping mit Scrapy: Erfassen von Daten von mehreren Webseiten.
  2. Datenhaltung mit MongoDB: Speicherung der Roh- und bereinigten Daten.
  3. Full-Text-Index: Einrichten eines deutschen Index in MongoDB.
  4. KI-Integration mit Teuken-7B: Verarbeitung von Benutzerfragen mit einer lokalen Instanz.
  5. Benutzeroberfläche mit Flask: Web-Interface zur Interaktion mit dem virtuellen Assistenten.

2. Web Scraping: FullSiteSpider

Erstelle einen Scrapy-Spider (spiders/fullsite_spider.py), der mehrere Domains und Seiten crawlt.

import scrapy
from bs4 import BeautifulSoup

class FullSiteSpider(scrapy.Spider):
    name = "fullsite"

    # Liste der erlaubten Domains und Start-URLs
    allowed_domains = ["example.com", "example2.com", "example3.org"]
    start_urls = [
        "https://www.example.com",
        "https://www.example2.com",
        "https://www.example3.org/start"
    ]

    def parse(self, response):
        # Rohdaten speichern
        raw_html = response.body.decode('utf-8')

        # Bereinigung der HTML-Daten
        cleaned_text = self.clean_html(raw_html)

        # Speichern der Daten
        yield {
            'url': response.url,
            'raw_html': raw_html,
            'cleaned_text': cleaned_text,
            'timestamp': response.headers.get('Date', '').decode('utf-8'),
        }

        # Folge allen Links auf der Seite
        for link in response.css('a::attr(href)').getall():
            if link.startswith('http') or link.startswith('/'):
                yield response.follow(link, self.parse)

    def clean_html(self, html_content):
        """Bereinigt HTML und extrahiert lesbaren Text."""
        soup = BeautifulSoup(html_content, 'html.parser')
        text = soup.get_text(separator=" ").strip()
        return " ".join(text.split())

3. Datenhaltung: MongoDB Pipeline

Speichere die gescrapten Daten direkt in MongoDB.

import pymongo
import json

class MongoPipeline:
    def __init__(self):
        # Konfiguration aus Datei laden
        with open('config.json') as config_file:
            config = json.load(config_file)
            self.mongo_uri = config['MONGO_URI']
            self.mongo_db = config['MONGO_DATABASE']

    def open_spider(self, spider):
        # Verbindung zur MongoDB herstellen
        self.client = pymongo.MongoClient(self.mongo_uri)
        self.db = self.client[self.mongo_db]

    def close_spider(self, spider):
        # Verbindung schließen
        self.client.close()

    def process_item(self, item, spider):
        # Daten in MongoDB speichern
        collection = self.db[spider.name]
        collection.insert_one({
            'url': item['url'],
            'raw_html': item['raw_html'],
            'cleaned_text': item['cleaned_text'],
            'timestamp': item['timestamp'],
        })
        return item

Konfiguration (config.json):

{
  "MONGO_URI": "mongodb://localhost:27017",
  "MONGO_DATABASE": "firmendaten"
}

Aktiviere die Pipeline in settings.py:

ITEM_PIPELINES = {
    'firmendaten.pipelines.MongoPipeline': 300,
}

4. Full-Text-Index in MongoDB

Richte den deutschen Full-Text-Index ein:

use firmendaten;
db.fullsite.createIndex(
  { cleaned_text: "text" },
  { default_language: "german" }
);

5. KI-Integration mit Teuken-7B

Implementiere die Integration in ki_helper.py:

from openai import OpenAI

# Verbindung zur lokalen KI
local_ai = OpenAI(base_url="http://127.0.0.1:1234/v1", api_key="lm-studio")

def generate_response(question, results):
    """
    Generiert eine Antwort mit der lokalen KI basierend auf den MongoDB-Ergebnissen.
    """
    # Kontext aus den MongoDB-Ergebnissen erstellen
    context = "\n".join(
        [f"URL: {doc['url']}\nText: {doc['cleaned_text']}" for doc in results]
    )

    # Nachrichtenformat für die KI
    messages = [
        {"role": "system", "content": "Du bist ein virtueller Assistent für Firmendaten."},
        {"role": "user", "content": f"Hier sind die Daten:\n{context}\n\nFrage: {question}"}
    ]

    # Anfrage an die lokale KI
    response = local_ai.chat.completions.create(
        model="teuken-7b",
        messages=messages,
        temperature=0.7
    )

    return response.choices[0].message.content.strip()

6. Benutzeroberfläche mit Flask

Erstelle die Flask-App (app.py):

from flask import Flask, render_template, request
from pymongo import MongoClient
from ki_helper import generate_response

# Flask-App initialisieren
app = Flask(__name__)

# Verbindung zur MongoDB
client = MongoClient("mongodb://localhost:27017/")
db = client["firmendaten"]
collection = db["fullsite"]

def search_mongodb(question):
    """
    Führt eine Volltextsuche in MongoDB aus und gibt relevante Ergebnisse zurück.
    """
    results = collection.find(
        {"$text": {"$search": question}},
        {"score": {"$meta": "textScore"}}
    ).sort("score", {"$meta": "textScore"}).limit(3)
    return list(results)

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        question = request.form['question']
        results = search_mongodb(question)

        if not results:
            return render_template('result.html', question=question, response="Keine relevanten Daten gefunden.")

        response = generate_response(question, results)
        return render_template('result.html', question=question, response=response)

    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True)

HTML-Templates: Siehe GitHub im Ordner webapp.


7. Ausführung und Tests

  1. Scrapy starten:

    scrapy crawl fullsite
  2. Flask-App starten:

    python app.py
  3. App im Browser öffnen:

    • URL: http://127.0.0.1:5000

Categories
Database Development Linux

Performance-Analyse

Performance-Analyse einer PostgreSQL-Datenbank mit pg_stat_statements

Bei einer Webanwendung mit einer PostgreSQL-Datenbank stießen wir auf Performance-Probleme. Hier beschreibe ich, wie man Performance-Probleme in PostgreSQL mittels der Erweiterung pg_stat_statements analysieren kann. Da die Erweiterung nicht auf der Produktivdatenbank installiert ist, zeige ich, wie man eine lokale Testumgebung aufsetzt.

Überblick der Vorgehensweise

  1. Backup der Produktivdatenbank erstellen
  2. Lokale Testdatenbank mit Docker aufsetzen
  3. pg_stat_statements installieren und konfigurieren
  4. Performance-Analyse durchführen

Vorbereitung

Zuerst prüfen wir die Version der Datenbank, um die passende Docker-Umgebung zu wählen:

SELECT version();
-- PostgreSQL 13.4


Backup erstellen

Das Backup erfolgt mittels Docker, um eine konsistente Umgebung zu gewährleisten. Hier das Script backup.sh:

targetfile=backup_`date +%Y-%m-%d`.sql

docker run --rm \
  --network=host \
  -e PGPASSWORD=PASSWORD \
  postgres:13.4-bullseye \
  pg_dump -h localhost -U myuser myschema > $targetfile

gzip $targetfile

Die Backup-Datei wird auf den lokalen Rechner übertragen.

Lokale Testdatenbank aufsetzen

Zuerst entfernen wir eine eventuell vorhandene alte Testinstanz und starten dann einen neuen Container:

docker stop mydb-performancetest && docker rm mydb-performancetest

docker run -d \
  --name mydb-performancetest \
  -e POSTGRES_USER=myuser \
  -e POSTGRES_PASSWORD=PASSWORD \
  -e POSTGRES_DB=myuser \
  -p 6432:5432 \
  postgres:13.4-bullseye

Verwendet wird Port 6432, um Konflikte mit einer möglicherweise laufenden lokalen PostgreSQL-Instanz zu vermeiden.

Daten importieren

Das Backup wird dann in die lokale Datenbank importiert, über das Script import.sh:

#!/bin/bash

# Check if arguments were passed
if [ $# -eq 0 ]; then
        echo "Error: No arguments provided"
            echo "Usage: $0 "
            exit 1
fi

# Check if file exists
if [ ! -f "$1" ]; then
      echo "Error: $1 is not a valid file."
        exit 1
fi
echo "File found: $1"

importfile=$(readlink -f "$1")
server=localhost
port=6432
username=myuser
password=PASSWORD
databasename=myuser

echo psql -h $server -U $username -d $databasename -f $importfile

docker run --rm \
  --network=host \
  -v $importfile:/script.sql \
  -e PGPASSWORD=$password \
  postgres:13.4-bullseye \
  psql -h $server -p $port -U $username -d $databasename -f script.sql

pg_stat_statements installieren

Um pg_stat_statements zu aktivieren, müssen wir die PostgreSQL-Konfiguration anpassen. Dazu bearbeiten wir die postgresql.conf. Da in dem Postgres Container kein Editor enthalten ist, ex- und importieren wir die Datei, um sie bearbeiten zu können:

# Export & bearbeiten:
docker cp mydb-performancetest:/var/lib/postgresql/data/postgresql.conf .
## shared_preload_libraries = 'pg_stat_statements' in einem Editor hinzufügen

# Re-Import & Neustart:
docker cp postgresql.conf mydb-performancetest:/var/lib/postgresql/data/postgresql.conf
docker restart mydb-performancetest

Danach aktivieren wir die Erweiterung in der Datenbank:

CREATE EXTENSION pg_stat_statements;

Performance-Analyse

Nach dem Ausführen der kritischen Anwendungsfälle können wir die problematischen Queries identifizieren:

SELECT
    query,
    calls,
    total_exec_time,
    min_exec_time,
    max_exec_time,
    mean_exec_time,
    rows,
    (total_exec_time / calls) AS avg_time_per_call
FROM
    pg_stat_statements
ORDER BY
    total_exec_time DESC
LIMIT 10;

Beispiel-Ergebnisse

Hier ein anonymisiertes Beispiel der Ausgabe:

query                              | calls | total_exec_time | min_exec_time | max_exec_time | mean_exec_time | rows  | avg_time_per_call
----------------------------------+-------+----------------+---------------+---------------+----------------+-------+-------------------
SELECT * FROM large_table WHERE... | 1500  | 350000.23      | 150.32       | 890.45        | 233.33         | 15000 | 233.33
UPDATE complex_table SET...       | 500   | 180000.45      | 250.12       | 750.89        | 360.00         | 500   | 360.00

Die Ergebnisse zeigen:

  • Anzahl der Aufrufe (calls)
  • Gesamtausführungszeit in ms (total_exec_time)
  • Minimale und maximale Ausführungszeit (min/max_exec_time)
  • Durchschnittliche Ausführungszeit (mean_exec_time)
  • Anzahl der betroffenen Zeilen (rows)

Besonders interessant sind Queries mit:

  • Hoher Gesamtausführungszeit
  • Hoher durchschnittlicher Ausführungszeit
  • Großer Differenz zwischen minimaler und maximaler Ausführungszeit
  • Unerwartet hoher Anzahl von Aufrufen

Reset

SELECT pg_stat_statements_reset();

Fazit

Mit dieser Methode können wir Performance-Probleme systematisch analysieren, ohne die Produktivumgebung zu beeinflussen.

Die Ergebnisse aus pg_stat_statements geben uns wichtige Hinweise, welche Queries optimiert werden sollten.