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_ms – Only 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
--quietmode to suppress the Smart Driver’s internal debug chatter and only show our final metrics.- • Supports a
--verbosemode 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 [--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
4.0.0
com.yb.smart
yb-smart-driver-test
1.0-SNAPSHOT
11
11
com.yugabyte
jdbc-yugabytedb
42.7.3-yb-4
org.apache.maven.plugins
maven-compiler-plugin
3.11.0
org.apache.maven.plugins
maven-assembly-plugin
3.6.0
com.yb.smart.App
jar-with-dependencies
package
single
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_msis measured externally in the Java program… basically a wall-clock delta from before callingDriverManager.getConnection(...)until after the connection is ready for use.•
driver_first_attempt_msis measured inside the driver logs between the first"Trying to establish..."and the first"AuthenticationOk"or"Created connection to".•
driver_final_attempt_msis 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!
