Skip to content

Upgrade qat-java to 2.5.0 and batch decompression into a single JNI call - #357

Merged
reta merged 2 commits into
opensearch-project:mainfrom
mulugetam:2.5.0
Jul 18, 2026
Merged

Upgrade qat-java to 2.5.0 and batch decompression into a single JNI call#357
reta merged 2 commits into
opensearch-project:mainfrom
mulugetam:2.5.0

Conversation

@mulugetam

Copy link
Copy Markdown
Contributor

Description

Bump the qat-java dependency from 2.4.0 to 2.5.0 and adapt to its API changes:

  • Rework QatCompressionMode decompression to read all needed compressed sub-blocks into one packed buffer and decompress them with a single qatZipper.decompressFull() call, instead of one JNI decompress call per block. This cuts JNI overhead on stored-fields reads that span multiple blocks.

  • Remove the retryCount parameter from QatZipperFactory: qat-java 2.5.0 drops retry-count configuration from QatZipper.Builder, so the factory overloads that accepted it are removed and the remaining overloads simplified.

Check List

  • Commits are signed per the DCO using --signoff.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Bump the qat-java dependency from 2.4.0 to 2.5.0 and adapt to its API
changes:

- Remove the retryCount parameter from QatZipperFactory: qat-java 2.5.0
  drops retry-count configuration from QatZipper.Builder, so the factory
  overloads that accepted it are removed and the remaining overloads
  simplified.

- Rework QatCompressionMode decompression to read all needed compressed
  sub-blocks into one packed buffer and decompress them with a single
  qatZipper.decompressFull() call, instead of one JNI decompress call
  per block. This cuts JNI overhead on stored-fields reads that span
  multiple blocks.

Signed-off-by: Mulugeta Mammo <mulugeta.mammo@intel.com>
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

PR Code Analyzer ❗

AI-powered 'Code-Diff-Analyzer' found issues on commit f4378ba.

PathLineSeverityDescription
build.gradle100highDependency version bump: com.intel.qat:qat-java upgraded from 2.4.0 to 2.5.0. Per mandatory supply chain policy, all dependency version changes must be flagged. The corresponding SHA1 integrity file (licenses/qat-java-2.5.0.jar.sha1) was also replaced. Maintainers should verify the new artifact (b1adc9a53eb6f1e8d61407f3461406d546bd11d4) against the official Intel QAT release and confirm the new decompressFull JNI method introduced in QatCompressionMode.java is present in the published 2.5.0 artifact.

The table above displays the top 10 most important findings.

Total: 1 | Critical: 0 | High: 1 | Medium: 0 | Low: 0


Pull Requests Author(s): Please update your Pull Request according to the report above.

Repository Maintainer(s): You can bypass diff analyzer by adding label skip-diff-analyzer after reviewing the changes carefully, then re-run failed actions. To re-enable the analyzer, remove the label, then re-run all actions.


⚠️ Note: The Code-Diff-Analyzer helps protect against potentially harmful code patterns. Please ensure you have thoroughly reviewed the changes beforehand.

Thanks.

Comment thread src/main/java/org/opensearch/index/codec/customcodecs/QatCompressionMode.java Outdated
Comment thread src/main/java/org/opensearch/index/codec/customcodecs/QatCompressionMode.java Outdated
@reta

reta commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Missing SHA for qat-java-2.5.0.jar. Run "gradle updateSHAs" to create them

…t-java jar.

- Fix decompress buffer over-allocation: start the packed compressed
  buffer at originalLength/2 with growNoCopy and grow on demand while
  packing, instead of pre-growing to the full decompressed size.
- Validate the decompressFull byte count via assert and drop the dead
  bytes.length = totalWritten assignment.
- Remove duplicate QatCompressor Javadoc line.
- Add SHA for qat-java-2.5.0.jar and remove the stale 2.4.0 SHA
  (gradle updateShas).

Signed-off-by: Mulugeta Mammo <mulugeta.mammo@intel.com>
@mulugetam

Copy link
Copy Markdown
Contributor Author

@reta Thanks for the review. All addressed:

@reta reta added the skip-diff-analyzer Maintainer to skip code-diff-analyzer check, after reviewing issues in AI analysis. label Jul 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🧪 No relevant tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ Recommended focus areas for review

Empty if-block dead code

The check if (compressedLength == 0) { break; } was refactored but the braces still wrap only the break. More importantly, the original code returned when compressedLength == 0; the new code breaks out of the loop and then proceeds to call decompressFull and set bytes.offset/bytes.length. If a zero-length block is encountered mid-iteration after some data has already been packed, totalDecompressed may not equal length, and the subsequent assertion bytes.isValid() (which likely validates against bytes.length = length) could fail or produce incorrect data. Verify that a zero compressedLength block cannot occur mid-range, or restore the early-return semantics.

while (offsetInBlock < offset + length) {
    final int compressedLength = in.readVInt();
    if (compressedLength == 0) {
        break;
    }

    compressed = ArrayUtil.grow(compressed, srcPos + compressedLength);
    in.readBytes(compressed, srcPos, compressedLength);
    srcPos += compressedLength;
    totalDecompressed += Math.min(blockLength, originalLength - offsetInBlock);
    offsetInBlock += blockLength;
}

if (srcPos == 0) {
    return;
}
Potential buffer size mismatch

bytes.bytes = ArrayUtil.grow(bytes.bytes, totalDecompressed) sizes the output only for the decompressed portion, but bytes.length is later set to length and bytes.offset to offsetInBytesRef. If offsetInBytesRef + length > totalDecompressed, subsequent reads via the BytesRef could exceed the buffer or read stale data. Previously the buffer grew by bytes.length + l cumulatively per block, matching offset semantics. Confirm that totalDecompressed >= offsetInBytesRef + length always holds.

bytes.bytes = ArrayUtil.grow(bytes.bytes, totalDecompressed);

// Single JNI call: native side loops through all concatenated
// compressed frames, decompressing them into the output buffer.
int totalWritten = qatZipper.decompressFull(compressed, 0, srcPos, bytes.bytes, 0, totalDecompressed);
assert totalWritten == totalDecompressed : "Decompressed byte count ("
    + totalWritten
    + ") does not match expected ("
    + totalDecompressed
    + ").";

bytes.offset = offsetInBytesRef;
bytes.length = length;

@github-actions

Copy link
Copy Markdown
Contributor

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Reset BytesRef fields on early return

When srcPos == 0 (first block has compressedLength == 0), the method returns without
setting bytes.offset and bytes.length, leaving the BytesRef in an inconsistent state
relative to the previous implementation which fell through the assertion path.
Additionally, offsetInBytesRef may be negative here, but bytes.length should be set
to 0 (or the caller's expected empty result) to avoid returning stale length data
from a reused BytesRef.

src/main/java/org/opensearch/index/codec/customcodecs/QatCompressionMode.java [197-199]

-while (offsetInBlock < offset + length) {
-    final int compressedLength = in.readVInt();
-    if (compressedLength == 0) {
-        break;
-    }
-
-    compressed = ArrayUtil.grow(compressed, srcPos + compressedLength);
-    in.readBytes(compressed, srcPos, compressedLength);
-    srcPos += compressedLength;
-    totalDecompressed += Math.min(blockLength, originalLength - offsetInBlock);
-    offsetInBlock += blockLength;
-}
-
 if (srcPos == 0) {
+    bytes.offset = 0;
+    bytes.length = 0;
     return;
 }
 
-bytes.bytes = ArrayUtil.grow(bytes.bytes, totalDecompressed);
-
-// Single JNI call: native side loops through all concatenated
-// compressed frames, decompressing them into the output buffer.
-int totalWritten = qatZipper.decompressFull(compressed, 0, srcPos, bytes.bytes, 0, totalDecompressed);
-assert totalWritten == totalDecompressed : "Decompressed byte count ("
-    + totalWritten
-    + ") does not match expected ("
-    + totalDecompressed
-    + ").";
-
-bytes.offset = offsetInBytesRef;
-bytes.length = length;
-
Suggestion importance[1-10]: 6

__

Why: Valid concern about leaving BytesRef in a potentially inconsistent state on early return, though the original code had similar behavior. Minor correctness improvement.

Low
General
Avoid zero-sized initial buffer allocation

originalLength / 2 can be 0 when originalLength <= 1, which causes
ArrayUtil.growNoCopy to allocate a minimal buffer and forces immediate regrowth. Use
at least a small minimum (e.g., Math.max(originalLength / 2, 16)) to avoid
unnecessary reallocations when decompressing tiny ranges.

src/main/java/org/opensearch/index/codec/customcodecs/QatCompressionMode.java [180]

-compressed = ArrayUtil.growNoCopy(compressed, originalLength / 2);
+compressed = ArrayUtil.growNoCopy(compressed, Math.max(originalLength / 2, 16));
 int srcPos = 0;
 int totalDecompressed = 0;
Suggestion importance[1-10]: 3

__

Why: Minor optimization to avoid tiny initial allocations. ArrayUtil.growNoCopy typically handles small sizes reasonably, so impact is limited.

Low

@reta
reta merged commit 9f88c9c into opensearch-project:main Jul 18, 2026
20 of 21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-diff-analyzer Maintainer to skip code-diff-analyzer check, after reviewing issues in AI analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants