Wer eine eigene Apache Solr Instanz aufsetzt, sieht sich recht bald mit der Frage konfrontiert wie denn nun die Daten in den Solr kommen sollen. Da gäbe es zunächst die Möglichkeit das per Skript zu erledigen und die fertigen Datensätze an die /update API zu schicken. Solr bietet aber auch die Option über einen DataImportHandler den Datenimport direkt zu erledigen.
Wie das genau von statten geht, soll in diesem Beitrag erläutert werden.
Vorbereitung
In der solrconfig.xml
muss folgendes eingefügt/angepasst werden.
1 2 3 4 5 |
<requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler"> <lst name="defaults"> <str name="config">db-data-config.xml</str> </lst> </requestHandler> |
Damit definieren wir einen neuen Endpunkt in der API namens /dataimport. Darüber können wir den Import anstoßen und steuern. Die genaue Konfiguration übernimmt dann der Übersicht halber eine andere Datei (db-data-config.xml
).
Das Gerüst
In die db-data-config.xml
fügen wir folgenden Code ein.
1 2 3 4 5 6 7 8 |
<dataConfig> <dataSource driver="com.mysql.jdbc.Driver" url="jdbc:mysql://dbhost/dbname?autoReconnect=true" batchSize="-1" user="root" password="123" encoding="UTF-8"/> <document> <entity name="company" ... > ... </entity> </document> </dataConfig> |
In datasource
definieren wir den MySql-Treiber und hinterlegen die Verbindungsdaten. Wer auf Nummer sicher gehen möchte kann noch ein readOnly="true"
hinzufügen. Aber Achtung: Wenn Stored Procedures verwendet werden sollen, dann darf die Verbindung nicht ready only sein.
In entity
steht nun endlich wie genau Solr die Daten beziehen soll. Der Name kann frei gewählt werden und hat nichts mit dem Namen des cores oder sonst irgendeiner Konfiguration zu tun. Er sollte – wie wir später sehen – dennoch sinnvoll sein.
SQL Statements und Zusammenbau der Entity
Der einfachste Fall – den natürlich nie einer hat – wäre jetzt folgender:
1 |
<entity name="company" query="SELECT * FROM companies"> |
Solr würde jetzt selbstständig bekannte (d. h. in schema.xml definierte) Spalten übernehmen. Also wenn es ein DB-Feld namens id und ein Solr-Feld namens id gibt, dann werden die Daten genau dahin übernommen. Unbekannte Spalten, die von der DB kommen, werden einfach ignoriert.
Zuordnung der Spalten
Da die Spalten aber natürlich unterschiedlich heißen können und vermutlich auch diverse aggregierte Informationen geliefert werden, gibt es zwei Möglichkeiten diese entsprechend zuzuordnen. Zunächst können wir natürlich im SQL die Spalten umbenenen.
1 |
<entity name="company" query="SELECT companyId AS id, CONCAT(firstname, ' ', lastname) AS contact, ... FROM companies"> |
Solr bietet aber auch eine eigene Variante an.
1 2 3 |
<entity name="company" query="SELECT * FROM companies"> <field column="id" name="companyId" /> </entity> |
Dabei ist column
der Name, den Solr kennt, und name
der Name in der DB.
Abfragen mehrerer Tabellen
Da in der DB die Daten vermutlich normalisiert und somit über mehrere Tabellen verteilt sind, müssen diese beim Import zusammengeführt werden. Dies gilt insbesondere für Felder, die in Solr mehrer Werte enthalten. Wie etwa
<field name="keywords" multiValued="true" ... />
Hier kommen wir mit einem JOIN nicht mehr hin. Dafür bietet Solr die Möglichkeit der Sub-Entity.
1 2 3 |
<entity name="company" query="SELECT companyId AS id, ... FROM companies"> <entity name="keywords" query="SELECT name AS keywords FROM keywords WHERE companyId = ${company.id}"></entity> </entity> |
An dieser Stelle wird ein weiteres Query ausgelöst, dass den Inhalt des keywords
Feldes des Datensatzes befüllt. Auch hier können wieder beide Varianten der Zuordnung genutzt werden. Mit einer Sub-Entity lassen sich auch mehrere Felder des Datensatzes gleichzeitig füllen. Ein wichtiger Unterschied ist, dass wir hier eine WHERE-Bedingung im Statement verwenden um nur die zugehörigen keywords zu selektieren. Dazu bietet uns Solr den Zugriff auf die Felder bereits abgefragter Entities. Wir können also mit ${company.id}
auf den Wert des Id-Feldes der company – definiert durch name="company"
– zugreifen.
Zu beachten ist hier, dass pro Sub-Entity für jeden Datensatz ein separates Query abgeschickt wird. Die Anzahl an Queries schnellt hier also drastisch in die Höhe!
Inkrementeller Import
Natürlich wollen wir nicht bei jeder Änderung der Daten einen vollen Import fahren. Wir benötigen also eine Möglichkeit Änderungen festzustellen und dann ein entsprechendes Update zu erzeugen. Dies geschieht mit der Definition zweier weiterer Queries.
1 2 3 4 5 |
<entity name="company" query="SELECT * FROM companies" deltaImportQuery="SELECT * FROM companies WHERE id = ${dih.delta.id}" deltaQuery="SELECT id FROM companies WHERE lastModified > '${dih.last_index_time}'"> </entity> |
Dabei stellt deltaQuery
das SQL-Statement dar mit dem geänderte Einträge erkannt werden. Solr liefert mit ${dih.last_index_time}
einen Timestamp (entspricht einem MySql DATETIME), der den letzten Delta-Import oder den letzten vollen Import repräsentiert. Als Rückgabe erwartet Solr ein Feld, mit dem es den Datensatz identifizieren kann (den primary key).
In deltaImportQuery
definieren wir das SQL-Statement mit dem wir das Update erzeugen. In den meisten Fällen wird es wohl identisch zum query
und lediglich um die WHERE-Bedingung für den einen Datensatz erweitert sein. Dabei können wir mit ${dih.delta.id}
auf die Rückgabe von deltaQuery
zugreifen. Es ist möglich das Query hier kleiner zu halten und Felder, die sich nicht ändern können, auszulassen. Solr wird den alten Datensatz nicht löschen, sondern lediglich die tatsächlich erhaltenen Felder aktualisieren.
Wenn Solr abfragt welche Einträge geändert wurden (deltaQuery
), spielen die Sub-Entities keine Rolle. Beim erzeugen des Updates danach, werden sie genau wie bei query
mitgenommen. Die Sub-Entities dürfen allerdings selbst eine deltaQuery
Definition enthalten um Änderungen hier feststellen zu können.
Stored Procedures
Aus diversen Gründen (Performance, Übersicht, …) bietet es sich an die Queries nicht direkt in die Konfiguration des Solr zu schreiben, sondern als Stored Procedure in der DB zu hinterlegen. Wie oben bereits erwähnt, darf die DB-Verbindung nicht read only sein. Mehr muss nicht beachtet werden.
1 |
<entity name="company" query="CALL companies_solr()"> |
Zusammengefasst
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<dataConfig> <dataSource driver="com.mysql.jdbc.Driver" url="jdbc:mysql://dbhost/dbname?autoReconnect=true" batchSize="-1" user="root" password="123" encoding="UTF-8"/> <document> <entity name="company" query="call companies_solr()" deltaImportQuery="call companies_solr_delta(${dih.delta.id})" deltaQuery="call companies_solr_modified('${dih.last_index_time}')"> <entity name="branches" query="call companies_branches_solr(${company.id})"></entity> <entity name="keywords" query="call companies_keywords_solr(${company.id})"></entity> </entity> </document> </dataConfig> |
Auslösen des Imports
Nachdem die Konfiguration erledigt ist, muss der Import nur noch gestartet werden.
Konfiguration neu einlesen: solrhost:8080/solr/corename/dataimport?command=reload-config
Kompletten Import starten: solrhost:8080/solr/corename/dataimport?command=full-import
Delta Import starten: solrhost:8080/solr/corename/dataimport?command=delta-import
Status abfragen: solrhost:8080/solr/corename/dataimport
Abbrechen: solrhost:8080/solr/corename/dataimport?command=abort