Benchmarking YSQL Connection Time in YugabyteDB with the JDBC Smart Driver

In a previous YugabyteDB Tip, we measured how long it takes to establish a YSQL connection using ysqlsh.

This time, we’ll take a similar approach, but instead of the shell, we’ll use the YugabyteDB JDBC Smart Driver and a small Java program to measure connection latency.

Why bother?
  • • Many applications connect via JDBC, not ysqlsh.

  • • The driver has its own startup overhead (handshakes, parameter setup, etc.).

  • • Measuring here gives you a more application-realistic connection time.

Step 1 – Create the Java Program

We’ll write a short program that:

  • • Opens a JDBC connection to YugabyteDB using the Smart Driver.

  • • Measures three key timings for connection establishment:

    • ◦ connection_time_ms – The total time from when our code calls DriverManager.getConnection(...) until the connection is fully ready for use.

    • ◦ driver_first_attempt_ms – The time from the driver’s first "Trying to establish a protocol version 3 connection to..." log entry until it receives either a "Created connection to..." or "AuthenticationOk" from the server.

    • ◦ driver_final_attempt_msOnly in load-balanced cases: The time from the driver’s second "Trying to establish..." log entry until the final "Created connection to..." or "AuthenticationOk".

  • • Detects if the connection was load balanced by checking whether there are two "Trying to establish..." log entries.

  • • Falls back to "AuthenticationOk" markers if "Created connection to..." entries aren’t present.

  • • Prints the measured times clearly in milliseconds, along with the server’s host, cloud, region, and zone.

  • • Supports a --quiet mode to suppress the Smart Driver’s internal debug chatter and only show our final metrics.

  • • Supports a --verbose mode to prints all driver log entries (great for in depth debugging)
How the timings fit together

Case 1 – No Load Balancing (single attempt)

				
					   T0:  [Start connection in app code]
        |
        | <---- connection_time_ms ---->|
        |
        |    [Driver logs: "Trying to establish..."]
        |      <--- driver_first_attempt_ms --->
        |                                |
   T1:  [AuthenticationOk or Created connection to]
        |
   T2:  [Driver finishes setup, returns connection]

				
			

Case 2 – Load Balanced (two attempts)

				
					   T0:  [Start connection in app code]
        |
        | <----------- connection_time_ms ----------->|
        |
        |    [Driver logs: "Trying to establish..."]  <-- First node
        |      <--- driver_first_attempt_ms --->
        |                                |
   T1:  [AuthenticationOk or Created connection to]
        |       (Driver detects load balancing, retries)
        |
        |    [Driver logs: "Trying to establish..."]  <-- Second node
        |      <--- driver_final_attempt_ms --->
        |                                |
   T2:  [AuthenticationOk or Created connection to]
        |
   T3:  [Driver finishes setup, returns connection]

				
			
Project layout
				
					yb-smart-driver-test/
├─ pom.xml
└─ src/
   └─ main/
      └─ java/
         └─ com/
            └─ yb/
               └─ smart/
                  └─ App.java

				
			
				
					src/main/java/com/yb/smart/App.java
				
			
				
					package com.yb.smart;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.ConsoleHandler;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

public class App {

    public static void main(String[] args) throws Exception {
        if (args.length < 1) {
            System.err.println("Usage: java -jar yb-smart-driver-test.jar <JDBC_URL> [--quiet|--verbose]");
            System.exit(1);
        }

        final String jdbcUrl = args[0];
        final boolean quiet   = Arrays.stream(args).anyMatch("--quiet"::equalsIgnoreCase);
        final boolean verbose = Arrays.stream(args).anyMatch("--verbose"::equalsIgnoreCase);

        // Primary timing markers based on "Created connection to ..."
        final AtomicLong tStart     = new AtomicLong(-1); // first "Trying to establish..."
        final AtomicLong tCreated1  = new AtomicLong(-1); // first "Created connection to ..."
        final AtomicLong tCreated2  = new AtomicLong(-1); // second "Created connection to ..." (load-balanced hop)

        // Fallback timing markers based on "AuthenticationOk"
        final AtomicLong tAuth1     = new AtomicLong(-1);
        final AtomicLong tAuth2     = new AtomicLong(-1);

        // Count "Trying..." to detect load balancing
        final AtomicInteger tryingCount = new AtomicInteger(0);

        setupLogging(quiet, verbose, tStart, tCreated1, tCreated2, tAuth1, tAuth2, tryingCount);

        // Measure wall-clock around the entire getConnection call
        final long t0 = System.nanoTime();
        String host = null, cloud = null, region = null, zone = null;
        try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
            final long t1 = System.nanoTime();
            final double connectionMs = (t1 - t0) / 1_000_000.0;
            System.out.printf("connection_time_ms=%.3f%n", connectionMs);

            // Decide which markers to use for first/final
            final boolean createdAvailable = (tCreated1.get() > 0);
            final long firstEnd  = createdAvailable ? tCreated1.get() : tAuth1.get();
            final long secondEnd = createdAvailable ? tCreated2.get() : tAuth2.get();

            // Names reflect the semantics explained in the blog
            final String firstLabel  = "driver_first_attempt_ms";
            final String finalLabel  = "driver_final_attempt_ms";

            if (tStart.get() > 0 && firstEnd > 0 && firstEnd >= tStart.get()) {
                System.out.printf("%s=%.3f%n", firstLabel, (firstEnd - tStart.get()) * 1.0);
            } else {
                System.out.printf("%s=N/A%n", firstLabel);
            }

            if (tStart.get() > 0 && secondEnd > 0 && secondEnd >= tStart.get()) {
                System.out.printf("%s=%.3f%n", finalLabel, (secondEnd - tStart.get()) * 1.0);
            }

            // Print load-balanced flag (true if we saw 2+ "Trying..." lines)
            System.out.printf("load_balanced=%b%n", tryingCount.get() >= 2);

            // Show where we landed
            try (Statement stmt = conn.createStatement()) {
                final String sql = "SELECT host(inet_server_addr()) AS host, " +
                        "yb_server_cloud() AS cloud, " +
                        "yb_server_region() AS region, " +
                        "yb_server_zone() AS zone";
                try (ResultSet rs = stmt.executeQuery(sql)) {
                    if (rs.next()) {
                        host   = rs.getString("host");
                        cloud  = rs.getString("cloud");
                        region = rs.getString("region");
                        zone   = rs.getString("zone");
                    }
                }
            }

            if (host != null) {
                System.out.printf("host=%s / cloud=%s / region=%s / zone=%s%n",
                        host, cloud, region, zone);
            }
        }
    }

    private static void setupLogging(
            boolean quiet,
            boolean verbose,
            AtomicLong tStart,
            AtomicLong tCreated1,
            AtomicLong tCreated2,
            AtomicLong tAuth1,
            AtomicLong tAuth2,
            AtomicInteger tryingCount
    ) {
        // Reset global logging configuration
        LogManager.getLogManager().reset();

        // Our single handler: captures markers; prints depending on flags
        Handler h = new ConsoleHandler() {
            private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
            { setLevel(Level.ALL); }

            @Override
            public synchronized void publish(LogRecord r) {
                String raw = r.getMessage();
                if (raw == null) return;

                // Expand {0} placeholders (driver uses MessageFormat)
                String msg = (r.getParameters() != null && r.getParameters().length > 0)
                        ? MessageFormat.format(raw, r.getParameters())
                        : raw;

                // Capture start and end markers
                if (msg.contains("Trying to establish a protocol version 3 connection to")) {
                    tryingCount.incrementAndGet();
                    tStart.compareAndSet(-1, r.getMillis()); // only first start matters as our zero
                }
                if (msg.contains("Created connection to")) {
                    if (tCreated1.compareAndSet(-1, r.getMillis())) {
                        // first Created
                    } else {
                        tCreated2.compareAndSet(-1, r.getMillis());
                    }
                }
                if (msg.contains("AuthenticationOk")) {
                    if (tAuth1.compareAndSet(-1, r.getMillis())) {
                        // first Auth
                    } else {
                        tAuth2.compareAndSet(-1, r.getMillis());
                    }
                }

                // Printing policy:
                //  - --quiet   : print nothing from the driver
                //  - --verbose : print ALL log entries
                //  - default   : print only key lines (start + Created + AuthenticationOk)
                if (quiet) {
                    return;
                }

                boolean isKey =
                        msg.contains("Trying to establish a protocol version 3 connection to") ||
                        msg.contains("Created connection to") ||
                        msg.contains("AuthenticationOk");

                if (verbose || isKey) {
                    String ts = sdf.format(new Date(r.getMillis()));
                    String loggerName = r.getLoggerName() != null ? r.getLoggerName() : "";
                    System.out.printf("%s %s %s%n", ts, loggerName, msg);
                }
            }
        };

        // Attach handler to Yugabyte driver loggers (and only those), at FINEST
        Level lvl = Level.FINEST;

        Logger core = Logger.getLogger("com.yugabyte.core");
        core.setUseParentHandlers(false);
        core.setLevel(lvl);
        core.addHandler(h);

        Logger drv = Logger.getLogger("com.yugabyte.Driver");
        drv.setUseParentHandlers(false);
        drv.setLevel(lvl);
        drv.addHandler(h);

        Logger jdbc = Logger.getLogger("com.yugabyte.jdbc");
        jdbc.setUseParentHandlers(false);
        jdbc.setLevel(lvl);
        jdbc.addHandler(h);

        // Silence everything else (root)
        Logger root = Logger.getLogger("");
        root.setUseParentHandlers(false);
        root.setLevel(Level.OFF);
    }
}
				
			
Step 2 – Maven Project Setup
				
					pom.xml
				
			
				
					<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.yb.smart</groupId>
    <artifactId>yb-smart-driver-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.yugabyte</groupId>
            <artifactId>jdbc-yugabytedb</artifactId>
            <version>42.7.3-yb-4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.6.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.yb.smart.App</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals><goal>single</goal></goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
				
			
Step 3 – Compile

From the project root:

				
					mvn -q -DskipTests package
				
			

You’ll get:

				
					target/yb-smart-driver-test-1.0-SNAPSHOT-jar-with-dependencies.jar
				
			
Step 4 – Run the Test
				
					[root@localhost yb-smart-driver-test]# java -jar target/yb-smart-driver-test-1.0-SNAPSHOT-jar-with-dependencies.jar "jdbc:yugabytedb://127.0.0.1:5433/yugabyte?user=yugabyte&password=yugabyte" --quiet
connection_time_ms=141.482
driver_first_auth_ms=39.000
host=127.0.0.1 / cloud=onprem / region=datacenter1 / zone=rack1

[root@localhost yb-smart-driver-test]# java -jar target/yb-smart-driver-test-1.0-SNAPSHOT-jar-with-dependencies.jar "jdbc:yugabytedb://127.0.0.1:5433/yugabyte?user=yugabyte&password=yugabyte&load-balance=true" --quiet
connection_time_ms=189.052
driver_first_auth_ms=42.000
driver_balanced_auth_ms=121.000
host=127.0.0.1 / cloud=onprem / region=datacenter1 / zone=rack2
				
			

If your environment uses trust authentication, you can omit user and password.

Notes:

  • connection_time_ms is measured externally in the Java program… basically a wall-clock delta from before calling DriverManager.getConnection(...) until after the connection is ready for use.

  • driver_first_attempt_ms is measured inside the driver logs between the first "Trying to establish..." and the first "AuthenticationOk" or "Created connection to".

  • driver_final_attempt_ms is measured inside the driver logs between the first "Trying to establish..." and the second "AuthenticationOk"/"Created connection to" if the connection was load-balanced.

Why this matters

By enabling FINEST logging for specific Yugabyte driver classes and filtering for handshake events, we capture the exact window where the driver is establishing the TCP + wire-protocol connection, without being buried in the full protocol chatter.

By breaking down connection times this way, you can:

  • • Identify slow initial connections (DNS, network latency, SSL handshake delays).

  • • Detect load-balancing behavior and measure its impact.

  • • Compare driver internal timings to application-perceived timings.

  • • Quickly see if slow connections are cluster-wide or just for certain regions/zones.

With this approach, you can benchmark YSQL connection latency directly from Java and because we’re using the JDBC Smart Driver, it works for YugabyteDB clusters anywhere your Java application can connect.

Have Fun!

Every Wednesday, Yugabyte gives us DoorDash credits. Faced with a menu full of semi-healthy Chinese options… guess who went straight for the bacon fried rice? 🥓🍚