27.082014

Personalisierte Inhalte ausgeben mit Elasticsearch

Servus alle miteinander, ich hatte die letzten Wochen ein paar Probleme, dass wir in bestimmten Fällen manchen Kunden direkte Werbung ausgeben lassen wollen, bzw Produkte einfach besser auf deren Wünsche ausliefern lassen. Wir haben seit einiger Zeit Elasticsearch im Einsatz und haben uns nun dazu entschlossen dies umzubauen. Da es im Internet dafür nicht besonders viele Referenzen für Entwickler gibt, blogge ich darüber diese Woche mal.

Also fangen wir mal an und nehmen das Beispiel einer Buchhandlung. Gleich vorweg, ich werde hier natürlich nicht alles komplett bis ins kleinste Detail schreiben, sondern euch diverse Denkansätze geben, wie eine solch personalisierte Inhalte aussehen können.

Das Mapping für unseren Test sieht wie folgt aus:

{
  "books": {
    "properties": {
      "categories": {
        "type": "integer"
      },
      "author": {
        "type": "integer"
      },
      "date": {
        "type": "date",
        "format": "YYYY-MM-dd HH:mm:ss"
      },
      "title": {
        "type": "string"
      }
    }
  }
}

Hierbei zu erwähnen ist, dass author und categories als indizes bei uns gespeichert werden, die in anderen mappings liegen. Die brauchen wir aber für eine solche Abfrage nicht wirklich. Kommen wir jetzt mal zum Praxiseinsatz, der User kommt das erste mal auf unsere Seite und guckt sich ein paar Bücher an. Theoretisch haben wir schon nach dem ersten Buch was der Kunde geklickt hat, eine erste Prognose, bezüglich der Bücher/Kategorien die der Kunde mag. Natürlich ist das nicht representativ. Aber dennoch können wir von Zeit zu Zeit wo der Kunde bei uns auf der Seite seine Zeit verbringt, ihn analysieren und auch sagen, was er mit sicherheit auch mag. Wir speichern als die Kategorien und Autoren die der User sich angeguckt hat. Natürlich ist die Haltezeit hierbei auch sehr wichtig, da wir nur Ergebnisse speichern wollen von beispielsweise einer Haltezeit von 10 Sekunden. Andernfalls, ist der Kunde doch nicht interessiert gewesen.

Aber das ist alles nur reine Theorie. Kommen wir zu unserem Beispielkunden Max Mustermann, der in Seiner Freizeit gerne Bücher über Vampire, Krimis und Liebesromane ließt. Sein Lieblingsauthor ist John Grissam aber er weiß nicht, welches Buch nun noch zu ihm passt.(Achtung das ist natürlich nicht echt, aber irgend ein blödes Beispiel musste ich ja wählen :D )

Wir wollen nun wissen, welches Buch passt zu diesem User, aber wissen nicht wie? Dann kommen nun die Aggregations von Elasticsearch zum Einsatz, die jedem Entwickler etwas sagen sollten. Wenn nicht, hier der query, den man über curl XPOST an den Elasticsearch schicken kann:

{
  "query": {
    "function_score": {
      "query": {
        "filtered": {
          "filter": {
            "bool": {
              "must": [
                {
                  "range": {
                    "date": {
                      "lt": "2014-08-22 13:40:54"
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "functions": [
        {
          "filter": {
            "term": {
              "categories": 3
            }
          },
          "boost_factor": 1.1
        },
        {
          "filter": {
            "term": {
              "categories": 6
            }
          },
          "boost_factor": 1.1
        },
        {
          "filter": {
            "term": {
              "categories": 9
            }
          },
          "boost_factor": 1.1
        },
        {
          "filter": {
            "term": {
              "author": 54
            }
          },
          "boost_factor": 1.2
        },
        {
          "filter": {
            "exists": {
              "field": "date"
            }
          },
          "script_score": {
            "params": {
              "now": 1408800000000
            },
            "script": "(0.08 \/ ((3.16*pow(10,-11)) * abs(now - doc['date'].date.getMillis()) + 0.05)) + 1.0"
          }
        }
      ],
      "score_mode": "multiply"
    }
  },
  "size": 4
}

Hier werden natürlich keinerlei Sachen komplett ausgeschlossen, außer Bücher die noch nicht veröffentlicht sind. Diesen Filter habe ich beigefügt als Beispiel, dass man dadurch nochmal bestimmtere Auswahlen treffen kann und auch Ergebnisse direkt rausfiltern.

Hier kann man so viele Filter mit einzelnem Boost angeben wie man möchte und bekommt direkt die Ergebnisse zurück geliefert, die den Kunden interessieren können. Sortiert wird hierbei außerdem noch nach dem Veröffentlichungsdatum, sodass neue Bücher die auf die Bedürfnisse des Kunden zutreffen, als erstes Angezeigt werden.

Nun wenn ich den Faktor Haltezeit wieder aufgreife, kann man hierdrüber als Entwickler eine Statistik jedes einzelnen Users führen mit Haltezeiten pro Kategorie und Autor. Dementsprechend kann man noch den Boost_factor wirklich komplett variabel einstellen. Für uns haben bei den ersten Tests nun diese Werte überzeugt. Wie die Werte nun wirklich sein sollten, kann jeder selbst in eigenen Tests ausfindig machen.

Ich hoffe ich konnte hiermit einigen helfen die etwas ähnliches vorhaben aber keine Ahnung hatten, wie sie das realisieren wollen.

Schönes Bergfest, Mark