github-actions[bot] commented on code in PR #69175:
URL: https://github.com/apache/doris/pull/69175#discussion_r4296011403
##########
fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonColumnValue.java:
##########
@@ -291,6 +306,40 @@ public void unpackStruct(List<Integer> structFieldIndex,
List<ColumnValue> value
}
}
+ @Override
+ public FileValue getFile() {
+ Blob blob = record.getBlob(idx);
+ if (blobAsDescriptor) {
+ // Use the SDK's typed descriptor API without reading the payload.
+ BlobDescriptor descriptor = blob.toDescriptor();
+ return new FileValue(descriptor.uri(), descriptor.offset(),
+ descriptor.length() < 0 ? null : descriptor.length(),
null, null, null);
+ }
+ return new FileValue(null, null, null, null, null,
readBlobPayload(blob));
+ }
+
+ private byte[] readBlobPayload(Blob blob) {
+ if (blob instanceof BlobRef) {
+ // Managed .blob references carry their length, so reject before
opening the payload.
+ // External references can have length -1 and are checked after
reading below.
+ checkBlobSize(blob.toDescriptor().length());
+ }
+ // TODO: Use bounded streaming reads for unknown-length BLOBs so the
size limit can be
+ // enforced before toData() allocates and reads the entire payload.
+ byte[] payload = blob.toData();
Review Comment:
[P1] Bound unknown-length BLOB reads before materializing them
A descriptor produced by Spark/Flink `path_to_descriptor` has length `-1`,
so a descriptor-storage BLOB read with `blob-as-descriptor=false` passes
`checkBlobSize(-1)`. Paimon 1.4.2's
[`BlobRef.toData()`](https://github.com/apache/paimon/blob/release-1.4.2/paimon-common/src/main/java/org/apache/paimon/data/BlobRef.java#L46-L53)
then reads the entire object through an unbounded `ByteArrayOutputStream`
before the check below runs. A large external object can exhaust the shared BE
JVM despite `paimon_blob_max_size=10485760`. Use a bounded, closed
`newInputStream()` read that stops after the configured limit plus one byte and
reports the limit error before allocating the whole object; cover a persisted
unknown-length descriptor.
##########
be/src/format_v2/blob/paimon_blob_column_reader.cpp:
##########
@@ -0,0 +1,193 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#include "format_v2/blob/paimon_blob_column_reader.h"
+
+#include <algorithm>
+#include <ranges>
+#include <utility>
+
+#include "core/data_type/data_type_file.h"
+#include "core/pod_array.h"
+#include "format_v2/blob/blob_file_reader.h"
+#include "io/file_factory.h"
+#include "io/io_common.h"
+
+namespace doris::format::paimon {
+
+Status PaimonBlobColumnReader::validate_blob_files(std::vector<const
TPaimonBlobFileDesc*>* files,
+ int64_t anchor_first_row_id,
+ int64_t anchor_row_count) {
+ if (files->empty()) {
+ return Status::InvalidArgument("Cannot validate an empty managed BLOB
file group");
+ }
+ for (const auto* file : *files) {
+ if (!file->__isset.path || !file->__isset.file_size ||
!file->__isset.first_row_id ||
+ !file->__isset.row_count || file->path.empty() || file->file_size
< 5 ||
+ file->row_count <= 0 || file->first_row_id < anchor_first_row_id ||
+ file->first_row_id > anchor_first_row_id + anchor_row_count ||
+ file->row_count > anchor_first_row_id + anchor_row_count -
file->first_row_id) {
+ return Status::InvalidArgument("Invalid managed BLOB file
descriptor");
+ }
+ }
+ std::ranges::sort(*files, {}, [](const auto* file) { return
file->first_row_id; });
+ int64_t next = anchor_first_row_id;
+ for (const auto* file : *files) {
+ if (file->first_row_id != next) {
+ return Status::Corruption("BLOB column {} has overlapping or
missing row ranges",
+ file->field_id);
+ }
+ next += file->row_count;
+ }
+ if (next != anchor_first_row_id + anchor_row_count) {
+ return Status::Corruption("BLOB column {} does not cover its Parquet
anchor",
+ files->front()->field_id);
+ }
+ return Status::OK();
+}
+
+PaimonBlobColumnReader::PaimonBlobColumnReader(const ColumnDefinition& column,
+ const std::vector<const
TPaimonBlobFileDesc*>& files,
+ bool blob_as_descriptor)
+ : _blob_column_name(column.name),
+ _blob_column_type(column.type),
+ _blob_as_descriptor(blob_as_descriptor) {
+ _blob_files.reserve(files.size());
+ for (const auto* file : files) {
+ _blob_files.push_back(BlobFileState {.blob_metadata = *file,
.blob_reader = nullptr});
+ }
+}
+
+PaimonBlobColumnReader::~PaimonBlobColumnReader() = default;
+
+Status PaimonBlobColumnReader::_open_blob_file(BlobFileState* blob_file,
+ const ReadContext& context) {
+ if (blob_file->blob_reader) {
+ return Status::OK();
+ }
+ io::FileDescription description;
+ description.path = blob_file->blob_metadata.path;
+ description.file_size = blob_file->blob_metadata.file_size;
+ description.is_immutable = true;
+ description.fs_name = context.fs_name;
+ description.file_cache_admission = context.file_cache_admission;
+ auto reader = FileFactory::create_file_reader(
Review Comment:
[P2] Include companion reads in scanner I/O accounting
This raw `FileFactory` reader goes directly into `BlobFileReader`, whereas
the normal `format::FileReader` path wraps it in `TracingFileReader` to update
`io_ctx->file_reader_stats`. Passing `IOContext` to `read_at()` does not update
those stats itself. With file cache disabled, all `.blob` footer/index/payload
bytes disappear from `FileReadBytes`/calls/time, query scan-byte and resource
accounting, and scan metrics, leaving only the small Parquet anchor counted.
Wrap companion reads in the same tracing path and propagate and flush their I/O
profiles.
##########
fe/be-java-extensions/java-common/src/main/java/org/apache/doris/common/jni/vec/ColumnType.java:
##########
@@ -116,6 +105,17 @@ public ColumnType(String name, Type type, int length, int
precision, int scale)
this.length = length;
this.precision = precision;
this.scale = scale;
+ if (type == Type.FILE) {
+ // FILE has a fixed schema, even when nested inside ARRAY, MAP or
STRUCT.
+ childNames = Arrays.asList("uri", "offset", "size",
"content_type", "checksum", "inline");
+ childTypes = Arrays.asList(
+ new ColumnType("uri", Type.STRING),
+ new ColumnType("offset", Type.BIGINT),
+ new ColumnType("size", Type.BIGINT),
+ new ColumnType("content_type", Type.STRING),
+ new ColumnType("checksum", Type.STRING),
+ new ColumnType("inline", Type.VARBINARY));
Review Comment:
[P2] Release the entry buffer owned by the new FILE child
Every FILE now creates this VARBINARY child, including descriptor-only and
SQL-NULL output. Its `reserveCapacity()` allocates `16 * capacity` bytes, but
the existing `VectorColumn.close()` calls `freeVarbinaryData()`, which frees
only payload pointers, then clears `data` without freeing that array.
`JniTableReader` releases and recreates the Java table for every batch, so the
new Paimon BLOB-to-FILE path permanently leaks at least roughly 16 bytes per
row per FILE (about 1.6 GB for 100 million rows), even without inline payloads.
Free the owned VARBINARY entry array after its payloads and cover repeated FILE
batch release.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]