Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[German] You Complete Me

[German] You Complete Me

Ein Projekt des letzten halbjährigen internen Hackathons bei Chefkoch.de war die Integration einer Autovervollständigung für unsere Rezeptsuche. Dieser Talk soll einen Einblick geben, welche Strategien wir angewendet haben, um ein solches High-Traffic Feature mit derzeit mehreren hundert Requests pro Sekunde in nur drei Tagen auf die Straße gebracht haben.

Themen: Skalierung, Caching, Golang, DevOps, Docker, ELK

Follow me:
https://twitter.com/b00gizm
https://github.com/b00gizm

Avatar for Pascal Cremer

Pascal Cremer

February 16, 2017
Tweet

More Decks by Pascal Cremer

Other Decks in Programming

Transcript

  1. METT-A-THON • Halbjährlicher (interner) Chefkoch Hackathon • Eine gesamte Woche

    (1 Discovery Day + 3.5 Tage "Hacking") • Nicht nur die Entwickler, sondern das gesamte Chefkoch Team! (Redaktion, Sales, Community etc.) • Projekte MÜSSEN innerhalb der Woche live gehen! (sonst rm -rf) • Anhand von vorher festgelegten KPIs wird nach 2 Wochen über den Erfolg (oder Misserfolg) eines Mett-a-Thon Projekts entschieden • http:/ /instagram.com/chefkoch.de
  2. –Pitch zum Projekt "Autovervollständigung für Rezeptsuche" „Autovervollständigung kann den Nutzer

    bei der Suche auf verschiedene Arten und Weisen unterstützen, z.B. schnellere und gezieltere Suche durch Anzeige der Suchbegriffe, die Ergebnisse liefern werden - oder auch als Inspiration.“
  3. –Hypothese des Projekts "Autovervollständigung für Rezeptsuche" „Durch eine verbesserte Suche

    werden weniger Nutzer über Google suchen, sondern länger auf der Chefkoch Seite bleiben und die eigene Suche verwenden.“
  4. class RecipesController extends Controller { public function suggestAction(Request $request) {

    $conn = $this->get('database_connection'); $queryBuilder = $conn->getQueryBuilder(); $queryBuilder ->select('title') ->from('recipes') ->where('title LIKE :term') ->setMaxResults(10) ->setParameter('term', $request->query->get('term').'%') ; $results = array_map(function($row) { return $row['title']; }, $queryBuilder->execute()->fetchAll()); return new JsonResponse(['suggestions' => $results]); } }
  5. 189,53 Mio.* Page Impressions (Pro MONAT) 36,87 MIO.* UNIQUE VISITS

    (PRO MONAT) NUTZUNG DER SUCHE IM WEB (Pro TAG) 300.000 * Dezember 2016, Quelle: www.ivw.eu
  6. class RecipesController extends Controller { public function suggestAction(Request $request) {

    $conn = $this->get('database_connection'); $queryBuilder = $conn->getQueryBuilder(); $queryBuilder ->select('title') ->from('recipes') ->where('title LIKE :term') ->setMaxResults(10) ->setParameter('term', $request->query->get('term').'%') ; $results = array_map(function($row) { return $row['title']; }, $queryBuilder->execute()->fetchAll()); return new JsonResponse(['suggestions' => $results]); } } JUST. NOPE!
  7. Search auto suggest • Search Engine statt MySQL Volltextsuche •

    Hochgradig performant (mehrere hundert Req/sec) • Einfach skalierbar • Monitoring / Tracking • Realisiert als eigener Microservice
  8. docker run -d —-name my-nginx -p 8080:80 nginx curl -I

    http://localhost:8080 HTTP/1.1 200 OK Server: nginx/1.11.9 Date: Mon, 13 Feb 2017 09:47:45 GMT Content-Type: text/html Content-Length: 612 Last-Modified: Tue, 24 Jan 2017 14:02:19 GMT Connection: keep-alive ETag: "58875e6b-264" Accept-Ranges: bytes
  9. TAG #1 - BE ELASTIC, BABY! • Elasticsearch als Suchmaschine

    • Lucene-basiert, REST-API, Autocomplete Out-of-the-Box • PHP Script zum regelmäßigen Update des Such-Indexes mit Rezepttiteln • Regelmäßiger Fetch von Change-Sets von der Chefkoch-API • jQuery Autocomplete Plugin
  10. FRONT VARNISH SEARCH-SUGGEST ELASTIC INDEXER (PHP) CK-API (holt Change-Sets) (befüllt

    Index per Cronjob) (Liefert Vorschläge) POST /search-suggestions/_search
  11. TAG #1 - MEH! • Qualität der Vorschläge war nicht

    zufriedenstellend • Wir wollen keine konkreten Rezepttitel als Vorschläge, sondern die Suchphrasen von anderen Nutzern • Response wird (noch) nicht gecached • Wir brauchen eine öffentliche API für unsere iOS & Android Apps, aber die Elasticsearch REST API ist "too chatty"
  12. POST search-suggestions/_search { "suggest": { "recipes-suggest" : { "prefix" :

    "spag", "completion" : { "field" : "suggest" } } } }
  13. { "_shards" : { "total" : 5, "successful" : 5,

    "failed" : 0 }, "hits": ... "took": 2, "timed_out": false, "suggest": { "recipes-suggest" : [ { "text" : "spag", "offset" : 0, "length" : 4, "options" : [ { "text" : "Spaghetti Bolognese", "_index": "recipes", "_type": "recipe", "_id": "1", "_score": 1.0, "_source": { "suggest": ["Spaghetti Bolognese"] } } ] } ] } }
  14. $(function() { $("#inputfield_quicksearch").autocomplete({ source: function(request, response) { $.ajax({ type: "POST",

    url: ..., data: JSON.stringify( { suggestions: { prefix: request.term, completion: { field: "suggest" } } } ), success: function (data) { var suggestions = []; $.each(data.suggestions, function() { $.each(this.options, function() { suggestions.push(this.text); }); }); response(suggestions); } }); }, success: function() {} }) });
  15. FRONT VARNISH POST /search-suggestions/_search SEARCH-SUGGEST ELASTIC INDEXER (PHP) CK-API (holt

    Change-Sets) (befüllt Index per Cronjob) (Liefert Vorschläge) NOT THERE YET!
  16. TAG #2 - IT'S JUST A FACADE... • API Facade,

    um exakt nur das per API zu exposen, das wir benötigen • Performanter "Proxy" zwischen Varnish und Elasticsearch zur Umformung von Request und Response • Front Varnish zum Cachen der Response nutzen • Caching Header über die API Facade setzen
  17. — Das "Facade" Entwurfsmuster (Wikipedia) „[Das Facade Pattern] bietet eine

    einheitliche und meist vereinfachte Schnittstelle zu einer Menge von Schnittstellen eines Subsystems. Die Fassade ist eine Klasse mit ausgewählten Methoden, die eine häufig benötigte Untermenge an Funktionalität des Subsystems umfasst. Sie delegiert die Funktionalität an andere Klassen des Subsystems und vereinfacht dadurch den Umgang mit dem Subsystem.“
  18. — Das "Facade" Entwurfsmuster (Wikipedia) „[Das Facade Pattern] bietet eine

    einheitliche und meist vereinfachte Schnittstelle zu einer Menge von Schnittstellen eines Subsystems. Die Fassade ist eine Klasse mit ausgewählten Methoden, die eine häufig benötigte Untermenge an Funktionalität des Subsystems umfasst. Sie delegiert die Funktionalität an andere Klassen des Subsystems und vereinfacht dadurch den Umgang mit dem Subsystem.“
  19. POST /search-suggestions/_search { "suggest": { "recipes-suggest" : { "prefix" :

    "spag", "completion" : { "field" : "suggest" } } } } GET /search-suggestions?t=spag
  20. { "_shards" : { "total" : 5, "successful" : 5,

    "failed" : 0 }, "hits": ... "took": 2, "timed_out": false, "suggest": { "recipes-suggest" : [ { "text" : "spag", "offset" : 0, "length" : 4, "options" : [ { "text" : "Spaghetti Bolognese", "_index": "recipes", "_type": "recipe", "_id": "1", "_score": 1.0, "_source": { "suggest": ["Spaghetti Bolognese"] } } ] } ] } } { "suggestions": [ "Spaghetti Bolognese", ... ] }
  21. Golang: Eine statisch kompilierte, "C"- ähnliche Programmiersprache mit einem sehr

    kleinen Sprachumfang, hohen Performance Benefits und einem einfachen Concurrency Modell.
  22. r := gin.New() r.Use(gin.Recovery()) r.Use(middlewares.Cache()) r.GET("/search-suggestions", func(c *gin.Context) { searchTerm,

    found := c.GetQuery("t") if !found { c.JSON(404, gin.H{ "error": "Missing search term", }) return } suggestion, err := elasticSuggester.Fetch(searchTerm) if err != nil { c.JSON(500, gin.H{ "error": "Error while fetching suggestions", }) return } c.JSON(200, suggestion) }) r.Run("0.0.0.0:8080")
  23. TAG #3 - SCALE ALL THE THINGS! • Ein besserer

    Index für unsere Vorschläge aus dem Elasticsearch • Export aller User-Suchphrasen aus unserem BI Hadoop-Cluster • API Facade und Elasticsearch auf beliebig viele Instanzen skalieren • Neue Elasticsearch Instanzen sollen sich "von selbst" indizieren • Monitoring und Logging integrieren
  24. INDEXER (PHP) ELASTICSEARCH Chefkoch Docker Registry ELASTICSEARCH FRONT VARNISH SEARCH-SUGGEST

    ELASTICSEARCH API FACADE API FACADE ELASTICSEARCH ELASTICSEARCH API FACADE BAMBOO (CI) NIGHTLY BUILD Hadoop Cluster (CSV Export)
  25. LEARNINGS! • Agilität ist dein Freund! • Die simpelste Lösung

    ist meist die beste Lösung • The right tool for the right job • Frühes Feedback einholen • Power of Proof • Der letzte Gin Tonic, 5 Uhr morgens am Release Tag, war nicht meine allerbeste Idee