go-hdb

repository·main·Indexed 41 days ago

https://github.com/sap/go-hdb

A pure Go, native driver for the SAP HANA database that implements the SAP HANA SQL Command Network Protocol. It is designed as a statically linked, cross-compilable binary with no dependency on cgo or official SAP HANA client libraries. It supports LZ4 compression via a plugin-based Compressor interface and includes the bulkbench tool for performance benchmarking.

Tokens
9.6K
Snippets
26
Records
52
Agent score
44%

What's inside go-hdb

  1. Handle decimal and fixed column changes

    main

    In v1.17.0, the value provided by the driver for decimal and fixed columns changed from *big.Rat to a different internal representation.

    Impact: If your application uses custom sql.Scanner implementations that perform type assertions to *big.Rat, your code will break.

    Action Required: Adapt your custom scanners to use the database/sql decimal Decompose / Compose interfaces. Note that code scanning directly into driver.Decimal or driver.NullDecimal is not affected by this change.

  2. Bulkbench test object and variants

    main

    Test Object

    The benchmark uses a column table with two columns:

    • id: integer
    • field: double

    SQL definition:

    create column table <TableName> (id integer, field double)

    Test Variants

    Tests insert data in chunks of batchSize for a total of batchCount chunks.

    • Sequential: Data is inserted sequentially.
    • Concurrent: Data is inserted in parallel using goroutines. The actual degree of parallelism is governed by the Go sql.DB connection pool configuration.
  3. How compression works in go-hdb

    main

    go-hdb supports LZ4 compression for data exchanged with a HANA database to reduce network traffic. To maintain a pure Go, no-CGO, and zero-dependency architecture, the driver does not ship with a built-in compressor. Instead, it uses a plugin-based model where you provide your own implementation of the Compressor interface.

    Compression Negotiation

    Compression is negotiated at connection time using two independent behaviors:

    1. Inbound (Receiving): Configuring any compressor tells the driver it can receive compressed packets. The driver will automatically decompress inbound packets if the server marks them as compressed.
    2. Outbound (Sending): The EnableWrite() method determines if the driver should attempt to send compressed packets. If EnableWrite() returns false, the driver will still decompress inbound traffic but will never compress outbound traffic.

    Important Constraints

    • Format: The implementation must use the LZ4 block format, not the LZ4 frame format.
    • Thresholds: The driver may choose to send a packet uncompressed if the payload is below a certain size threshold or if compression does not provide significant savings. In these cases, the Compress output is discarded and the original bytes are sent.
    type Compressor interface {
        EnableWrite() bool                       // send data compressed if the server supports it
        Decompress(src, dst []byte) (int, error) // decompress src into dst, returning bytes written
        CompressBound(n int) int                 // upper bound of compressed size for n input bytes
        Compress(src, dst []byte) (int, error)   // compress src into dst, returning bytes written
    }
  4. Compare driver stats with database/sql pool stats

    main

    To get a complete picture of performance, you must correlate go-hdb driver stats with Go's standard database/sql pool stats (db.Stats()).

    • Connection Contention: If sql.DBStats.WaitCount and WaitDuration are climbing, your MaxOpenConnections is likely too low for your application's parallelism.
    • Connection Churn: If sql.DBStats.MaxIdleTimeClosed or MaxIdleClosed are climbing, the pool is discarding connections. This forces the driver to perform new authentications on the next request, increasing latency. This is controlled by MaxIdleConns and ConnMaxIdleTime settings.
  5. Mental model for diagnosing go-hdb latency

    main

    When diagnosing slow database calls in Go, do not treat "slow SQL" as a single unit. A single Prepare + Exec operation involves several distinct phases, each with different causes and fixes. Application-level tracing (like otelsql) typically measures the total wall-clock time of all phases combined (A through H). To localize the issue, you must use the driver's internal instrumentation to split these phases apart.

    Latency Phases

    PhaseDescriptionPotential Cause / Fix
    (A) Pool WaitingWaiting for a free connection from database/sqlPool size or contention
    (B) Ping Round-tripOptional Ping when reusing an idle connectionSetPingInterval
    (C) TCP ConnectEstablishing a new physical connectionNetwork, DNS, or routing issues
    (D) HANA AuthThe authentication handshakeAuthTime
    (E) Auth RefreshExecution of auth refresh callbacksSetRefreshPassword, SetToken, or SetClientCert
    (F) Prepare RTTRound-trip to HANA for statement preparationSQLTimes["prepare"]
    (G) Exec/Query RTTRound-trip + data fetchingSQLTimes["exec"|"query"|"fetch"]
    (H) HANA ExecutionActual work performed by the HANA backendHANA plan cache or expensive statements

    Key Insight: Establishing a new physical connection (Phases C–E) is expensive. High connection churn (frequent opening/closing of connections) is a common source of latency that is unrelated to the SQL statements themselves.

  6. Understand bulkbench performance metrics

    main

    In addition to standard Go benchmark results (like ns/op), bulkbench reports four specific time-based metrics for inserting BatchCount x BatchSize records:

    • avgsec/op: Average time per operation.
    • maxsec/op: Maximum time per operation.
    • medsec/op: Median time per operation.
    • minsec/op: Minimum time per operation.
  7. Quickstart: Connect to HANA using Basic Authentication

    main

    You can connect to a HANA database by creating a connector using driver.NewBasicAuthConnector and then opening a database handle with sql.OpenDB. This approach avoids the need for a DSN string and uses the driver's native connector interface.

    package main
    
    import (
    	"database/sql"
    	"fmt"
    
    	"github.com/SAP/go-hdb/driver"
    )
    
    func main() {
    	connector := driver.NewBasicAuthConnector("host:port", "user", "password")
    
    	db := sql.OpenDB(connector)
    	defer db.Close()
    
    	var name string
    	if err := db.QueryRow("select current_user from dummy").Scan(&name); err != nil {
    		panic(err)
    	}
    	fmt.Println(name)
    }
  8. Enable and use SQL tracing

    main

    SQL tracing allows you to see individual statements and their execution durations.

    Important Caveat: The tracer is attached to a connection when it is created. If you enable tracing at runtime via driver.SetSQLTrace(true), it will only affect new connections. Existing pooled connections will not be traced until they are recycled. To trace all traffic immediately, enable it at process startup using the flag -hdb.sqlTrace=true.

    Log Format: The trace uses structured slog at INFO level with the message "SQL". Example: level=INFO msg=SQL exec="SELECT * FROM t WHERE id=?" ms=12 arg.1=42

    Routing Logs: By default, traces use slog.Default(). You can route them to your application's logger using connector.SetLogger(myStructuredLogger).

    // Enable/disable at runtime
    driver.SetSQLTrace(true)   // enable
    driver.SetSQLTrace(false)  // disable
    driver.SQLTrace()          // returns current bool state
    
    // Or via CLI flag at startup
    // -hdb.sqlTrace=true
  9. Investigate and diagnose go-hdb performance issues

    main

    When experiencing performance issues, follow this checklist to isolate whether the bottleneck is in the HANA backend, the network, or the driver/pool.

    Investigation Checklist

    1. Check HANA Backend: Monitor load, CPU, memory, expensive statements, and the SQL plan cache.
    2. Check Network: Look for latency, loss, or idle-connection drops by firewalls/NAT.
    3. Establish a Baseline: Use cmd/bulkbench to create a controlled reference measurement with a known network baseline and warmed pool.
    4. Sample Driver Stats: Use NativeDriver().Stats() or db.ExStats() to log AuthTime.Count, AuthTime.Sum, and SQLTimes[*].Count.
    5. Monitor Connection Churn: Check if AuthTime.Count climbs during steady traffic.
    6. Check Pool Contention: Sample sql.DBStats for WaitCount/WaitDuration (contention) and MaxIdleTimeClosed/MaxIdleClosed (idle eviction).
    7. Export Metrics: Use go_hdb_* metrics and NewDBStatsCollector with Prometheus/OTel to correlate auth/churn spikes with application tracing.
    8. Enable SQL Tracing: Use driver.SetSQLTrace(true) (affects new connections only) to see per-statement millisecond timing.
    9. Review Pool Config: Ensure MaxIdleConns, ConnMaxIdleTime, and MaxOpenConns match observed parallelism.
    10. Audit Callbacks: Ensure any registered refresh callbacks do not perform synchronous I/O.
    11. Verify Idle Connections: Use SetPingInterval to weed out dead connections.
    12. Cross-check with HANA: Compare the driver's SQLTimes["exec"] tail against HANA's measured execution time. A large gap indicates the delay is on the client, network, or pool side.