https://bugs.koha-community.org/bugzilla3/show_bug.cgi?id=43569

            Bug ID: 43569
           Summary: update_index masks per-document ES failures; reindex
                    reports silent success
   Initiative type: ---
        Sponsorship ---
            status:
           Product: Koha
           Version: Main
          Hardware: All
                OS: All
            Status: NEW
          Severity: normal
          Priority: P5 - low
         Component: Searching - Elasticsearch
          Assignee: [email protected]
          Reporter: [email protected]
        QA Contact: [email protected]
  Target Milestone: ---

Elasticsearch indexing can silently drop records: update_index() masks
per-document bulk failures, and rebuild_elasticsearch.pl reports success even
when records fail to index.

This is a follow-up to bug 42669, which fixed the es_indexer_daemon.pl silent
job failure and NoNodes recovery. That fix only handles thrown exceptions
(whole-operation failures). It does not address the case where the ES bulk
request returns HTTP 200 but individual documents fail (mapping conflicts,
strict_dynamic_mapping_exception, field-too-long, etc.). Those per-item
failures are currently swallowed.

== Current behavior ==

Koha::SearchEngine::Elasticsearch::Indexer::update_index():
  - On a bulk response with $response->{errors} true, it only carp()s
    "One or more ElasticSearch errors occurred when indexing documents" and
    returns the raw response as if successful.
  - Callers cannot easily tell which records failed or act on them.

misc/workers/es_indexer_daemon.pl:
  - Ignores the update_index() return value entirely; relies only on
    try/catch for exceptions. A partial bulk failure leaves the batch marked
    'finished', so the affected records are silently missing from search.

misc/search_tools/rebuild_elasticsearch.pl:
  - The buffered commit is wrapped in try/catch that only logs and continues;
    dropped records are never retried.
  - The final commit (uncommitted tail) is NOT wrapped, so under 'use autodie'
    an exception there kills the process mid-slice.
  - _handle_response() prints per-item error detail only at verbosity level 2,
    and even the summary line is suppressed at the default verbosity used by
    cron. Reindex cronjobs therefore report success while records fail.
  - Exit status is always 0, so cron wrappers cannot detect failures.
  - With --processes, child exit status is not propagated (wait() ignores $?),
    so a failed slice is invisible to the parent.

== Proposed change (two-phase implementation on this bug) ==

Phase 1: Surface per-document bulk failures.
  - update_index() returns a Koha::Result::Boolean instead of the raw ES
    response. False when any bulk item errored; one message per failed record
    via add_message({ type => 'error', message => reason,
    payload => { record_id => id, error => ... } }).
  - Empty-body case returns a true Boolean (removes the current undef return
    and the undef-deref hazard in callers).
  - Whole-operation failures (bulk call throws, NoNodes) keep throwing
    Koha::Exceptions::Elasticsearch::BadResponse, so the daemon NoNodes reset
    path from bug 42669 stays intact. Exceptions mean "could not run";
    the Boolean means "ran, some documents failed".
  - Migrate callers:
      * es_indexer_daemon.pl: inspect the Boolean; on false set index_ok = 0
        so the batch is marked 'failed' instead of 'finished'. NOTE: this makes
        previously-hidden partial failures visible as failed jobs (intended).
      * rebuild_elasticsearch.pl: minimal migration so _handle_response does
        not break on the new return type (full hardening is Phase 2).
      * index_records() and bulkmarcimport.pl currently ignore the return;
        left as-is in Phase 1, noted as optional future work.
  - Tests: update t/db_dependent/Koha/SearchEngine/Elasticsearch/Indexer.t
    (the existing update_index assertion) and add all-success,
    partial-failure, empty-body, and whole-op-throws cases.

Phase 2: Harden rebuild_elasticsearch.pl (depends on Phase 1).
  - Track failed/skipped counts; exit non-zero on any failure so cron wrappers
    can detect trouble. (Visible behavior change for existing automation.)
  - Wrap the final commit in the same try/catch as the buffered one, honoring
    the NoNodes-vs-real-error distinction so a hard outage is not masked as a
    soft warning under autodie.
  - Propagate child exit status in the --processes path (children exit non-zero
    on failure; parent aggregates $? from wait()).
  - Report fetched vs indexed vs failed vs skipped at the end; the current
    "Total N records indexed" line overstates success.

== Out of scope ==

Standardizing logging across the ES indexing stack (Carp vs Koha::Logger
alignment) will be handled in a separate report.

== Open questions for QA ==

  - Should index_records() aggregate and return a combined Boolean, or stay
    fire-and-forget for now?
  - Does the daemon 'finished' -> 'failed' semantic shift for partial failures
    warrant a release note?

-- 
You are receiving this mail because:
You are the assignee for the bug.
You are watching all bug changes.
_______________________________________________
Koha-bugs mailing list -- [email protected]
To unsubscribe send an email to [email protected]
website : http://www.koha-community.org/
git : http://git.koha-community.org/
bugs : http://bugs.koha-community.org/

Reply via email to