KI-basiertes Debugging für PostgreSQL auf Databricks: Ein Detail Leitfaden

Zu diesem Artikel gibt es ein kurzes Video:TikTok

1. Übersicht

Wenn eine Anwendung, die sich mit PostgreSQL auf Databricks verbindet (über Lakebase oder Lakehouse Federation), auf Fehler stößt, kostet das manuelle Lesen von Stack Traces, HikariCP-Protokollen und EXPLAIN ANALYZE sehr viel Zeit. Databricks verfügt über einen integrierten AI-Assistenten, der Protokolle, SQL-Abfragen und Connection-Pool-Konfigurationen lesen kann, um Ursachen zu ermitteln und Lösungen direkt vorzuschlagen.

Dieser Artikel erklärt, wie der KI-Datenbankdebugmechanismus funktioniert, und geht dann auf drei häufige PostgreSQL-Fehlerszenarien ein (Connection-Pool-Erschöpfung, N+1-Queries, fehlende Berechtigungen) mit Spring-Boot-Code, damit Sie diese nachvollziehen und die KI-Vorschläge selbst überprüfen können. Nach dem Lesen werden Sie wissen, wie Sie Protokolle und Abfragen vorbereiten, damit die KI (oder Sie selbst) schneller und genauer Diagnosen stellen können.

2. Databricks Postgres (Lakebase) – Was ist das?

Lakebase ist ein verwaltetes PostgreSQL, das direkt im Databricks-Workspace läuft und für OLTP-Workloads (transaktionale) neben Delta Lake für OLAP verwendet wird. Backend-Anwendungen verbinden sich mit Lakebase wie mit einem normalen PostgreSQL – über JDBC, mit Host/Port/Datenbank, die Databricks bereitstellt.

2.1. Verbindung von Spring Boot

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
spring.datasource.url=jdbc:postgresql://<workspace>.lakebase.databricks.com:5432/orders_db
spring.datasource.username=app_user
spring.datasource.password=${DB_PASSWORD}
spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.connection-timeout=3000

Dies ist die Mindestkonfiguration: maximum-pool-size und connection-timeout sind zwei Parameter, die immer wieder in den Debug-Fällen in Abschnitt 4 auftauchen, da die KI normalerweise damit beginnt, diese zu analysieren.

3. Wie funktioniert das KI-Datenbankdebugging in Databricks?

3.1. Datenquellen, die die KI liest

Der Databricks-Assistent „rät” nicht willkürlich – er liest aus drei Quellen:

  • Stack Trace/Exception-Log von Job oder Notebook.
  • Query-Plan (EXPLAIN ANALYZE), wenn Sie nach langsamen Abfragen fragen.
  • Schema und Metadaten (Tabellen, Indizes, Constraints), um die Gültigkeit von SQL zu überprüfen.

Mit anderen Worten: Die KI ist nur so gut wie Ihre Protokolle detailliert sind. Eine Exception, die nur "Connection is not available" ausgibt, führt zu viel vageren Vorschlägen als ein Protokoll, das die aktuelle Pool-Größe und die Anzahl aktiver Verbindungen enthält.

3.2. Typischer Debug-Ablauf

1. Sie fügen den Stack Trace oder die fehlerhafte SQL-Anweisung in den Databricks-Assistenten ein.
2. Die KI ermittelt die Art des Fehlers: Connection Pool, Syntax, Berechtigung, Lock, Timeout ...
3. Die KI gleicht diese mit dem Schema/Metadaten im aktuellen Workspace ab.
4. Die KI schlägt Fixes vor: Konfiguration ändern, SQL reparieren, Index hinzufügen, GRANT ausführen ...
5. Sie wenden den Fix an, führen den Code erneut aus, und die KI bestätigt, dass die Protokolle sauber sind.

Der nächste Abschnitt simuliert genau diesen Ablauf anhand von drei konkreten Fällen.

4. Praxis: Fehler reproduzieren und Debug-Vorschläge lesen

4.1. Datenvorbereitung

CREATE TABLE customers (
    id BIGSERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    customer_id BIGINT REFERENCES customers(id),
    total NUMERIC(12,2) NOT NULL,
    created_at TIMESTAMP DEFAULT now()
);

Und die entsprechende Entity:

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "customer_id")
    private Customer customer;

    private BigDecimal total;
}

4.2. Fall 1 – Connection Pool erschöpft

Der folgende Service hat einen Connection-Leak-Fehler, da er EntityManager manuell öffnet, aber nicht schließt:

@Service
public class OrderReportService {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Order> findAllOrdersUnsafe() {
        EntityManager em = entityManager.getEntityManagerFactory()
                .createEntityManager(); // wird nie geschlossen
        return em.createQuery("SELECT o FROM Order o", Order.class)
                .getResultList();
    }
}

Wenn diese API kontinuierlich aufgerufen wird, erzeugt sie Protokolleinträge wie:

java.sql.SQLTransientConnectionException: HikariPool-1 - Connection is not available,
request timed out after 3000ms.

Wenn Sie diesen Stack Trace zusammen mit der application.properties-Datei an die KI übergeben, sind die typischen Vorschläge:

- Pool-Größe (10) ist nicht die Hauptursache; das Problem ist, dass Verbindungen nicht an den Pool zurückgegeben werden.
- Überprüfen Sie alle Stellen, an denen Sie `createEntityManager()`/`getConnection()` manuell aufrufen.
- Stellen Sie sicher, dass Sie try-with-resources verwenden oder Spring die Transaktionen verwaltet.

Die richtige Lösung ist, das manuelle Erstellen von EntityManager zu vermeiden und stattdessen ein Standard-Repository zu verwenden:

public interface OrderRepository extends JpaRepository<Order, Long> {
    List<Order> findAll();
}
@Service
public class OrderReportService {

    private final OrderRepository orderRepository;

    public OrderReportService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public List<Order> findAllOrdersSafe() {
        return orderRepository.findAll(); // Spring verwaltet die Verbindung automatisch
    }
}

Hinweis: Die Erhöhung von maximum-pool-size bei diesem Fehler ist die falsche Richtung – es verzögert das Problem nur, anstatt das eigentliche Connection Leak zu beheben.

4.3. Fall 2 – N+1-Abfragen verlangsamen das System

public List<OrderSummary> summarize(List<Order> orders) {
    return orders.stream()
            .map(o -> new OrderSummary(o.getId(), o.getCustomer().getName())) // lazy load
            .toList();
}

Da customer mit FetchType.LAZY konfiguriert ist, erzeugt jedes Element eine separate Abfrage an customers. Wenn Sie EXPLAIN ANALYZE auf den Databricks-Protokollen ausführen, sehen Sie Hunderte von Vorkommen von:

SELECT * FROM customers WHERE id = $1;

Die KI weist normalerweise auf Folgendes hin, wenn sie diesen Query-Plan liest:

- 200 Abfragen wiederholen das gleiche Muster `SELECT ... WHERE id = $1` in einer Anfrage.
- Verdacht auf N+1-Problem durch Lazy Loading ohne JOIN FETCH.
- Vorschlag: Verwenden Sie `JOIN FETCH` oder `@EntityGraph`, um dies in 1 Abfrage zusammenzufassen.

Die Lösung mit JPQL JOIN FETCH:

@Query("SELECT o FROM Order o JOIN FETCH o.customer")
List<Order> findAllWithCustomer();

Ergebnis: Es gibt jetzt nur noch eine SELECT ... JOIN customers ...-Anweisung statt N+1 separate Abfragen.

4.4. Fall 3 – Fehlende Berechtigung (permission denied)

SELECT * FROM orders WHERE total > 1000;
ERROR: permission denied for table orders

Die KI gleicht die Rollen-Metadaten in Databricks ab und gibt einen entsprechenden GRANT-Vorschlag zurück:

GRANT SELECT ON orders TO app_user;

Hinweis: Überprüfen Sie GRANT-Vorschläge immer, bevor Sie sie in der Produktionsumgebung ausführen – die KI kennt Ihre interne Sicherheitsrichtlinie nicht, weiß aber, welche Syntax erforderlich ist, damit die Abfrage ausgeführt wird. Erteilen Sie keine weiteren Berechtigungen als das erforderliche Minimum (Prinzip der geringsten Berechtigung).

5. Tests schreiben, um die Behebung zu bestätigen

Nach dem Beheben des N+1-Problems in Fall 4.3 müssen Sie testen, um Regressions auszuschließen:

@DataJpaTest
class OrderRepositoryTest {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private TestEntityManager entityManager;

    @Test
    void shouldFetchOrdersWithCustomerInOneQuery() {
        Customer customer = new Customer();
        customer.setName("Nón Lá Dev");
        entityManager.persist(customer);

        Order order = new Order();
        order.setCustomer(customer);
        order.setTotal(new BigDecimal("150.00"));
        entityManager.persist(order);
        entityManager.flush();

        List<Order> orders = orderRepository.findAllWithCustomer();

        assertThat(orders)
                .hasSize(1)
                .first()
                .extracting(o -> o.getCustomer().getName())
                .isEqualTo("Nón Lá Dev");
    }
}

Dieser Test „zählt” nicht direkt die Anzahl der Abfragen, aber kombiniert mit Hibernate Statistics (spring.jpa.properties.hibernate.generate_statistics=true) können Sie die Anzahl der ausgeführten Abfragen protokollieren und überprüfen, dass sie 1 ist – genau diese Art von Protokoll nutzt die KI, um zu bestätigen, ob die Behebung wirksam ist.

6. Überwachung für effektiveres KI-Debugging

Wenn Sie möchten, dass die KI (oder Kollegen, die später Protokolle lesen) schnell Diagnosen stellen, sollten Sie strukturierte Protokolle schreiben, statt nur e.printStackTrace():

@RestControllerAdvice
public class DatabaseExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(DatabaseExceptionHandler.class);

    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<String> handle(DataAccessException ex) {
        log.error("db_error_type={}, sql_state={}, message={}",
                ex.getClass().getSimpleName(),
                extractSqlState(ex),
                ex.getMostSpecificCause().getMessage());
        return ResponseEntity.status(503).body("Database error, retry later");
    }

    private String extractSqlState(DataAccessException ex) {
        if (ex.getCause() instanceof SQLException sqlEx) {
            return sqlEx.getSQLState();
        }
        return "unknown";
    }
}

Strukturierte Protokolle im Key-Value-Format (db_error_type=, sql_state=) lassen sich viel leichter analysieren als reine Stack Traces und sind eine ideale Eingabe, wenn Sie sie in den Databricks-Assistenten einfügen, um zu fragen, „worauf ist dieser Fehler zurückzuführen”.

7. Wann Sie KI-Debugging NICHT verwenden sollten

  • Fehler mit sensiblen Daten: Fügen Sie keine Protokolle mit echten Kundendaten in einen KI-Assistenten ein, auch nicht intern – maskieren Sie diese zuerst.
  • Performance-Probleme auf Infrastrukturebene (Disk-I/O, Netzwerklatenz zwischen Regionen): Die KI kann den Query-Plan lesen, sieht aber nicht die physische Infrastruktur-Schicht und kann leicht falsche Vorschläge machen (z. B. „Index hinzufügen”, während die Ursache Netzwerk ist).
  • Transiente Fehler, die nur einmal auftreten: Wenn eine Exception nur einmal aufgrund von Netzwerkproblemen auftritt, ist das Debugging mit KI zeitaufwändiger als einfach einen Retry zu versuchen und das Geschehen zu überwachen.
  • Kein Ersatz für Code Review: GRANT-Vorschläge oder Änderungen an der Pool-Größe, die von der KI gemacht werden, sollten von jemandem mit Infrastruktur-Berechtigung überprüft werden – nicht automatisch auf Production anwenden.

8. Fazit

Das KI-Datenbankdebugging auf Databricks ist stark darin, Stack Traces, Query-Pläne und Metadaten schnell zu lesen, um die Ursache einzugrenzen – Connection Leaks, N+1-Queries und fehlende Berechtigungen sind drei sehr häufige Fälle, die dieser Artikel mit Spring Boot und PostgreSQL reproduziert hat. Der eigentliche Wert liegt in strukturierten Protokollen und Tests, die die Behebung bestätigen, denn die KI ist nur so gut wie die Qualität der Daten, die Sie bereitstellen. Überprüfen Sie immer Empfehlungen – besonders GRANT-Berechtigungen oder Infrastrukturänderungen – bevor Sie sie auf das echte System anwenden.

databrickspostgresqlaidebuggingspring-boot