📄 Database documents via OCR
Template: nifi/user_templates/opensearch_ingest_docs_db_ocr_service_to_es.json
View the flow definition on GitHub
🎯 Purpose
This flow reads binary documents from PostgreSQL, sends one document at a time to the CogStack OCR service, merges the extracted text with the source metadata, and indexes the result in OpenSearch.
🔗 Processor chain
GenerateTableFetch-encounters
-> ExecuteSQLRecord
-> CogStackPrepareRecordForOcr
-> InvokeHTTP
-> CogStackParseCogStackServiceResult
-> SplitJson
-> EvaluateJsonPath
-> PutElasticsearchJson
CogStackPrepareRecordForOcr converts the Avro binary field to a base64 JSON request
and preserves the remaining source fields in a footer. After OCR, the parser restores
that footer, adds the extracted text, and exposes id as a FlowFile attribute for
OpenSearch indexing.
📋 Bundled defaults
| Setting | Value |
|---|---|
| Database URL | jdbc:postgresql://samples-db:5432/db_samples |
| Source table | encounters |
| Incremental/partition column | cid |
| Partition size | 1000 |
| Rows per FlowFile | 1 |
| Binary source field | binarydocument |
| Document ID field | id |
| OCR endpoint | http://ocr-service:8090/api/process |
| Extracted text field | text |
| OpenSearch endpoint | https://elasticsearch-1:9200 |
| Target index | ${generatetablefetch.tableName} (encounters by default) |
| Index operation | index |
| OpenSearch identifier attribute | id |
✅ Requirements
- The core stack and sample database are running.
- The OCR service is running. From the repository root use:
make -C deploy start-ocr-services
- The source table contains a numeric incremental column, a unique document ID, and a binary document column.
- NiFi can resolve
ocr-serviceon the Docker network, or theInvokeHTTPURL is changed to a reachable endpoint. - Database, OpenSearch, keystore, and truststore passwords are entered after import.
📥 Import the flow
- Open NiFi at
https://localhost:8443and uploadnifi/user_templates/opensearch_ingest_docs_db_ocr_service_to_es.jsonas a process group. - Enter
opensearch_ingest_docs_db_ocr_service_to_esand keep it stopped. - Right-click an empty area, select Configure, and open Controller Services.
🎛️ Configure the controller services
| Controller service | Used by |
|---|---|
DBCPConnectionPool |
GenerateTableFetch-encounters, ExecuteSQLRecord |
AvroRecordSetWriter |
ExecuteSQLRecord |
StandardSSLContextService |
ElasticSearchClientServiceImpl |
ElasticSearchClientServiceImpl |
PutElasticsearchJson |
1. DBCPConnectionPool
| Property | Bundled value |
|---|---|
| Database Connection URL | jdbc:postgresql://samples-db:5432/db_samples |
| Database Driver Class Name | org.postgresql.Driver |
| Database Driver Locations | /opt/nifi/drivers/postgresql-42.7.7.jar |
| Database User | Value of POSTGRES_USER_SAMPLES, test by default |
| Password | Value of POSTGRES_PASSWORD_SAMPLES |
| Password Source | PASSWORD |
| Max Total Connections | 8 |
| Maximum Idle Connections | 8 |
| Minimum Idle Connections | 0 |
| Max Wait Time | 500 millis |
Read the credentials from security/env/users_database.env. When using another source,
change the URL, driver class, driver JAR, and credentials together. Apply and enable the
service.
2. AvroRecordSetWriter
| Property | Value |
|---|---|
| Schema Access Strategy | inherit-record-schema |
| Schema Write Strategy | avro-embedded |
| Compression Format | NONE |
| Encoder Pool Size | 32 |
| Cache Size | 1000 |
This writer supplies the embedded schema consumed by CogStackPrepareRecordForOcr.
Apply and enable it.
3. StandardSSLContextService
| Property | Bundled value |
|---|---|
| Keystore Filename | /security/certificates/elastic/opensearch/elasticsearch/elasticsearch-1/elasticsearch-1-keystore.jks |
| Keystore Password | Value of ES_CERTIFICATE_PASSWORD |
| Key Password | Value of ES_CERTIFICATE_PASSWORD |
| Keystore Type | JKS |
| Truststore Filename | /security/certificates/elastic/opensearch/elasticsearch/elasticsearch-1/elasticsearch-1-truststore.key |
| Truststore Password | Value of ES_CERTIFICATE_PASSWORD |
| Truststore Type | JKS |
| TLS Protocol | TLS |
Read the password from security/env/certificates_elasticsearch.env. Confirm that the
paths and store types match the files mounted into NiFi, then apply and enable the
service.
4. ElasticSearchClientServiceImpl
| Property | Bundled value |
|---|---|
| HTTP Hosts | https://elasticsearch-1:9200 |
| Authorization Scheme | BASIC |
| Username | Value of ELASTIC_USER, normally admin for OpenSearch |
| Password | Value of ELASTIC_PASSWORD |
| SSL Context Service | StandardSSLContextService |
| Connect Timeout | 5000 |
| Read Timeout | 60000 |
| Character Set | UTF-8 |
| Enable Compression | false |
| Sniff Cluster Nodes | false |
| Sniff on Failure | false |
| Suppress Null and Empty Values | always-suppress |
Read credentials from security/env/users_elasticsearch.env. Apply and enable this
service only after the SSL context is enabled.
⚙️ Configure each processor
Return to the process-group canvas and configure the processors in flow order.
1. GenerateTableFetch-encounters
| Property | Bundled value | Guidance |
|---|---|---|
| Database Connection Pooling Service | DBCPConnectionPool |
Must be enabled |
| Database Type | PostgreSQL |
Match the source database |
| Table Name | encounters |
Change for another source table |
| Maximum-value Columns | cid |
Numeric, indexed, monotonically increasing column |
| Column for Value Partitioning | cid |
Usually the same indexed column |
| Partition Size | 1000 |
Reduce for an initial OCR test |
| Output Empty FlowFile on Zero Results | false |
Keep |
Connect success to ExecuteSQLRecord and failure to its funnel. This processor
stores incremental state; clearing it deliberately resubmits old rows to OCR.
2. ExecuteSQLRecord
| Property | Bundled value | Guidance |
|---|---|---|
| Database Connection Pooling Service | DBCPConnectionPool |
Must be enabled |
| Record Writer | AvroRecordSetWriter |
Must be enabled |
| Max Rows Per FlowFile | 1 |
Required for one OCR request per document |
| Output Batch Size | 0 |
Keep |
| Fetch Size | 0 |
Driver default |
| Use Avro Logical Types | true |
Keep unless conversion is deliberately changed |
| Set Auto Commit | true |
Bundled default |
Leave SQL Query empty because the incoming FlowFile contains the query generated by
GenerateTableFetch. Connect success to CogStackPrepareRecordForOcr and failure
to its funnel.
3. CogStackPrepareRecordForOcr
| Property | Bundled value | Meaning |
|---|---|---|
process_flow_file_type |
avro |
Input from ExecuteSQLRecord |
binary_field_name |
binarydocument |
Source document bytes |
document_id_field_name |
id |
Stable destination ID |
output_text_field_name |
text |
Field that receives OCR text |
operation_mode |
base64 |
Encodes bytes in the JSON request |
All named fields must exist in the SQL result. Connect success to InvokeHTTP and
failure to its funnel; original is auto-terminated. The processor also sets the
request MIME type used by InvokeHTTP.
4. InvokeHTTP
| Property | Bundled value | Guidance |
|---|---|---|
| HTTP URL | http://ocr-service:8090/api/process |
Must be reachable from NiFi |
| HTTP Method | POST |
Keep |
| Request Body Enabled | true |
Required |
| Request Content-Type | ${mime.type} |
Supplied by the preparation processor |
| Connection Timeout | 15 secs |
Increase only for connection establishment issues |
| Socket Read Timeout | 30 secs |
Increase for documents that legitimately take longer |
| Socket Write Timeout | 30 secs |
Bundled default |
| Response Body Ignored | false |
Required by the parser |
| Response Generation Required | false |
Bundled default |
For the text-only OCR container, use
http://ocr-service-text-only:8090/api/process. Connect Response to
CogStackParseCogStackServiceResult; route No Retry, Retry, and Failure to the
HTTP failure funnel. Original is auto-terminated.
5. CogStackParseCogStackServiceResult
| Property | Bundled value |
|---|---|
service_message_type |
ocr |
document_id_field_name |
id |
document_text_field_name |
text |
output_text_field_name |
text |
medcat_output_mode |
not_set |
The MedCAT-prefixed properties are shared by the generic parser and are not used for a
normal OCR response. Connect success to SplitJson and failure to its funnel;
original is auto-terminated.
6. SplitJson
| Property | Bundled value |
|---|---|
| JsonPath Expression | $.* |
| Null Value Representation | empty string |
| Max String Length | 20 MB |
Connect split to EvaluateJsonPath, route failure to its funnel, and leave
original auto-terminated.
7. EvaluateJsonPath
| Property | Bundled value |
|---|---|
| Destination | flowfile-attribute |
| Return Type | auto-detect |
| Path Not Found Behavior | ignore |
Dynamic property id |
$.id |
| Max String Length | 20 MB |
This creates the id FlowFile attribute used by the OpenSearch writer. Connect
matched to PutElasticsearchJson; route unmatched and failure to their funnels.
8. PutElasticsearchJson
| Property | Bundled value | Guidance |
|---|---|---|
| Client Service | ElasticSearchClientServiceImpl |
Must be enabled |
| Index | ${generatetablefetch.tableName} |
Resolves to encounters |
| Index Operation | index |
Replaces an existing document with the same ID |
| Identifier Attribute | id |
Must match EvaluateJsonPath |
| Input Content Format | Single JSON |
Required after SplitJson |
| Max FlowFiles Per Batch | 100 |
Reduce while testing if desired |
| Max JSON Field String Length | 20 MB |
Increase only for known larger output |
| Retain Identifier Field | true |
Keeps id in _source |
| Log Error Responses | false |
Enable while diagnosing failures |
| Output Error Responses | false |
Bundled default |
The successful and original relationships are auto-terminated. Route retry to its
funnel and failure plus errors to the general OpenSearch failure funnel.
✅ Pre-start validation
Before starting, confirm that all four controller services are enabled, all eight
processors are valid, the source query returns cid, id, and binarydocument, and
the OCR URL resolves from NiFi. Start with a small partition, then inspect HTTP response
attributes, provenance, bulletins, and every failure queue before processing the full
table.
▶️ Run and verify
Start the process group with a small source range first. Confirm that OCR responses
reach CogStackParseCogStackServiceResult, that split records have an id attribute,
and that the destination index count increases:
curl --cacert security/certificates/elastic/opensearch/elastic-stack-ca.crt.pem \
-u '<user>:<password>' \
'https://localhost:9200/encounters/_count'
The default flow writes OCR-enriched records to an index with the same name as the source table. Change the target index before starting if source and enriched documents must be kept separately.
⚠️ Failure handling
HTTP failures, parse failures, SQL failures, unmatched IDs, and OpenSearch errors are
routed to separate funnel queues. Inspect the response body and NiFi bulletin before
retrying. Clearing GenerateTableFetch state causes source rows to be submitted to OCR
again and should be done only deliberately.