jobs/rebuild_indices meldete jede Nacht Built 29 indices. Failed building 6 indices und beendete sich mit exit 1 — an allen 225 Nächten, die das Debug-Protokoll noch abdeckt (01.01.2026 bis 26.08.2026; weiter reicht es nicht zurück).
Unbemerkt, weil die Cron-Zeilen bis zum 24.08.2026 auf >/dev/null 2>&1 endeten. substance_index_atc hatte gar keine Tabelle mehr — der Index wird vor dem Neuaufbau gelöscht, und der Aufbau kam nie durch.
Drei Ursachen, die nichts miteinander zu tun haben
1. Latin-1 in searchterms.rb — vier Indizes
substance_index, substance_soundex_index, doctor_index, hospital_index
incompatible encoding regexp match (UTF-8 regexp with ISO-8859-1 string)
src/util/searchterms.rb:85:in 'String#gsub'
Der Schutz prüfte auf ASCII_8BIT und auf valid_encoding?. Bei ISO-8859-1 sagen beide „in Ordnung":
s = "Bef\xFCllung".force_encoding("ISO-8859-1")
s.encoding == Encoding::ASCII_8BIT # => false
s.valid_encoding? # => true <- in Latin-1 ist jede Bytefolge gueltig
s.gsub(/[[:punct:]]/u, "") # => Encoding::CompatibilityError
Hier muss umgerechnet werden, nicht umetikettiert — force_encoding ist nur bei ASCII_8BIT richtig, wo UTF-8-Bytes ohne Etikett liegen. Der neue to_utf8 behandelt zusätzlich eingefrorene Strings, die der alte Schutz komplett übersprang (unless term.frozen?, weil force_encoding in place mutiert hätte).
Ein fünfter Index hing am selben Ort: indication_index scheiterte an "\xC3" from ASCII-8BIT to UTF-8.
2. Ein einziges kaputtes Objekt — zwei Indizes
substance_index_atc, substance_index_sequence
undefined method 'substance' for an instance of Array
odba-1.1.9/lib/odba/stub.rb:115:in 'ODBA::Stub#method_missing'
Composition 58333159 von Dermophil MED Lippenbalsam (25790/02) hielt ein leeres Array dort, wo ein ActiveAgent hingehört. Beide Definitionen rufen .substance auf jedem Element von active_agents — ein kaputtes Element kostet also den ganzen Index, nicht bloss den einen Eintrag.
Eines von 41 801. Und es tarnt sich gut:
odba_id=58333163
is_a? ActiveAgent : true <- Stub antwortet aus der Deklaration, ohne aufzuloesen
respond_to? :substance : false <- respond_to? loest auf und sagt die Wahrheit
odba_instance : Array (leer)
Der Composition-Text nennt, was fehlte:
Quelle : balsamum peruvianum 5 mg, levomenolum 2 mg, salolum 10 mg, ...
heil : 1:Levomenolum, 2:Salolum
kaputt : 0:58333163 (Array)
jobs/repair_broken_active_agents stellt solche Elemente aus dem Text wieder her — über die Position, nicht per Regex-Rateverfahren, und es fasst nichts an, wenn die heilen Nachbarn nicht dort stehen, wo der Text sie erwartet. Balsamum Peruvianum 5 mg ist wieder da.
Eine Falle dabei: Array#delete vergleicht mit ==, das geht bei einem Stub durch ActiveAgentCommon#== und ruft .substance auf dem Gegenüber — also genau auf dem kaputten Element. Man kann es nicht per Wert löschen. object_id ist der Weg; es steht auf ODBAs no_override-Liste (stub.rb:83), equal? nicht und würde auflösen.
3. Eine Methode, die es auf der Klasse nicht gibt
index_name: 'substance_index_sequence'
origin_klass: 'ODDB::Substance'
resolve_target: 'active_sequences' # <- gibt es nur auf OddbPrevalence
init_source: 'self.active_sequences' # <- hier richtig, self ist die Anwendung
Substance hat sequences, kein active_sequences. Der Aufruf lief über SimpleLanguage#method_missing ins Leere — und zwar auch beim normalen Speichern einer Substanz, nicht nur beim Neuaufbau.
Behoben
Beide resolve_origin-Ausdrücke sind jetzt abgedichtet, damit ein einzelnes kaputtes Element nie wieder einen ganzen Index kostet.
Alle sieben bauen durch, null Fehler:
| Index |
Dauer |
substance_index_atc |
313 s |
substance_index |
86 s |
substance_index_sequence |
366 s |
substance_soundex_index |
98 s |
indication_index |
32 s |
doctor_index |
439 s |
hospital_index |
5 s |
Regressionstests: test/test_util/searchterms.rb (9 runs).
Die Lehre
Ein Job, der jede Nacht mit exit 1 endet und dessen Ausgabe niemand liest, ist genau so gut wie kein Job. Dieselbe Sorte Befund wie die drei toten Rack-Backends im August und der SL-Import, der zwölf Tage lang nichts tat und exit 0 meldete. Seit dem 24.08. schreibt bin/oddb_cron nach log/cron/<job>-<YYYY-MM>.log — deshalb ist das hier überhaupt aufgefallen.
jobs/rebuild_indicesmeldete jede NachtBuilt 29 indices. Failed building 6 indicesund beendete sich mit exit 1 — an allen 225 Nächten, die das Debug-Protokoll noch abdeckt (01.01.2026 bis 26.08.2026; weiter reicht es nicht zurück).Unbemerkt, weil die Cron-Zeilen bis zum 24.08.2026 auf
>/dev/null 2>&1endeten.substance_index_atchatte gar keine Tabelle mehr — der Index wird vor dem Neuaufbau gelöscht, und der Aufbau kam nie durch.Drei Ursachen, die nichts miteinander zu tun haben
1. Latin-1 in
searchterms.rb— vier Indizessubstance_index,substance_soundex_index,doctor_index,hospital_indexDer Schutz prüfte auf
ASCII_8BITund aufvalid_encoding?. Bei ISO-8859-1 sagen beide „in Ordnung":Hier muss umgerechnet werden, nicht umetikettiert —
force_encodingist nur beiASCII_8BITrichtig, wo UTF-8-Bytes ohne Etikett liegen. Der neueto_utf8behandelt zusätzlich eingefrorene Strings, die der alte Schutz komplett übersprang (unless term.frozen?, weilforce_encodingin place mutiert hätte).Ein fünfter Index hing am selben Ort:
indication_indexscheiterte an"\xC3" from ASCII-8BIT to UTF-8.2. Ein einziges kaputtes Objekt — zwei Indizes
substance_index_atc,substance_index_sequenceComposition 58333159 von Dermophil MED Lippenbalsam (25790/02) hielt ein leeres Array dort, wo ein
ActiveAgenthingehört. Beide Definitionen rufen.substanceauf jedem Element vonactive_agents— ein kaputtes Element kostet also den ganzen Index, nicht bloss den einen Eintrag.Eines von 41 801. Und es tarnt sich gut:
Der Composition-Text nennt, was fehlte:
jobs/repair_broken_active_agentsstellt solche Elemente aus dem Text wieder her — über die Position, nicht per Regex-Rateverfahren, und es fasst nichts an, wenn die heilen Nachbarn nicht dort stehen, wo der Text sie erwartet.Balsamum Peruvianum 5 mgist wieder da.Eine Falle dabei:
Array#deletevergleicht mit==, das geht bei einem Stub durchActiveAgentCommon#==und ruft.substanceauf dem Gegenüber — also genau auf dem kaputten Element. Man kann es nicht per Wert löschen.object_idist der Weg; es steht auf ODBAsno_override-Liste (stub.rb:83),equal?nicht und würde auflösen.3. Eine Methode, die es auf der Klasse nicht gibt
Substancehatsequences, keinactive_sequences. Der Aufruf lief überSimpleLanguage#method_missingins Leere — und zwar auch beim normalen Speichern einer Substanz, nicht nur beim Neuaufbau.Behoben
Beide
resolve_origin-Ausdrücke sind jetzt abgedichtet, damit ein einzelnes kaputtes Element nie wieder einen ganzen Index kostet.Alle sieben bauen durch, null Fehler:
substance_index_atcsubstance_indexsubstance_index_sequencesubstance_soundex_indexindication_indexdoctor_indexhospital_indexRegressionstests:
test/test_util/searchterms.rb(9 runs).Die Lehre
Ein Job, der jede Nacht mit exit 1 endet und dessen Ausgabe niemand liest, ist genau so gut wie kein Job. Dieselbe Sorte Befund wie die drei toten Rack-Backends im August und der SL-Import, der zwölf Tage lang nichts tat und exit 0 meldete. Seit dem 24.08. schreibt
bin/oddb_cronnachlog/cron/<job>-<YYYY-MM>.log— deshalb ist das hier überhaupt aufgefallen.