Skip to content

Commit d7ecce1

Browse files
committed
#838 Improve blob performance by combining open and first fetch
1 parent e754c42 commit d7ecce1

File tree

19 files changed

+585
-140
lines changed

19 files changed

+585
-140
lines changed

devdoc/jdp/jdp-2025-02-lazy-send-blob-optimizations.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55

66
== Status
77

8-
* Draft
9-
* Proposed for: Jaybird 5, Jaybird 6, Jaybird 7
8+
* Published: 2025-03-13
9+
* Implemented in: Jaybird 5, Jaybird 6, Jaybird 7
1010

1111
== Type
1212

jaybird-native/src/main/java/org/firebirdsql/gds/ng/jna/JnaBlob.java

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: Copyright 2014-2024 Mark Rotteveel
1+
// SPDX-FileCopyrightText: Copyright 2014-2025 Mark Rotteveel
22
// SPDX-License-Identifier: LGPL-2.1-or-later
33
package org.firebirdsql.gds.ng.jna;
44

@@ -11,7 +11,6 @@
1111
import org.firebirdsql.gds.ng.AbstractFbBlob;
1212
import org.firebirdsql.gds.ng.FbBlob;
1313
import org.firebirdsql.gds.ng.FbExceptionBuilder;
14-
import org.firebirdsql.gds.ng.LockCloseable;
1514
import org.firebirdsql.gds.ng.listeners.DatabaseListener;
1615
import org.firebirdsql.jaybird.util.ByteArrayHelper;
1716
import org.firebirdsql.jna.fbclient.FbClientLibrary;
@@ -116,10 +115,11 @@ public void open() throws SQLException {
116115
} else {
117116
bpb = ByteArrayHelper.emptyByteArray();
118117
}
119-
try (LockCloseable ignored = withLock()) {
118+
try (var ignored = withLock()) {
120119
checkDatabaseAttached();
121120
checkTransactionActive();
122121
checkBlobClosed();
122+
clearDeferredException();
123123

124124
final JnaDatabase db = getDatabase();
125125
if (isOutput()) {
@@ -130,11 +130,12 @@ public void open() throws SQLException {
130130
getJnaHandle(), blobId, (short) bpb.length, bpb);
131131
}
132132
processStatusVector();
133-
setOpen(true);
133+
setState(BlobState.OPEN);
134134
resetEof();
135+
throwAndClearDeferredException();
135136
}
136137
} catch (SQLException e) {
137-
exceptionListenerDispatcher.errorOccurred(e);
138+
errorOccurred(e);
138139
throw e;
139140
}
140141
}
@@ -146,7 +147,7 @@ public final boolean isOutput() {
146147

147148
@Override
148149
public byte[] getSegment(int sizeRequested) throws SQLException {
149-
try (LockCloseable ignored = withLock()) {
150+
try (var ignored = withLock()) {
150151
if (sizeRequested <= 0) {
151152
throw FbExceptionBuilder.forException(jb_blobGetSegmentNegative)
152153
.messageParameter(sizeRequested)
@@ -157,11 +158,12 @@ public byte[] getSegment(int sizeRequested) throws SQLException {
157158
checkBlobOpen();
158159
ShortByReference actualLength = new ShortByReference();
159160
ByteBuffer responseBuffer = getSegment0(sizeRequested, actualLength);
161+
throwAndClearDeferredException();
160162
byte[] segment = new byte[actualLength.getValue() & 0xFFFF];
161163
responseBuffer.get(segment);
162164
return segment;
163165
} catch (SQLException e) {
164-
exceptionListenerDispatcher.errorOccurred(e);
166+
errorOccurred(e);
165167
throw e;
166168
}
167169
}
@@ -183,7 +185,7 @@ private ByteBuffer getSegment0(int sizeRequested, ShortByReference actualLength)
183185

184186
@Override
185187
protected int get(final byte[] b, final int off, final int len, final int minLen) throws SQLException {
186-
try (LockCloseable ignored = withLock()) {
188+
try (var ignored = withLock()) {
187189
validateBufferLength(b, off, len);
188190
if (len == 0) return 0;
189191
if (minLen <= 0 || minLen > len ) {
@@ -206,9 +208,10 @@ protected int get(final byte[] b, final int off, final int len, final int minLen
206208
segmentBuffer.get(b, off + count, dataLength);
207209
count += dataLength;
208210
}
211+
throwAndClearDeferredException();
209212
return count;
210213
} catch (SQLException e) {
211-
exceptionListenerDispatcher.errorOccurred(e);
214+
errorOccurred(e);
212215
throw e;
213216
}
214217
}
@@ -219,7 +222,7 @@ private int getBlobBufferSize() {
219222

220223
@Override
221224
public void put(final byte[] b, final int off, final int len) throws SQLException {
222-
try (LockCloseable ignored = withLock()) {
225+
try (var ignored = withLock()) {
223226
validateBufferLength(b, off, len);
224227
if (len == 0) {
225228
throw FbExceptionBuilder.toException(jb_blobPutSegmentEmpty);
@@ -249,26 +252,29 @@ public void put(final byte[] b, final int off, final int len) throws SQLExceptio
249252
processStatusVector();
250253
count += segmentLength;
251254
}
255+
throwAndClearDeferredException();
252256
} catch (SQLException e) {
253-
exceptionListenerDispatcher.errorOccurred(e);
257+
errorOccurred(e);
254258
throw e;
255259
}
256260
}
257261

258262
@Override
259263
public void seek(int offset, SeekMode seekMode) throws SQLException {
260-
try (LockCloseable ignored = withLock()) {
264+
try (var ignored = withLock()) {
261265
checkDatabaseAttached();
262266
checkTransactionActive();
267+
checkBlobOpen();
263268

264269
// result is the current position in the blob (see .NET provider source)
265270
// We ignore the result TODO check if useful; not used in wire protocol either
266271
IntByReference result = new IntByReference();
267272
clientLibrary.isc_seek_blob(statusVector, getJnaHandle(), (short) seekMode.getSeekModeId(), offset,
268273
result);
269274
processStatusVector();
275+
throwAndClearDeferredException();
270276
} catch (SQLException e) {
271-
exceptionListenerDispatcher.errorOccurred(e);
277+
errorOccurred(e);
272278
throw e;
273279
}
274280
}
@@ -277,20 +283,21 @@ public void seek(int offset, SeekMode seekMode) throws SQLException {
277283
public byte[] getBlobInfo(byte[] requestItems, int bufferLength) throws SQLException {
278284
try {
279285
final ByteBuffer responseBuffer;
280-
try (LockCloseable ignored = withLock()) {
286+
try (var ignored = withLock()) {
281287
responseBuffer = getByteBuffer(bufferLength);
282288
checkDatabaseAttached();
289+
checkBlobOpen();
283290
clientLibrary.isc_blob_info(statusVector, getJnaHandle(),
284291
(short) requestItems.length, requestItems,
285292
(short) bufferLength, responseBuffer);
286293
processStatusVector();
294+
throwAndClearDeferredException();
287295
}
288-
289296
byte[] responseArr = new byte[bufferLength];
290297
responseBuffer.get(responseArr);
291298
return responseArr;
292299
} catch (SQLException e) {
293-
exceptionListenerDispatcher.errorOccurred(e);
300+
errorOccurred(e);
294301
throw e;
295302
}
296303
}

src/docs/asciidoc/release_notes.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,24 @@ These were previously not explicitly licensed.
408408

409409
For more information, see: https://github.com/FirebirdSQL/jaybird/blob/master/devdoc/jdp/jdp-2025-01-apply-spdx-license-info-and-comply-with-reuse-specification.adoc[jdp-2025-01: Apply SPDX license info and comply with REUSE specification^]
410410

411+
[#blob-performance]
412+
=== Blob performance improvements
413+
414+
[#blob-performance-defer-open]
415+
==== Deferred blob open
416+
417+
In the pure Java implementation, performance of reading and writing blobs was improved by deferring the server-side opening or creating of a blob until an actual server-side operation (putting or getting a segment, or getting blob info).
418+
The open or create blob request is pipelined with the subsequent operation, avoiding a round trip to the server.
419+
This is especially noticeable in connections with high latency.
420+
421+
Artificial testing on local WiFi with small blobs shows around 85% increase in throughput (comparing a 6.0.1-SNAPSHOT against 6.0.0).
422+
423+
This optimization is available for Firebird 2.1 and higher, but formally only supported for Firebird 3.0 and higher.
424+
425+
This optimization was backported to Jaybird 5.0.7 and Jaybird 6.0.1.
426+
427+
For native connections, a similar optimization -- but only for reading blobs -- is available when using a Firebird 5.0.2 or higher fbclient, independent of the Jaybird version.
428+
411429
// TODO add major changes
412430

413431
[#other-fixes-and-changes]

src/main/org/firebirdsql/gds/impl/wire/WireProtocolConstants.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
@SuppressWarnings({ "unused", "java:S115", "java:S1214" })
2020
public interface WireProtocolConstants {
2121

22+
/**
23+
* Object handle id that can be used to refer to a just created server-side object (for protocol versions supporting
24+
* {@link #ptype_lazy_send}).
25+
* <p>
26+
* This can be used to pipeline the creation of an object with the subsequent use of that object without waiting on
27+
* the server response with the actual handle id.
28+
* </p>
29+
*/
2230
int INVALID_OBJECT = 0xFFFF;
2331

2432
int op_void = 0;

0 commit comments

Comments
 (0)