Skip to content

Configurable input-size safeguards for the default spaCy analyzer #2446

Open

Description

@suhaskaranth3

Summary

Could Presidio provide a configurable input-size safeguard for the default spaCy-backed analyzer, or document a supported bounded-processing configuration for memory-constrained deployments?

This is a request about predictable resource usage, not a claim that all inputs over 100,000 characters fail.

Problem and evidence

Our deployment notes record Presidio analyzer 2.2.362 pods being OOMKilled (exit 137) under a 2Gi memory limit after repeated requests. We do not have a complete reproduction trace, exact payload lengths, or peak-memory measurements to attach. A subsequent configuration reduces Gunicorn concurrency to one worker; we have not demonstrated that this makes large or repeated requests safe.

Source inspection of the latest release, 2.2.366, shows:

  • The stock Dockerfile still selects conf/default.yaml, which configures spacy with en_core_web_lg.
  • SpacyNlpEngine is unchanged from 2.2.362: process_text() invokes the entire pipeline on the supplied text, and process_batch() processes each entire document through nlp.pipe().
  • /analyze has no explicit application-level text-length/body-size safeguard or automatic per-document splitting before NLP processing.
  • Selecting only CREDIT_CARD, US_SSN, IBAN_CODE, EMAIL_ADDRESS, and PHONE_NUMBER does not bypass the default NLP preprocessing.

spaCy's default max_length of 1,000,000 characters is not a memory-budget guarantee. Its E088 message warns that parser/NER processing can require roughly 1GB of temporary memory per 100,000 characters; this is guidance, not a universal failure threshold.

Suggested investigation

We have not executed this sample against 2.2.366. It is a candidate workload for an isolated, memory-limited test environment, not a verified reproducer. Do not run it against a shared or production analyzer.

from presidio_analyzer import AnalyzerEngine

analyzer = AnalyzerEngine()
entities = ["CREDIT_CARD", "US_SSN", "IBAN_CODE", "EMAIL_ADDRESS", "PHONE_NUMBER"]
seed = "Please contact [email protected] about the order status. "

for size in (100_000, 200_000, 500_000):
    text = (seed * (size // len(seed) + 1))[:size]
    for _ in range(3):
        print(f"Analyzing {len(text)} characters", flush=True)
        analyzer.analyze(text=text, language="en", entities=entities)

For the HTTP path, use a dedicated container with one Gunicorn worker and a 2Gi memory limit, exercise equivalent payloads through /analyze, and record peak RSS/cgroup memory, exit status, and whether memory stabilizes between sequential requests. The sample cannot predict the OOM threshold; tokenization and text shape matter.

Expected behavior / requested improvement

An explicitly configured safety boundary should reject an oversized input before costly NLP processing, with a documented error and no silent truncation or skipped PII inspection. A character cap and an HTTP body cap address different stages and could be documented separately.

If caller-side overlapping chunking remains the recommended solution, please document a supported configuration and its limitations: entity offsets, boundary-spanning entities, throughput, and the fact that a safe chunk size depends on the model and available memory.

Please also clarify the supported configuration for structural-only recognizers using NoOpNlpEngine, including the loss of text-derived context enhancement and resulting score-threshold implications.

Existing work and related issues

We recognize that SlimSpacyNlpEngine and NoOpNlpEngine are available in newer releases. They are optional alternatives, not a change to the stock spaCy path. We are not requesting that Presidio automatically disable NLP based on an entity list.

Source references: spaCy engine, default NLP configuration, HTTP analyzer, Dockerfile.

Activity

  1. valbarov commented on Nov 10, 2026

    @valbarov
    Contributor

    I'd like to work on a first, narrow part of this: an opt-in limit on text length.

    Proposal:

    • AnalyzerEngine(max_text_length=None). The default keeps current behavior.
    • A text longer than the limit raises TextTooLongError (a ValueError) before the NLP engine runs. No truncation, so nothing is skipped silently.
    • BatchAnalyzerEngine checks each value before NLP processing. One oversized text fails the call.
    • max_text_length as a key in the analyzer YAML, validated at load time.
    • /analyze returns 413 with a clear message instead of the generic 500.

    Out of scope for this PR: automatic chunking in the spaCy path, an HTTP body-size cap, and the NoOpNlpEngine docs. Those can follow separately.

    Two questions before I open the PR:

    1. Is 413 the right status, or do you prefer 400?
    2. Is the limit better on AnalyzerEngine, or on the NLP engine config?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions