fix(iris): raise jdbc query prefetch for large results

Refs: #7953
This commit is contained in:
t8y2 committed 2026-09-05 14:38:57 +08:00
1 parent 2f63c34ffa
commit cbaccb3a3a
2 files changed
+119

No files matched your search

@@ -2,11 +2,14 @@ package com.dbx.agent.iris;
import com.dbx.agent.ConfiguredJdbcAgent;
import com.dbx.agent.ColumnInfo;
import com.dbx.agent.ConnectParams;
import com.dbx.agent.JdbcAgentProfile;
import com.dbx.agent.JdbcExecutor;
import com.dbx.agent.MultiSessionJsonRpcServer;
import com.dbx.agent.StandardJdbcMetadata;
import com.intersystems.jdbc.IRISConnection;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
@@ -32,6 +35,36 @@ public final class IrisAgent extends ConfiguredJdbcAgent {
super(IRIS_PROFILE);
}
// The InterSystems driver's default query prefetch block (32768 bytes) means
// one server round trip per ~32KB of result data. Large result sets over
// high-latency links then spend most of their wall time waiting on the
// network instead of transferring rows, while other clients that negotiate
// larger fetch blocks finish several times faster. Statement.setFetchSize
// does not help here: intersystems-jdbc stores that value but never sends
// it to the server. The connection-level query prefetch size is the only
// effective knob, so raise it for every DBX IRIS connection. This is
// advisory: servers that reject the value keep the driver default, and the
// driver only round-trips when the value actually changes.
static final int IRIS_QUERY_PREFETCH_SIZE = 262144;
@Override
protected void afterConnect(ConnectParams params, Connection connection) {
super.afterConnect(params, connection);
applyQueryPrefetchSize(connection);
}
static void applyQueryPrefetchSize(Connection connection) {
if (!(connection instanceof IRISConnection)) {
return;
}
try {
((IRISConnection) connection).setQueryPrefetchSize(IRIS_QUERY_PREFETCH_SIZE);
} catch (Exception ignored) {
// Performance tuning only; keep the connection usable with the
// driver default when a server refuses the larger prefetch.
}
}
@Override
protected Object resultValue(ResultSet rs, int index, int sqlType) {
if (sqlType == Types.OTHER) {
@@ -0,0 +1,86 @@
package com.dbx.agent.iris;
import com.dbx.agent.ConnectParams;
import com.intersystems.jdbc.IRISConnection;
import org.junit.jupiter.api.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
class IrisAgentQueryPrefetchTest {
/** Records setQueryPrefetchSize calls without touching driver internals. */
static final class RecordingIrisConnection extends IRISConnection {
final List<Integer> prefetchSizes = new ArrayList<>();
@Override
public void setQueryPrefetchSize(int size) {
prefetchSizes.add(size);
}
}
static final class FailingIrisConnection extends IRISConnection {
@Override
public void setQueryPrefetchSize(int size) throws SQLException {
throw new SQLException("server rejected prefetch size");
}
}
@Test
void afterConnectAppliesLargerQueryPrefetch() {
RecordingIrisConnection connection = new RecordingIrisConnection();
new IrisAgent().afterConnect(new ConnectParams(), connection);
assertEquals(Collections.singletonList(IrisAgent.IRIS_QUERY_PREFETCH_SIZE), connection.prefetchSizes);
}
@Test
void afterConnectKeepsNonIrisConnectionsUntouched() {
Connection connection = proxyConnection();
assertDoesNotThrow(() -> new IrisAgent().afterConnect(new ConnectParams(), connection));
}
@Test
void prefetchFailureDoesNotBreakConnect() {
FailingIrisConnection connection = new FailingIrisConnection();
assertDoesNotThrow(() -> new IrisAgent().afterConnect(new ConnectParams(), connection));
}
@Test
void directApplyIsBoundedToIrisConnections() {
// A plain java.sql.Connection (for example a test or wrapper instance)
// must not be cast or fail when the prefetch tuning runs.
assertDoesNotThrow(() -> IrisAgent.applyQueryPrefetchSize(proxyConnection()));
}
private static Connection proxyConnection() {
return (Connection) Proxy.newProxyInstance(
IrisAgentQueryPrefetchTest.class.getClassLoader(),
new Class<?>[] { Connection.class },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Class<?> returnType = method.getReturnType();
if (returnType == boolean.class) {
return false;
}
if (returnType.isPrimitive() && returnType != void.class) {
return 0;
}
return null;
}
}
);
}
}