cTAKES USDHUBClinician documentation
Startup and capacityEnter to completed receiptsPhase timings retained

Why cTAKES can look idle before the first note

A cTAKES JVM loads a terminology database, analysis engines, and statistical models before it can finish the first document. The wrapper separates that startup work from note processing and refuses a resource plan that does not fit the job boundary.

01

The run has visible phases before, during, and after note processing

The wrapper validates the batch and runtime, plans capacity, validates the dictionary and WSD model, starts the JVM and models, processes notes, drains writers, exports files, and completes receipts.
Figure 1. Startup ends when the first note completes, not when the shell launches Java.
PhaseWhat the user may seeWhat is happening
Wrapper preflightFast checks and printed capacity lines.Filename, path, versions, hashes, dictionary, WSD, CPU, memory, commit, and disk are checked.
JVM initializationJava starts; no completed note yet.Classpath and UIMA initialize; dictionary connections and Apache models load.
First noteThe first START/COMPLETE event pair.The fully initialized pipeline processes and commits one note.
Steady note windowRepeated progress updates and changing ETA.Notes are processed through the already-loaded engines and writers.
Java drainNote count is complete but Java is still running.Analysis engines and writers close, flush, and release their runtime resources.
Post-Java finalizationJava has exited but the wrapper command has not returned.CSV export, XMI provenance, sanity read-back, storage/timing receipts, and final manifest status are completed.
02

Preflight finishes non-mutating checks before known outputs are cleaned

  1. Resolve the input, output, work, and optional SQLite paths to canonical locations.
  2. Reject any input/output/work nesting or SQLite target inside the input tree.
  3. Walk the complete input batch and enforce strict USDHUB filenames and unique normalized document identities.
  4. Locate Java 17, cTAKES 6.0.0, compiled wrapper helpers, and the pinned SQLite JDBC jar.
  5. Resolve the selected dictionary descriptor and strict manifest.
  6. Hash the descriptor and dictionary database, then compare release/UMLS/hash fields with the manifest.
  7. Resolve and validate the default WSD model and build receipt against that dictionary.
  8. Read CPU, memory, commit, and disk availability and calculate the write/heap/thread plan.
  9. Only after those checks pass, replace known prior wrapper outputs or create the new run workspace.

Read the runner gate: scripts/run_pipeline.sh and the reusable capacity probes in scripts/system_capacity.sh.

03

The JVM has to assemble the exact extraction system before it processes text

The wrapper creates run_pipeline.effective.piper by taking the reviewed recipe and inserting the selected writers and WSD profile. It also creates a temporary dictionary descriptor whose database path points to the protected runtime copy. Those two files are audit copies of what the JVM was asked to use.

Initialization workWhy it can be expensive
Java/UIMA class loadingcTAKES is a large modular Java application with many analysis engine and type-system classes.
Dictionary openThe HSQL database and lookup tables/indexes must be opened before phrase matching.
POS/chunk/assertion modelsStatistical model resources are loaded once per JVM.
Drug NERThe legacy component has shared state and is synchronized by the wrapper.
Relation modelsDegree and location models add another initialization and inference stage.
WSD model openThe validated 116 MB SQLite model opens and checks its schema/completion/profile table.

Adding more threads does not make every one of these steps parallel. It can increase heap and CAS pressure while synchronized Drug NER remains one-at-a-time.

04

The timing receipt separates work that earlier logs blended together

TimingStartEndInterpretation
Wrapper wallRunner beginsRunner exits after final receiptsEverything the user waited for, including preflight and post-run validation.
Engine wallJava process startsJava process exitsJVM initialization, note processing, and engine/writer shutdown.
Startup to first noteJava process startsFirst COMPLETE eventModel/dictionary initialization plus the first document.
Note windowFirst START eventLast COMPLETE eventThe observed document-processing interval after startup begins producing note events.
DrainLast COMPLETE eventJava exitsWriter/engine shutdown after the final note.
FinalizationJava exitsManifest becomes complete or failedMerge/export/XMI/sanity/storage/timing and final status work.

The receipt uses monotonic clocks for elapsed intervals, so a wall-clock adjustment cannot make a phase negative or move an event backward.

Current wrapper 2.1 correctness receipts

Current fixtureWrapperEngineStartup to first noteNote windowResult
Direct SQLite, WSD default on65.982 s43.201 s38.678 s3.930 s139 concepts / 14 medications / 13 abstentions / sanity pass
Direct standard with XMI, WSD default on34.861 s28.233 s24.274 s3.490 s139 concepts / 14 medications / XMI and sanity pass
Direct SQLite, explicit WSD off62.429 s55.588 s49.650 s5.257 sSame 139 concepts / 14 medications / blank WSD fields / sanity pass
Two-shard async, WSD default on60.234 s57.269 s42.751 s12.644 s754 concepts / 91 medications / 30 abstentions / sanity pass

These are literal receipts from completed wrapper 2.1 correctness runs on a busy host. They are not a benchmark and they do not prove that WSD improves speed: cache state, host load, helper compilation, and filesystem activity differed between executions. A site should measure 25–100 representative notes with the intended writer and WSD profile, alternate cold and warm starts, and size from the slower end of the observed trial distribution.

Read the timing derivation: scripts/write_timing_report.py.

05

WSD startup reuses one completed validation without skipping model checks

The wrapper hashes the model, verifies its build receipt and dictionary binding, opens SQLite, and runs quick_check before Java starts. It then passes three values into Java: wrapper validation is complete, the validated real model path, and the complete model SHA-256.

The annotator reuses that completed validation only when its resolved path and expected hash match. It still checks the schema, build-complete marker, and non-empty profile table. Standalone or mismatched use performs the full validation path.

BoundaryWhat must be true
Before JavaModel hash, receipt, dictionary binding, SQLite integrity, schema, completion marker, and profile table all pass.
Validation reuseThe wrapper-validation flag, resolved model path, and full SHA-256 agree with the model Java opens.
Inside JavaSchema, completion marker, and non-empty profiles are checked again before scoring starts.
Any mismatchThe full validation path runs. The annotator does not trust a partial or path-only signal.

This avoids repeating the same full database scan during one startup. It is a startup mechanism, not a claim that WSD makes note processing faster.

Read the WSD scoring and startup boundary, including why this shortcut cannot bypass path/hash matching.

06

CPU, memory, and Linux commit are all separate limits

ProbeWhat it prevents
CPU affinityUsing cores the process is not allowed to schedule on.
cgroup CPU quotaTreating a container with a two-core quota as the full host.
MemAvailablePlanning from total installed RAM while other processes already use it.
cgroup memory remainingCrossing a container/job memory ceiling even when the host has free RAM.
CommitLimit - Committed_ASRequesting virtual memory the kernel cannot commit.
Aggregate async heapGiving each shard a heap that fits alone but not when all JVMs run together.

Automatic heap selection uses the smaller safe memory/commit budget, keeps a 512 MiB planning gap, applies the configured percentage, and caps the result. The initial heap is one quarter of maximum with a 512 MiB floor and 1 GiB cap, so Java does not commit the full maximum at launch.

bash scripts/run_pipeline.sh \
  --input /approved/usdhub/batch \
  --output /approved/usdhub/results \
  --xmx 4096

An explicit heap still has to pass current RAM/commit hard guards. --no-autoscale disables automatic selection, not those safety checks.

07

Storage admission changes with the selected writer

Writer profileSource-byte factorPersistent output
sqlite24×Canonical SQLite + concepts/drugs CSV + receipts
csv96×Compatibility name for the canonical no-XMI delivery
standard256×SQLite + CSV + full native XMI
lean48×Diagnostic per-note output, not a USDHUB delivery
none16×Pipeline diagnosis with delivery writers disabled

The factor is an admission estimate, not an output guarantee. A 256 MiB output floor, separate SQLite/work checks, 2 GiB per concurrent async JVM, and a 10 GiB free-space reserve are applied where relevant.

When SQLite, output, work, or async shards use separate capacity checks
  • A custom SQLite filesystem gets its own estimate with a 128 MiB floor. For 100 MiB of notes, that is 800 + 10,240 = 11,040 MiB free on the SQLite target.
  • An output filesystem separate from work must pass its output estimate plus reserve: 2,400 + 10,240 = 12,640 MiB in this example.
  • A work filesystem separate from output must pass work plus reserve: 2,048 + 10,240 = 12,288 MiB.
  • Async admission adds 2,048 MiB for every concurrent shard/JVM. For two shards and the same 100 MiB batch, the aggregate estimate is 2,400 + (2 × 2,048) + 10,240 = 16,736 MiB on the checked shared target.

storage_report.json records the persistent bytes and observed work/shard footprint at measurement time. It does not replace infrastructure monitoring for true peak disk, peak RSS, I/O latency, or cgroup throttling.

Read the admission formulas in scripts/run_pipeline.sh and scripts/run_async.sh; read observed post-run categories in scripts/analyze_storage.py.

08

Async helps only when the notes can amortize another JVM startup

The planner starts from at least 25 notes per JVM, then caps shard count by available documents, CPU, aggregate heap, and storage. Two shards mean two dictionaries and two sets of models in memory.

  1. Validate the whole batch once and calculate one accepted identity set.
  2. Partition unique notes across shards.
  3. Give each child a unique run_id while requiring matching release and analysis inputs.
  4. Run each child into a separate SQLite database and retained log tree.
  5. Merge child databases into a new aggregate database inside a transaction.
  6. Regenerate aggregate CSV, XMI provenance, manifest, sanity, storage, and timing receipts.
  7. Delete temporary shards only after all aggregate checks pass. Preserve them on any child, merge, export, provenance, sanity, or finalization failure.
bash scripts/run_async.sh \
  --input /approved/usdhub/batch \
  --output /approved/usdhub/results \
  --flatten-output
09

Use the retained events to tell slow startup from a stopped run

tail -100 /approved/usdhub/results/run_pipeline.log
python3 -m json.tool /approved/usdhub/results/timing_report.json
python3 -m json.tool /approved/usdhub/results/run_manifest.json
python3 -m json.tool /approved/usdhub/results/sanity_report.json
ObservationRead next
No Java process and manifest failed before engine launchFilename, dictionary, WSD, capacity, JDBC, or path error printed by preflight.
Java exists but no first START/COMPLETE pairEngine startup log, dictionary/model initialization, heap/commit, and current host I/O.
START without matching COMPLETEThe exception or last progress event for that note; preserve the failed output.
All notes complete but command continuesDrain/finalization phase: writer shutdown, export, XMI receipt, sanity, storage, or timing.
Async failedEach retained child run.log, child manifest, and child sanity receipt under the preserved shard tree.

Resume is restricted because a partial canonical SQLite run cannot satisfy a full-batch run scope. A clean rerun uses a new or intentionally cleared output folder. Any resume must keep input, writer, dictionary, recipe, WSD profile, and release identity unchanged.