Skip to content

Commit ab0bbda

Browse files
committed
#842 Improve blob performance by combining open and first fetch - backport to Jaybird 6
1 parent cadd32d commit ab0bbda

File tree

18 files changed

+618
-133
lines changed

18 files changed

+618
-133
lines changed

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

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.firebirdsql.gds.ng.AbstractFbBlob;
2828
import org.firebirdsql.gds.ng.FbBlob;
2929
import org.firebirdsql.gds.ng.FbExceptionBuilder;
30-
import org.firebirdsql.gds.ng.LockCloseable;
3130
import org.firebirdsql.gds.ng.listeners.DatabaseListener;
3231
import org.firebirdsql.jaybird.util.ByteArrayHelper;
3332
import org.firebirdsql.jna.fbclient.FbClientLibrary;
@@ -132,10 +131,11 @@ public void open() throws SQLException {
132131
} else {
133132
bpb = ByteArrayHelper.emptyByteArray();
134133
}
135-
try (LockCloseable ignored = withLock()) {
134+
try (var ignored = withLock()) {
136135
checkDatabaseAttached();
137136
checkTransactionActive();
138137
checkBlobClosed();
138+
clearDeferredException();
139139

140140
final JnaDatabase db = getDatabase();
141141
if (isOutput()) {
@@ -146,11 +146,12 @@ public void open() throws SQLException {
146146
getJnaHandle(), blobId, (short) bpb.length, bpb);
147147
}
148148
processStatusVector();
149-
setOpen(true);
149+
setState(BlobState.OPEN);
150150
resetEof();
151+
throwAndClearDeferredException();
151152
}
152153
} catch (SQLException e) {
153-
exceptionListenerDispatcher.errorOccurred(e);
154+
errorOccurred(e);
154155
throw e;
155156
}
156157
}
@@ -162,7 +163,7 @@ public final boolean isOutput() {
162163

163164
@Override
164165
public byte[] getSegment(int sizeRequested) throws SQLException {
165-
try (LockCloseable ignored = withLock()) {
166+
try (var ignored = withLock()) {
166167
if (sizeRequested <= 0) {
167168
throw FbExceptionBuilder.forException(jb_blobGetSegmentNegative)
168169
.messageParameter(sizeRequested)
@@ -173,11 +174,12 @@ public byte[] getSegment(int sizeRequested) throws SQLException {
173174
checkBlobOpen();
174175
ShortByReference actualLength = new ShortByReference();
175176
ByteBuffer responseBuffer = getSegment0(sizeRequested, actualLength);
177+
throwAndClearDeferredException();
176178
byte[] segment = new byte[actualLength.getValue() & 0xFFFF];
177179
responseBuffer.get(segment);
178180
return segment;
179181
} catch (SQLException e) {
180-
exceptionListenerDispatcher.errorOccurred(e);
182+
errorOccurred(e);
181183
throw e;
182184
}
183185
}
@@ -199,7 +201,7 @@ private ByteBuffer getSegment0(int sizeRequested, ShortByReference actualLength)
199201

200202
@Override
201203
protected int get(final byte[] b, final int off, final int len, final int minLen) throws SQLException {
202-
try (LockCloseable ignored = withLock()) {
204+
try (var ignored = withLock()) {
203205
validateBufferLength(b, off, len);
204206
if (len == 0) return 0;
205207
if (minLen <= 0 || minLen > len ) {
@@ -222,9 +224,10 @@ protected int get(final byte[] b, final int off, final int len, final int minLen
222224
segmentBuffer.get(b, off + count, dataLength);
223225
count += dataLength;
224226
}
227+
throwAndClearDeferredException();
225228
return count;
226229
} catch (SQLException e) {
227-
exceptionListenerDispatcher.errorOccurred(e);
230+
errorOccurred(e);
228231
throw e;
229232
}
230233
}
@@ -235,7 +238,7 @@ private int getBlobBufferSize() {
235238

236239
@Override
237240
public void put(final byte[] b, final int off, final int len) throws SQLException {
238-
try (LockCloseable ignored = withLock()) {
241+
try (var ignored = withLock()) {
239242
validateBufferLength(b, off, len);
240243
if (len == 0) {
241244
throw FbExceptionBuilder.toException(jb_blobPutSegmentEmpty);
@@ -265,26 +268,29 @@ public void put(final byte[] b, final int off, final int len) throws SQLExceptio
265268
processStatusVector();
266269
count += segmentLength;
267270
}
271+
throwAndClearDeferredException();
268272
} catch (SQLException e) {
269-
exceptionListenerDispatcher.errorOccurred(e);
273+
errorOccurred(e);
270274
throw e;
271275
}
272276
}
273277

274278
@Override
275279
public void seek(int offset, SeekMode seekMode) throws SQLException {
276-
try (LockCloseable ignored = withLock()) {
280+
try (var ignored = withLock()) {
277281
checkDatabaseAttached();
278282
checkTransactionActive();
283+
checkBlobOpen();
279284

280285
// result is the current position in the blob (see .NET provider source)
281286
// We ignore the result TODO check if useful; not used in wire protocol either
282287
IntByReference result = new IntByReference();
283288
clientLibrary.isc_seek_blob(statusVector, getJnaHandle(), (short) seekMode.getSeekModeId(), offset,
284289
result);
285290
processStatusVector();
291+
throwAndClearDeferredException();
286292
} catch (SQLException e) {
287-
exceptionListenerDispatcher.errorOccurred(e);
293+
errorOccurred(e);
288294
throw e;
289295
}
290296
}
@@ -293,20 +299,22 @@ public void seek(int offset, SeekMode seekMode) throws SQLException {
293299
public byte[] getBlobInfo(byte[] requestItems, int bufferLength) throws SQLException {
294300
try {
295301
final ByteBuffer responseBuffer;
296-
try (LockCloseable ignored = withLock()) {
302+
try (var ignored = withLock()) {
297303
responseBuffer = getByteBuffer(bufferLength);
298304
checkDatabaseAttached();
305+
checkBlobOpen();
299306
clientLibrary.isc_blob_info(statusVector, getJnaHandle(),
300307
(short) requestItems.length, requestItems,
301308
(short) bufferLength, responseBuffer);
302309
processStatusVector();
310+
throwAndClearDeferredException();
303311
}
304312

305313
byte[] responseArr = new byte[bufferLength];
306314
responseBuffer.get(responseArr);
307315
return responseArr;
308316
} catch (SQLException e) {
309-
exceptionListenerDispatcher.errorOccurred(e);
317+
errorOccurred(e);
310318
throw e;
311319
}
312320
}

src/docs/asciidoc/release_notes.adoc

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ For known issues, consult <<known-issues>>.
3737

3838
The following was fixed or changed since Jaybird 6.0.0:
3939

40-
* ...
40+
* Improvement: backported deferred blob open optimization from Jaybird 7 (https://github.com/FirebirdSQL/jaybird/issues/842[#842])
41+
+
42+
See also <<blob-performance-defer-open>>.
4143

4244
=== Jaybird 6.0.0
4345

@@ -774,6 +776,21 @@ If the length is larger than the maximum segment size, or if the offset is non-z
774776

775777
Similar to the improvements for reading, we were also able to realise some other optimizations (in both pure Java and JNA), by avoiding allocation of a number of intermediate objects, but this has only marginal effects on the throughput.
776778

779+
[#blob-performance-defer-open]
780+
==== Deferred blob open
781+
782+
Added in: Jaybird 6.0.1, backported from Jaybird 7
783+
784+
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).
785+
The open or create blob request is pipelined with the subsequent operation, avoiding a round trip to the server.
786+
This is especially noticeable in connections with high latency.
787+
788+
Artificial testing on local WiFi with small blobs shows around 85% increase in throughput (comparing a 6.0.1-SNAPSHOT against 6.0.0).
789+
790+
This optimization is available for Firebird 2.1 and higher, but formally only supported for Firebird 3.0 and higher.
791+
792+
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.
793+
777794
[#blob-performance-min-buf]
778795
==== Minimum `blobBufferSize` 512 bytes
779796

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

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

35+
/**
36+
* Object handle id that can be used to refer to a just created server-side object (for protocol versions supporting
37+
* {@link #ptype_lazy_send}).
38+
* <p>
39+
* This can be used to pipeline the creation of an object with the subsequent use of that object without waiting on
40+
* the server response with the actual handle id.
41+
* </p>
42+
*/
3543
int INVALID_OBJECT = 0xFFFF;
3644

3745
/* Operation (packet) types */

0 commit comments

Comments
 (0)