Skip to content

Commit 75e59ee

Browse files
committed
TIKA-4809: Document numClients's dual role, endpoint groups, and backpressure
1 parent 7ced454 commit 75e59ee

3 files changed

Lines changed: 145 additions & 4 deletions

File tree

docs/modules/ROOT/pages/migration-to-4x/migrating-tika-server-4x.adoc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,24 @@ All tika-server configurations must now include a `pipes` section and a `file-sy
182182
}
183183
----
184184
185+
[IMPORTANT]
186+
====
187+
`numClients` is not boilerplate to copy unchanged from this example. In 3.x,
188+
`/tika`, `/rmeta`, and `/unpack` parsed in-process, in the request-handling
189+
JVM — no forked processes, no fixed concurrency limit. In 4.x, these same
190+
default-on endpoints *always* fork `numClients` child JVMs and share a fixed
191+
pool of that many concurrent workers. Size it too low for your request volume
192+
and callers start getting `429 CLIENT_UNAVAILABLE_WITHIN_MS` under load that
193+
used to just queue up on request threads instead (this is deliberate
194+
backpressure, not a bug — see
195+
xref:using-tika/server/index.adoc#_endpoints_and_forked_process_groups[Endpoints
196+
and Forked-Process Groups]). Size it too high for your host's core count, and
197+
the forked workers individually starve each other of CPU (see
198+
xref:pipes/cpu-sizing.adoc[Forked-JVM CPU Sizing]). Neither failure mode
199+
throws an error pointing at `numClients` as the cause — size it deliberately
200+
before deploying, not from this example.
201+
====
202+
185203
== New Features
186204
187205
=== Process Isolation

docs/modules/ROOT/pages/pipes/cpu-sizing.adoc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,37 @@ Two `WARN`-level messages call out clearly-bad provisioning:
112112

113113
`grep pipes-cpu-sizing` on the parent's logs surfaces all sizing-related output.
114114

115+
[#_known_limitation_multiple_pipes_groups_in_one_process]
116+
== Known limitation: multiple Pipes groups in one process
117+
118+
Everything above describes sizing for *one* `PipesParser` — one `pipes`
119+
config section, one set of forked workers. The auto-sizer has no visibility
120+
into anything else running in the same JVM.
121+
122+
This matters concretely for tika-server: `/tika`+`/rmeta`+`/unpack` and
123+
`/pipes`+`/async` are backed by two *independent* `PipesParser` groups when
124+
both are enabled in the same server. Each group's auto-sizer computes its
125+
slice from `Runtime.availableProcessors()` as if it were the only consumer on
126+
the host — it does not know a sibling group in the same process is about to
127+
fork its own `numClients` workers too. The result: with `numClients=2` on
128+
both, you get 4 total forked JVMs, each capped assuming exclusive access to
129+
the whole host. Whether that's *actually* oversubscribed depends on your
130+
host's real core count relative to those combined `numClients` values — it's
131+
not automatic, but the auto-sizer also won't warn you, because each group
132+
looks correctly sized from its own perspective alone. See
133+
xref:using-tika/server/index.adoc#_endpoints_and_forked_process_groups[Endpoints
134+
and Forked-Process Groups] for the tika-server-specific guidance.
135+
136+
The same applies to any application embedding `PipesForkParser`/`PipesParser`
137+
directly and constructing more than one instance in a single JVM — the
138+
auto-sizer will size each independently, with the same caveat.
139+
140+
There is no automatic fix for this today: unlike the single-group case, where
141+
Tika detects and warns about bad provisioning, a *second* group has no way to
142+
learn what a sibling group already claimed. Mitigate it explicitly — either
143+
run only one group per process, or set `-XX:ActiveProcessorCount` yourself
144+
(next section) with the combined total in mind.
145+
115146
== Disabling or overriding
116147

117148
If you want to manage `ActiveProcessorCount` yourself (e.g., to allocate a

docs/modules/ROOT/pages/using-tika/server/index.adoc

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,29 @@ request-handling JVM; treat those as best-effort under load. See
3333
xref:migration-to-4x/migrating-tika-server-4x.adoc[Migrating Tika Server to 4.x]
3434
for the full breaking-change list when upgrading from 3.x.
3535

36+
[IMPORTANT]
37+
====
38+
This is not opt-in the way `/pipes` and `/async` are (those require
39+
`allowPipes=true` and refuse to start without it). `/tika`, `/rmeta`, and
40+
`/unpack` are **on by default** — the moment you run a basic `tika-server` and
41+
PUT a document to `/tika`, you are running Tika Pipes, with a real forked
42+
child process behind it. (`/meta` is the exception among the main
43+
content-extraction endpoints — it still parses in-process; see below.) If
44+
you're upgrading from 3.x, where these endpoints parsed in-process in a
45+
single JVM, this is a profound change: `pipes.numClients` now controls both
46+
how many requests these endpoints can serve concurrently and how many forked
47+
JVMs run at once, and it's easy to size it thinking about only one of those
48+
two things. Undersized for your request volume, and callers start waiting —
49+
then failing with `429`s — under load that used to just queue up on request
50+
threads in 3.x. Oversized for your host's core count, and the forked workers
51+
individually starve each other of CPU. Neither shows up as an error in your
52+
own code; both show up as "the server got slower" with nothing pointing at
53+
`numClients` as the cause. See
54+
<<_endpoints_and_forked_process_groups,Endpoints and Forked-Process Groups>>
55+
below and xref:pipes/cpu-sizing.adoc[Forked-JVM CPU Sizing] before deploying —
56+
don't treat the `numClients` value in example configs as safe boilerplate.
57+
====
58+
3659
== Security
3760

3861
IMPORTANT: The primary rule is *trusted callers only*. `tika-server` is not a security boundary:
@@ -196,10 +219,17 @@ a `message` field is also included (it often contains a server-side stack trace)
196219
|HTTP status |`status` values |Meaning
197220

198221
|`503 Service Unavailable`
199-
|`TIMEOUT`, `OOM`, `UNSPECIFIED_CRASH`, `CLIENT_UNAVAILABLE_WITHIN_MS`
200-
|The forked parse process failed, or no parse client became available within the
201-
configured wait time (`CLIENT_UNAVAILABLE_WITHIN_MS`). The server is still healthy;
202-
the client may retry.
222+
|`TIMEOUT`, `OOM`, `UNSPECIFIED_CRASH`
223+
|The forked parse process actually failed (crashed, OOM'd, or exceeded its timeout).
224+
The server is still healthy; the client may retry.
225+
226+
|`429 Too Many Requests`
227+
|`CLIENT_UNAVAILABLE_WITHIN_MS`
228+
|Nothing failed — no parse client became available within the configured wait time
229+
(deliberate backpressure, not a bug; see <<_endpoints_and_forked_process_groups,Endpoints
230+
and Forked-Process Groups>>). Distinct from `503` above on purpose: a `429` spike means
231+
"raise `numClients` or add capacity," a `503` spike means "something is actually
232+
crashing" — you can tell them apart from the status code alone.
203233

204234
|`500 Internal Server Error`
205235
|`FAILED_TO_INITIALIZE`, `FETCH_EXCEPTION`, `EMIT_EXCEPTION`,
@@ -228,6 +258,10 @@ Server behavior beyond host/port is controlled by a JSON config file passed via
228258
|`false`
229259
|Opt-in for the `/pipes` and `/async` endpoints, which drive process-isolated fetching and parsing. The server refuses to start if either is selected without this flag (see <<_security_configuration,Security Configuration>>).
230260

261+
|`endpoints`
262+
|_all defaults_
263+
|Which endpoints to expose. Leave unset to get the full default set (includes `/tika` and `/rmeta`). Explicitly listing endpoints also controls how many independent forked-process groups you run — see <<_endpoints_and_forked_process_groups,Endpoints and Forked-Process Groups>> below before combining `/tika`/`/rmeta` with `/pipes`/`/async`.
264+
231265
|`allowPerRequestConfig`
232266
|`false`
233267
|Opt-in for per-request parser configuration: the `/config` family of endpoints and the multipart `config` part. When off, such requests are rejected with 403 (see <<_security_configuration,Security Configuration>>).
@@ -261,6 +295,64 @@ For the full Pipes-related sections (`pipes`, `fetchers`, `emitters`, `parse-con
261295
that tika-server 4.x requires, see
262296
xref:migration-to-4x/migrating-tika-server-4x.adoc#_configuration_changes[Configuration Changes].
263297

298+
[#_endpoints_and_forked_process_groups]
299+
== Endpoints and Forked-Process Groups
300+
301+
Two independent pipes-backed process groups exist, plus one endpoint that
302+
isn't pipes-backed at all:
303+
304+
* **`/tika` + `/rmeta` + `/unpack`** share one group — all three go through
305+
the same `PipesParsingHelper`/`PipesParser`, sized by `pipes.numClients`.
306+
* **`/pipes` + `/async`** share a separate group (gated behind `allowPipes`),
307+
sized by the same `pipes.numClients` setting in the same config, but as an
308+
independent set of forked processes.
309+
* **`/meta` is not pipes-backed** — it still parses in-process, in the
310+
request-handling JVM, as in 3.x. It isn't bound by `numClients` and doesn't
311+
participate in anything below, but it also has no crash/OOM isolation: a
312+
hostile or pathological document sent to `/meta` can affect the
313+
request-handling process itself, unlike the pipes-backed endpoints where the
314+
same document only takes down a forked child. Treat `/meta` as best-effort
315+
under adversarial input.
316+
317+
Within a pipes-backed group, `numClients` does two *separate* jobs, and it's
318+
worth understanding both before picking a value.
319+
320+
=== It bounds how many requests that group can serve at once
321+
322+
Each group holds a fixed pool of `numClients` workers. A request that arrives
323+
when all of them are busy doesn't fail immediately — it waits, up to
324+
`pipes.maxWaitForClientMillis` (default 60s), for one to free up. This is
325+
deliberate backpressure, not a bug: if a worker frees up in time, the request
326+
is served normally; if the wait times out, the server returns `429` with
327+
`status: CLIENT_UNAVAILABLE_WITHIN_MS` — an explicit "I'm at capacity, retry"
328+
signal, not a crash (see <<_error_responses,Error Responses>> above). Under
329+
3.x's in-process model there was no equivalent hard cap — requests just piled
330+
up on the HTTP server's own thread pool instead. If you're seeing
331+
`CLIENT_UNAVAILABLE_WITHIN_MS` under real load, that's this group's
332+
concurrency limit telling you it's undersized for your request volume: raise
333+
`numClients` for more concurrent capacity, or tune `maxWaitForClientMillis` to
334+
fail faster (surface backpressure to the caller sooner) or more patiently
335+
(absorb bursts, at the cost of tying up more request threads while waiting).
336+
337+
=== It sizes each forked worker's view of available CPU
338+
339+
Independently of the above, each group also auto-sizes its forked JVMs'
340+
`-XX:ActiveProcessorCount` from `numClients` and the host's core count — see
341+
xref:pipes/cpu-sizing.adoc[Forked-JVM CPU Sizing] for the full mechanics. This
342+
part *can* go wrong across groups: the auto-sizer for one group has no
343+
visibility into another group running in the same process, so if you enable
344+
both `/tika`/`/rmeta`/`/unpack` *and* `/pipes`/`/async` together — a config
345+
listing all of them, or simply leaving `endpoints` unset while
346+
`allowPipes=true` — each group's auto-sizer computes its slice as if it owned
347+
the whole host. Whether that actually causes oversubscription depends on your
348+
`numClients` values relative to the host's core count; it's not automatic, but
349+
it's also not something the auto-sizer will warn you about, because from
350+
either group's perspective alone the sizing looks fine. See
351+
xref:pipes/cpu-sizing.adoc#_known_limitation_multiple_pipes_groups_in_one_process[Known
352+
limitation: multiple Pipes groups in one process] for the mechanics and
353+
mitigation (scope `endpoints` to what you actually use, or set
354+
`-XX:ActiveProcessorCount` explicitly with the combined total in mind).
355+
264356
== Topics
265357

266358
* xref:using-tika/server/tls.adoc[TLS/SSL Configuration] — Secure your server with TLS and mutual authentication

0 commit comments

Comments
 (0)