Projektbericht zur Nutzung der Solr/LuceneSuchengine in einem Zahlungskontrollsystem Michael Meyer Joh. Berenberg, Gossler & Co. KG Hamburg Schlüsselworte: Entwicklung, Solr, Lucene, Elastic, Oracle Text, Performance, PL/SQL, SQL, XML Einleitung: Zur Erkennung von Geldwäscheverdachtsfällen und Embargoverstößen durchsuchen Banken Zahlungsvorgänge nach Schlüsselwörtern. Im Rahmen des vorgestellten Projektes wurde diese Textsuche von Oracle Text auf Solr/Lucene umgestellt. Dargestellt der gesamte Projektablauf: Motivation für das Projekt; Toolauswahl mit jeweiligen Vor- und Nachteilen; Umsetzung; Resümee; weitere Anwendungsszenarien. Schwerpunkt ist die Darstellung der Fähigkeiten der Solr/Lucene-Suchengine. Ausgangslage, Status-Quo Im Jahr 2006 wurde die Vorgängerversion einer Suchengine zur Erkennung von Geldwäscheverdachtsfällen und Embargoverstößen produktiv geschaltet (realisiert mit Oracle Text). Diese wurde mit den Jahren den steigenden Anforderungen nicht mehr gerecht. Insbesondere bestand der Wunsch nach unscharfen Suchverfahren und es sollten wesentlich mehr Datenquellen in die Suche einbezogen werden. (neue) Anforderungen • Unscharfe Suchen (fuzzy logic) • Beispiele: „Sergey“, „Sergei“, „Sergej“ „Osama“, „Usama“ • Nachvollziehbare Scoringwerte, (Zur Beantwortung der Frage: „warum ist dieser Vorgang auffällig?“) • Datenlieferung der Schlüsselbegriffe durch externen Dienstleister. Täglicher Import • Wesentlich mehr Datenquellen für die Schlüsselbegriffe, wesentlich mehr Schlüsselbegriffe • Möglichkeit für den Fachbereich, eigene Schlüsselbegriffe zu ergänzen • „Google“-artige Recherchemöglichkeit über alle Schlüsselbegriffe für den Fachbereich • Tägliche Prüfung der Stammdaten gegen alle Schlüsselbegriffe Toolauswahl Kandidaten, die untersucht wurden: • Solr / Lucene • Elastic / Lucene • Oracle-Text Oracle Text -- CONTEXT-Index anlegen: -- Basistabelle: CREATE TABLE ZKS2.WC_ENTITIES ( WC_ENTITIES_ID NUMBER(12,0) NOT NULL ENABLE, ENT_ID NUMBER, NAME VARCHAR2(4000), SCHLUESSELBEGRIFF_CONTEXT VARCHAR2(4000), CONSTRAINT XPKWC_ENTITIES PRIMARY KEY (WC_ENTITIES_ID) ); CREATE INDEX ZKS2.RULE_SCHLUESSEL_CONTEXT ON ZKS2.WC_ENTITIES (SCHLUESSELBEGRIFF_CONTEXT) INDEXTYPE IS CTXSYS.CONTEXT PARAMETERS ('STORAGE WCO_BASICSTORAGE LEXER WCO_PREFERENCES STOPLIST WCO_STOPLIST TRANSACTIONAL SECTION GROUP WCO_SECTION_GROUP WORDLIST WCO_WORDLIST MEMORY 500M') PARALLEL 4 ; -- Die Parameter der CTX-Umgebung: DECLARE v_tablespace varchar2(100) := 'ZKS2INDEX' ; BEGIN BEGIN ctx_ddl.drop_preference('WCO_BASICSTORAGE'); EXCEPTION WHEN OTHERS THEN NULL; END; ctx_ddl.create_preference ('WCO_BASICSTORAGE' ,'BASIC_STORAGE'); ctx_ddl.set_attribute ('WCO_BASICSTORAGE','I_TABLE_CLAUSE' , 'tablespace ' || v_tablespace); ctx_ddl.set_attribute ('WCO_BASICSTORAGE','K_TABLE_CLAUSE' , 'tablespace ' || v_tablespace); ctx_ddl.set_attribute ('WCO_BASICSTORAGE','R_TABLE_CLAUSE' , 'tablespace ' || v_tablespace ||' LOB(DATA) STORE AS (CACHE)'); ctx_ddl.set_attribute ('WCO_BASICSTORAGE','N_TABLE_CLAUSE' , 'tablespace ' || v_tablespace); ctx_ddl.set_attribute ('WCO_BASICSTORAGE','I_INDEX_CLAUSE' , 'tablespace ' || v_tablespace ||' compress 2'); end; / BEGIN BEGIN ctx_ddl.drop_stoplist('WCO_STOPLIST'); OTHERS THEN NULL; END; ctx_ddl.create_stoplist('WCO_STOPLIST'); ctx_ddl.add_stopword('WCO_STOPLIST', 'xxxxx'); ctx_ddl.add_stopword('WCO_STOPLIST', 'yyyyy'); END; / EXCEPTION WHEN --- Test begin BEGIN ctx_ddl.drop_preference('WCO_PREFERENCES'); EXCEPTION WHEN OTHERS THEN NULL; END; CTX_DDL.create_preference('WCO_PREFERENCES', 'BASIC_LEXER'); CTX_DDL.set_attribute('WCO_PREFERENCES','ALTERNATE_SPELLING', 'GERMAN'); CTX_DDL.set_attribute('WCO_PREFERENCES','COMPOSITE', 'GERMAN'); CTX_DDL.set_attribute('WCO_PREFERENCES', 'MIXED_CASE', 'NO'); CTX_DDL.set_attribute('WCO_PREFERENCES', 'BASE_LETTER', 'YES'); CTX_DDL.set_attribute('WCO_PREFERENCES', 'PRINTJOINS', '-'); CTX_DDL.set_attribute('WCO_PREFERENCES','INDEX_STEMS', 'GERMAN'); end; / BEGIN BEGIN ctx_ddl.drop_preference('WCO_WORDLIST'); EXCEPTION WHEN OTHERS THEN NULL; END; ctx_ddl.create_preference('WCO_WORDLIST', 'BASIC_WORDLIST'); ctx_ddl.set_attribute('WCO_WORDLIST', 'NDATA_ALTERNATE_SPELLING', 'TRUE'); ctx_ddl.set_attribute('WCO_WORDLIST','NDATA_BASE_LETTER', 'TRUE'); ctx_ddl.set_attribute('WCO_WORDLIST','NDATA_JOIN_PARTICLES', 'de:di:la:da:el:del:qi:abd:los:la:dos:do:an:li:yi:yu:van:jon:un:s ai:ben:al'); end; / -- Beispielabfrage: SELECT * FROM (SELECT /* FIRST_ROWS(10) */ score(1) score, t.name FROM wc_entities t where contains(schluesselbegriff_context, 'NDATA(NAME,"OMAR MOHAMMED")',1) > 0 ORDER BY score DESC ) WHERE ROWNUM<=10; -- Ergebnis: Score Name 86 OMAR MOHAMMED 86 OMAR MOHAMMED MULLAH 76 MUHAMMAD OMAR ZADRAN MOHAMMAD OMAR JADRAN 76 JOUMAA MOHAMAD SAID JOMAA MOHAMED SAID 76 ZADRAN MUHAMMAD OMAR JADRAN MOHAMMAD OMAR 71 MOHAMMAD IBRAHIM OMARI IBRAHIM HAQQANI 69 AL AHMARI HAMED MOHAMMED 67 HOMAYOON MOHAMMAD Score Name 67 OSMAN MOHAMED Problem: Was sagen die Scoringwerte 86, 76, … 67 aus? Solr/Lucene, Elastic/Lucene Allgemeines: • Lucene (1999, Doug Cutting), Solr (2004, Yonik Seeley), Elastic ( 2010) • Top Level Apache Projects (Lucene: 2005, Solr: 2007) • Solr, Elastic: o Datatypes: Text, Integer, Double, Date, Time, Spatial • Structure: o Collections [=contain Documents (Document = fields & values; A field can occur multiple times, Documents are immutable (update = delete + insert new version)] Popularity (http://db-engines.com/en/ranking/search+engine) Allgemeine Features Solr / Elastic / Lucene: • REST API • Faceting („group by“) • Pivot • Language Detection During Indexing • Spell Checking, Stemming • Suggester (incremental search) • More Like This • Pagination of Results • Query Elevation („sponsored search“, „editorial boosting“) • Near time Searching • Highlighting • Debug • Synonyms • Stopwords (index time, query time) • Unstructured Content (PDF, MS Office, email, instant messages, …) • Statistics (avg-query-time, number-of-queries, …) • Data Import Handler Solr / Lucene : bekannte Anwender • Instagram: geo-search API • AOL: channel: Yellow Pages, Music, NFL Sports, AOL Recipes, Real Estate, Autos, Travel, StyleList • SourceForge: faceted search across all its projects • eBay: search German Classified sites (“Kleinanzeigen”) • Netflix: site search feature Solr-Sample Request: http://localhost:8983/solr/gsl/browse?q=hamburg „Problem“: Berechnung / Nachvollziehbarkeit des Score-Wertes Einflussfaktoren auf den Scoringwert sind die • Häufigkeit und Stellung der Suchbegriffe im gefundenen Dokument. • Gesamtanzahl der Dokumente TF-IDF-Formel • tf(t in d) = Term Frequency (number of times term t appears in document d) • idf(t) = Inverse Document Frequency 1 ln 1 number_of_documents number_of_documentsinwhichtappers Durch Überschreiben der similarity-Klasse kann das Scoringverhalten beeinflusst werden. Damit erhält man „erklärbare“ Scoringwerte. Default-Verhalten: Verhalten mit angepasster Similarity-Klasse: Überschriebene Similaity-Klasse zur Anpassung der Scoringwerte package de.berenberg.zks.lucene.similarity; import org.apache.lucene.index.FieldInvertState; import org.apache.lucene.search.similarities.DefaultSimilarity; /** * Die angepasste Similarity Klasse für das ZKS * * <ul> * <li><b>tf</b> = term frequency in document = measure of how often a term appears in the document</li> * <li><b>idf</b> = inverse document frequency = measure of how often the term appears across the index</li> * <li><b>coord</b> = number of terms in the query that were found in the document</li> * <li><b>lengthNorm</b> = measure of the importance of a term according to the total number of terms in the field</li> * <li><b>queryNorm</b> = normalization factor so that queries can be compared</li> * </ul> * * @version $Revision: 81919 $, 2013-09-02 15:25:18 * @author Maier */ public class ZksSimilarity extends DefaultSimilarity { @Override public float coord(int overlap, int maxOverlap) { float coord = 1.0f; return coord; } @Override public float queryNorm(float sumOfSquaredWeights) { float queryNorm = 1.0f; return queryNorm; } @Override public float lengthNorm(FieldInvertState state) { final int numTerms; if (discountOverlaps) { numTerms = state.getLength() - state.getNumOverlap(); } else { numTerms = state.getLength(); } final float lengthNorm; if (numTerms > 0) { // Falls es Terms gibt, berechnen wird // trotzdem keine Plus- bzw. Minuspukte // berücksichtigen aber den Boost-Wert lengthNorm = state.getBoost(); } else { // Ganz ohne Terme lifern wir keine Punkte lengthNorm = 0.0f; } return lengthNorm; } @Override public float tf(float freq) { float tf = 1.0f; return tf; } @Override public float idf(long docFreq, long numDocs) { // Wir igorieren Einfluss der Häufigkeit // in allen Dokumenten float idf = 1.0f; return idf; } } Entscheidungsfindung Als Tool wird Solr / Lucene gewählt Projektresümee Pro / Contra + Unscharfe Suchen (fuzzy logic) + Nachvollziehbare Ergebnisse, Scoring (mit Austausch der Similarity-Klasse) + Wesentlich mehr Datenquellen und mehr Schlüsselbegriffe + Datenlieferung der Schlüsselbegriffe durch Dienstleister + Möglichkeit für den Fachbereich eigene Schlüsselbegriffe zu ergänzen + „Google“-artige Recherchemöglichkeit über alle Schlüsselbegriffe für den Fachbereich + Tägliche Prüfung der Stammdaten (ca. 15 Min. Laufzeit) + Gute Adminoberfläche - zusätzliche Infrastruktur (Backup, Recovery, Hochverfügbarkeit) Einstellung der JBoss-Unterstützung ab Sol 5.xx Weitere Anwendungsfälle Kundensuche, Wertpapiersuche Kontaktadresse: Michael Meyer Joh. Berenberg, Gossler & Co. KG Neuer Jungfernstieg 20 D-20354 Hamburg Telefon: Fax: E-Mail Internet: +49 (0) 40-350 60-186 +49 (0) 40-350 60-954 [email protected] www.berenberg.de