[
https://issues.apache.org/jira/browse/TIKA-4795?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18100952#comment-18100952
]
Davide Polato edited comment on TIKA-4795 at 8/1/26 1:07 PM:
-------------------------------------------------------------
[~tallison] [~kristian] there is a decision in here that I do not think should
be mine.
h3. The bound on the branch does not actually do anything
PARSE_BYTES_MAX_BYTES is 64 MiB and the handler rejects anything bigger with
RESOURCE_EXHAUSTED. But grpc-java's default maxInboundMessageSize is about 4
MiB, so a 10 MB request never gets as far as that check. The transport refuses
it first. The 64 MiB number is decoration.
It took me a while to notice because the tests use the in-process transport,
which ignores the inbound limit entirely. Whatever we settle on needs at least
one test against a real Netty server, otherwise we are only exercising a path
no client ever takes.
I went looking for a ceiling further down and did not find one on the way in.
For ParseBytes the document bytes reach the worker through the spool file: the
pipes request carries only the fetch key, and the worker reads the file from
disk. maxIpcPayloadBytes, default 100 MiB, bounds the FINISHED result coming
back rather than anything going in, and it is configurable in the pipes section
of tika-config.json in any case. So the transport limit is the only thing
standing between a caller and a large parse.
The hard ceiling is Integer.MAX_VALUE, since maxInboundMessageSize is an int.
[~kristian] pointed that out offline. Worth remembering that a unary message is
materialised in full in heap before the handler runs, so whatever we set is
also a bound on how much heap a single caller can ask the server for. That is
why I would not simply set it to the maximum the API allows.
h3. Why I am asking rather than picking one
maxInboundMessageSize is a per-server setting. Raising it for ParseBytes raises
it for SaveFetcher, SavePipesIterator and the rest of the management surface,
which only ever carry small JSON. That changes what the server will accept from
anyone who can reach it, and it is not the sort of thing I want to slip in
quietly inside a feature branch.
Three ways to go:
# Leave the default alone, and operators who want large ParseBytes payloads
raise maxInboundMessageSize themselves. The server ships conservative. The cost
is that ParseBytes is capped near 4 MiB out of the box, which is small for the
crawler-shaped callers this ticket is aimed at.
# Raise the server default to 100 MB. ParseBytes then works without
configuration and the code stays simple. The cost is that every other RPC now
accepts 100 MB too.
# Raise the default to 100 MB and add a ServerInterceptor that puts the limit
back near 4 MiB for every method except ParseBytes. Large payloads only where
they are meant to be. The cost is another moving part, and one more thing to
remember whenever a method is added.
I lean towards the third, and finding no ceiling downstream does not change
that. The question was never how large the number can be, it is whether raising
it should be global or per method. The second option is defensible if the view
is that a Tika gRPC server never faces untrusted callers, but that is a
judgement about how people actually deploy this, and I would rather hear it
from someone who has seen more deployments than I have.
h3. What has to happen either way
Right now the bound is validated in two places that answer with two different
gRPC statuses, RESOURCE_EXHAUSTED in the v2 handler and INVALID_ARGUMENT
further down. Only the first one is reachable today, but they should collapse
into a single check with a single answer, lined up with whatever the real
transport limit turns out to be. Then the real-transport test. And
flowControlWindow, which defaults to 1 MiB and is worth raising once messages
run to several MB, or the transfer spends round trips on window updates instead
of moving bytes.
h3. This is what is keeping the ticket open
The bound is one of the two things this ticket says it needs. The other is
cancellation. An in-flight parse is not cancelled when the client disconnects,
and with a shared PipesClient, interrupting one request disturbs the worker
that is serving the others. That needs its own ticket and its own fix, and I
have not started it.
So TIKA-4795 cannot close on what is on the branch today. Either both land, or
we agree here to split cancellation out and say so on the ticket instead of
leaving it implied.
I will put a number on the branch so the demo can move, but that is a fork
making a proof of concept work, not an answer to the above. The branch is at
https://github.com/ai-pipestream/tika/tree/TIKA-4795-parseBytes. It sits on top
of the TIKA-4766 branch from #2961, so it cannot be a pull request of its own
until that one lands.
was (Author: JIRAUSER312865):
[~tallison] [~krickert] there is a decision in here that I do not think should
be mine.
h3. The bound on the branch does not actually do anything
PARSE_BYTES_MAX_BYTES is 64 MiB and the handler rejects anything bigger with
RESOURCE_EXHAUSTED. But grpc-java's default maxInboundMessageSize is about 4
MiB, so a 10 MB request never gets as far as that check. The transport refuses
it first. The 64 MiB number is decoration.
It took me a while to notice because the tests use the in-process transport,
which ignores the inbound limit entirely. Whatever we settle on needs at least
one test against a real Netty server, otherwise we are only exercising a path
no client ever takes.
There is a second limit above us that no configuration can move. Pipes frames
are Smile-encoded, which measures at roughly 1.14x expansion on this payload
shape, and PipesMessage.MAX_PAYLOAD_BYTES is 100 MiB checked when the frame is
read. So an oversized frame does not fail politely in the server, it kills the
worker. Usable content tops out somewhere near 87 MiB no matter what the
transport is willing to accept.
h3. Why I am asking rather than picking one
maxInboundMessageSize is a per-server setting. Raising it for ParseBytes raises
it for SaveFetcher, SavePipesIterator and the rest of the management surface,
which only ever carry small JSON. That changes what the server will accept from
anyone who can reach it, and it is not the sort of thing I want to slip in
quietly inside a feature branch.
Three ways to go:
# Leave the default alone, and operators who want large ParseBytes payloads
raise maxInboundMessageSize themselves. The server ships conservative. The cost
is that ParseBytes is capped near 4 MiB out of the box, which is small for the
crawler-shaped callers this ticket is aimed at.
# Raise the server default to 100 MB. ParseBytes then works without
configuration and the code stays simple. The cost is that every other RPC now
accepts 100 MB too.
# Raise the default to 100 MB and add a ServerInterceptor that puts the limit
back near 4 MiB for every method except ParseBytes. Large payloads only where
they are meant to be. The cost is another moving part, and one more thing to
remember whenever a method is added.
I lean towards the third. It is the only one that gives ParseBytes room without
also widening what the management RPCs will swallow, and an interceptor says
the intent out loud instead of burying it in one global number. The second is
defensible if the view is that a Tika gRPC server never faces untrusted
callers, but that is a judgement about how people actually deploy this, and I
would rather hear it from someone who has seen more deployments than I have.
h3. What has to happen either way
Right now the bound is validated in two places that answer with two different
gRPC statuses, RESOURCE_EXHAUSTED in the v2 handler and INVALID_ARGUMENT
further down. Only the first one is reachable today, but they should collapse
into a single check with a single answer. The constant then needs to line up
with whatever the real transport limit turns out to be and with the worker
ceiling above it. Plus the real-transport test.
h3. This is what is keeping the ticket open
The bound is one of the two things this ticket says it needs. The other is
cancellation. Aelled when the client disconnects, and with a shared
PipesClient, interrupting one requestdisturbs the worker that is serving the
others. That needs its own ticket and its own fix, and I have not started it.
So TIKA-4795 cannot close on what is on the branch today. Either both land, or
we agree here to split cancellation out and say so on the ticket instead of
leaving it implied.
The branch is at
https://github.com/ai-pipestream/tika/tree/TIKA-4795-parseBytes. It sits on top
of the TIKA-4766 branch from #2961, so it cannot be a pull request of its own
until that one lands.
> ParseBytes: parse-only entrypoint for callers that already hold the document
> bytes
> ----------------------------------------------------------------------------------
>
> Key: TIKA-4795
> URL: https://issues.apache.org/jira/browse/TIKA-4795
> Project: Tika
> Issue Type: New Feature
> Components: tika-pipes
> Affects Versions: 4.0.0
> Reporter: Davide Polato
> Priority: Major
> Labels: grpc, pipes, protobuf
>
> h4. Goal
> Add a parse-only tika-grpc entrypoint that parses the exact bytes supplied by
> the caller, without asking Tika to fetch or re-fetch the resource.
> This was split from the typed {{Document}} work in
> [TIKA-4766|https://issues.apache.org/jira/browse/TIKA-4766] / [PR
> #2961|https://github.com/apache/tika/pull/2961], whose description states
> that "{{ParseBytes}} is deferred to its own JIRA."
> h4. Motivation
> {{FetchAndParse}} obtains content through a registered fetcher. Callers such
> as web crawlers have already acquired the resource bytes. Requiring another
> acquisition path can duplicate the transfer or parse a different
> representation from the one the caller observed because of redirects,
> cookies, authentication, robots and rate-limiting decisions, or transient
> content.
> The parse result should correspond to the exact representation captured by
> the caller.
> Apache StormCrawler is a concrete consumer. Its parsing bolts already receive
> the acquired bytes together with URL and protocol metadata. StormCrawler can
> contribute crawler requirements and input fixtures including truncated
> payloads, declared-vs-actual charset mismatches, malformed archives,
> documents containing embedded resources, HTML with {{<base>}}, and distinct
> URLs with identical content.
> h4. Proposed observable behavior
> This issue defines observable behavior, not the internal buffering, fetcher,
> or Pipes implementation.
> The request carries the exact byte sequence. It may also carry:
> * an optional opaque caller correlation id, echoed but never interpreted
> * source URI and effective URI when available, plus a base URI for link
> resolution; these are provenance metadata and must never be dereferenced by
> the service
> * resource name
> * declared media type and charset hints
> * optional declared content length and digest
> * a truncation flag
> Input must be bounded, and deadline or cancellation must terminate the
> associated parse work.
> The reply reuses the typed {{Document}} contract introduced by #2961 rather
> than defining a second parse-result model.
> The current direction discussed in #2961 is to place this on the experimental
> v2 surface so that it does not gate the 4.0.0 release.
> h4. Open design decisions
> * bounded unary versus client streaming for the request bytes
> * exact service and package placement within v2
> * which provenance fields and parsing hints are necessary
> * whether and how caller-supplied length and digest are validated
> * maximum payload size and behavior for truncated payloads
> h4. Non-goals
> This issue does not define the structured content tree or its Markdown
> projection, the embedded-document representation, extension payloads such as
> {{google.protobuf.Any}}, or downstream NLP and embedding enrichment. Those
> remain separate TIKA-4766 stages and follow-up issues.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)