CINXE.COM
Elasticsearch case study for realtime analytics
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Elasticsearch case study for realtime analytics</title> <meta name="viewport" content="width=device-width"> <link href="/assets/css/style.css" rel="stylesheet" /> <!-- favicon code start, by https://favicon.io/favicon-generator --> <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> <link rel="manifest" href="/site.webmanifest"> <!-- favicon code end --> </head> <body> <div id="header-outer"> <header id="header"> <h1><a href="/">Michael Pollmeier ~ dev blog</a></h1> <nav> <ul> <li><a href="/">blog posts</a></li> <li><a href="/presentations">presentations</a></li> <li><a href="https://mastodontech.de/@mpollmeier">mastodon</a></li> <li><a href="/feed.xml">blog rss</a></li> <li><a href="/about">about me</a></li> </ul> </nav> </header> </div> <div id="page"> <div id="content"> <article class="post"> <h1><a href="/elasticsearch-case-study">Elasticsearch case study for realtime analytics</a></h1> <p class="meta"> Posted on <span class="postdate">May 18, 2014</span><br/> tags: <!-- <a href="/tags.html#elasticsearch">elasticsearch</a> --> elasticsearch<span class="tag-separator">,</span> <!-- <a href="/tags.html#case-study">case-study</a> --> case-study<span class="tag-separator">,</span> <!-- <a href="/tags.html#analytics">analytics</a> --> analytics<span class="tag-separator">,</span> <!-- <a href="/tags.html#spray">spray</a> --> spray </p> <div class="post-content"><h1 id="what-were-trying-to-do">What we’re trying to do</h1> <p>If you want to do big data analytics there are many solutions out there to do that. For the past couple of weekends I’ve been playing with some of them, and it looks like I finally found a great tool for my use case: Elasticsearch. A typical question you might want to answer could be: <strong>Find the number of members who are between 20 and 30 years old and have borrowed a book of a certain author last month</strong>. Here’s some criteria for the solution:</p> <ul> <li>arbitrary ad-hoc queries should return numbers within a minute</li> <li>(linearly) scalable to very large amounts of data</li> <li>update speed not critical</li> </ul> <p>I’m a <a href="https://github.com/mpollmeier/gremlin-scala">graph database enthusiast</a> so I first tried to solve this with <a href="https://github.com/thinkaurelius/titan">Titan</a> (a distributed graph database). However I learned that a graph db <a href="https://groups.google.com/forum/#!topic/aureliusgraphs/lwDP8Eh8z9E">is not the right tool</a> for this particular problem.</p> <p>All I needed was something that precomputes indexes and can combine composite indexes in arbitrary queries. So I gave <a href="http://elasticsearch.org">Elasticsearch</a> a try and it seems to work really well for this use case. Elasticsearch let’s you persist arbitrary documents and automatically creates indexes on all the fields. It has some nice strategies like nested and parent/child documents that allow it to effectively shard the documents yet allow for powerful searches. E.g. if you define a collection inside your document as a <a href="http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/mapping-nested-type.html">nested type</a>, elasticsearch will index it as a separate entity, yet assure that it’s hosted on the same shard as it’s parent. By default it creates indexes for all provided fields and it’s very efficient for combining indexes.</p> <h1 id="setup-with-elasticsearch">Setup with elasticsearch</h1> <p>Setting up an index and some documents is really nice and easy with it’s rest api:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"member"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"name"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w"> </span><span class="nl">"index"</span><span class="p">:</span><span class="w"> </span><span class="s2">"not_analyzed"</span><span class="p">},</span><span class="w"> </span><span class="nl">"age"</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"integer"</span><span class="p">},</span><span class="w"> </span><span class="nl">"properties"</span><span class="p">:{</span><span class="w"> </span><span class="nl">"books"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nested"</span><span class="p">,</span><span class="w"> </span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">},</span><span class="w"> </span><span class="nl">"borrowedOn"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"date"</span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <ul> <li>member has an age and a (non-indexed) name</li> <li>books is a nested array inside members</li> <li>a book has an author and the information when the member borrowed it</li> </ul> <p>For my test case I set up 1 million members and 10 million borrowed books - to make that batch insert go fast I used <a href="http://spray.io">spray</a> to send those http requests. That part itself is quite interesting for learning spray and Akka, so I’ve included the code in the appendix. A typical member looks like this:</p> <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w"> </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"member 1"</span><span class="p">,</span><span class="w"> </span><span class="nl">"age"</span><span class="p">:</span><span class="w"> </span><span class="mi">25</span><span class="p">,</span><span class="w"> </span><span class="nl">"books"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="p">{</span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ranicki"</span><span class="p">,</span><span class="w"> </span><span class="nl">"borrowedOn"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2014-05-17"</span><span class="p">},</span><span class="w"> </span><span class="p">{</span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">"klein"</span><span class="p">,</span><span class="w"> </span><span class="nl">"borrowedOn"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2014-04-01"</span><span class="p">}</span><span class="w"> </span><span class="p">]</span><span class="w"> </span><span class="p">}</span><span class="w"> </span></code></pre></div></div> <h1 id="querying-elasticsearch">Querying Elasticsearch</h1> <p>Now that everything is setup we are ready to query elasticsearch. Remember we want to find the number of members who are between 20 and 30 years and have borrowed a book written by ranicki in the last month. Here’s how you can do that using curl:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl http://localhost:9200/members/member/_search?pretty<span class="o">=</span><span class="nb">true</span> <span class="nt">-d</span> <span class="s1">'{ "filter": { "and": [ { "range": { "age": { "gt" : 20, "lt" : 30 } } }, { "nested": { "path": "books", "query": { "bool": { "must": [ { "term": { "books.author": "ranicki" } }, { "range": { "books.borrowedOn": { "gt" : "2014-02-18", "lt" : "2014-05-18" } } } ] } } } } ] } }'</span> </code></pre></div></div> <p>And the results come back in around than 100 milliseconds - with cold caches on my three year old laptop! Now that’s damn fast! In this case there’s about 95k members that hit the criteria. That being said it returns the count of total members and a couple of sample members (optionally the <code class="language-plaintext highlighter-rouge">highest scoring</code> ones). This is the first time I’ve used Elasticsearch and it’s love at first sight!</p> <h1 id="appendix-setup">Appendix: setup</h1> <p><a href="http://www.elasticsearch.org/downloads/">Download</a> elasticsearch and start it with <em>bin/elasticsearch</em></p> <p>The spray-client code to setup the index, members and books. The full repo is on <a href="https://github.com/mpollmeier/elasticsearch-sprayclient">github</a> if you wanna play yourself.</p> <div class="language-scala highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">spray</span> <span class="k">import</span> <span class="nn">java.text.SimpleDateFormat</span> <span class="k">import</span> <span class="nn">java.util.Calendar</span> <span class="k">import</span> <span class="nn">scala.concurrent.Await</span> <span class="k">import</span> <span class="nn">scala.concurrent.duration._</span> <span class="k">import</span> <span class="nn">scala.concurrent.Future</span> <span class="k">import</span> <span class="nn">scala.util._</span> <span class="k">import</span> <span class="nn">akka.actor._</span> <span class="k">import</span> <span class="nn">akka.io.IO</span> <span class="k">import</span> <span class="nn">akka.pattern.ask</span> <span class="k">import</span> <span class="nn">akka.routing.RoundRobinRouter</span> <span class="k">import</span> <span class="nn">akka.util.Timeout</span> <span class="k">import</span> <span class="nn">spray.can.Http</span> <span class="k">import</span> <span class="nn">spray.client.pipelining._</span> <span class="k">import</span> <span class="nn">spray.http._</span> <span class="k">object</span> <span class="nc">ElasticSearchTryout</span> <span class="k">extends</span> <span class="nc">App</span> <span class="o">{</span> <span class="k">import</span> <span class="nn">MemberCreator._</span> <span class="k">val</span> <span class="nv">longTimeout</span> <span class="k">=</span> <span class="mi">300</span> <span class="n">minutes</span> <span class="k">implicit</span> <span class="k">val</span> <span class="nv">system</span> <span class="k">=</span> <span class="nc">ActorSystem</span><span class="o">()</span> <span class="k">implicit</span> <span class="k">val</span> <span class="nv">timeout</span> <span class="k">=</span> <span class="nc">Timeout</span><span class="o">(</span><span class="n">longTimeout</span><span class="o">)</span> <span class="k">import</span> <span class="nn">system.dispatcher</span> <span class="k">def</span> <span class="nf">await</span><span class="o">(</span><span class="n">f</span><span class="k">:</span> <span class="kt">Future</span><span class="o">[</span><span class="k">_</span><span class="o">])</span> <span class="k">=</span> <span class="nv">Await</span><span class="o">.</span><span class="py">result</span><span class="o">(</span><span class="n">f</span><span class="o">,</span> <span class="n">longTimeout</span><span class="o">)</span> <span class="k">val</span> <span class="nv">pipeline</span><span class="k">:</span> <span class="kt">Future</span><span class="o">[</span><span class="kt">SendReceive</span><span class="o">]</span> <span class="k">=</span> <span class="nf">for</span> <span class="o">(</span> <span class="nv">Http</span><span class="o">.</span><span class="py">HostConnectorInfo</span><span class="o">(</span><span class="n">connector</span><span class="o">,</span> <span class="k">_</span><span class="o">)</span> <span class="k"><-</span> <span class="nc">IO</span><span class="o">(</span><span class="nc">Http</span><span class="o">)</span> <span class="o">?</span> <span class="nv">Http</span><span class="o">.</span><span class="py">HostConnectorSetup</span><span class="o">(</span><span class="s">"localhost"</span><span class="o">,</span> <span class="n">port</span> <span class="k">=</span> <span class="mi">9200</span><span class="o">)</span> <span class="o">)</span> <span class="k">yield</span> <span class="nf">sendReceive</span><span class="o">(</span><span class="n">connector</span><span class="o">)</span> <span class="k">def</span> <span class="nf">setupIndex</span><span class="o">()</span> <span class="k">=</span> <span class="o">{</span> <span class="nf">await</span><span class="o">(</span><span class="n">pipeline</span> <span class="nf">flatMap</span> <span class="o">(</span><span class="nf">_</span><span class="o">(</span><span class="nc">Delete</span><span class="o">(</span><span class="n">s</span><span class="s">"/members"</span><span class="o">))))</span> <span class="nf">await</span><span class="o">(</span><span class="n">pipeline</span> <span class="nf">flatMap</span> <span class="o">(</span><span class="nf">_</span><span class="o">(</span><span class="nc">Put</span><span class="o">(</span><span class="n">s</span><span class="s">"/members"</span><span class="o">))))</span> <span class="nf">await</span><span class="o">(</span><span class="n">pipeline</span> <span class="nf">flatMap</span> <span class="o">(</span><span class="nf">_</span><span class="o">(</span><span class="nc">Post</span><span class="o">(</span><span class="n">s</span><span class="s">"/members/member/_mapping"</span><span class="o">,</span> <span class="s">""" { "member":{ "name" : {"type": "string", "index": "not_analyzed"}, "age" : {"type": "integer"}, "properties":{ "books": { "type": "nested", "properties": { "author": {"type": "string"}, "borrowedOn": {"type": "date"} } } } } }"""</span><span class="o">))))</span> <span class="o">}</span> <span class="nf">setupIndex</span><span class="o">()</span> <span class="nf">println</span><span class="o">(</span><span class="s">"index setup complete"</span><span class="o">)</span> <span class="k">val</span> <span class="nv">memberCount</span> <span class="k">=</span> <span class="mi">1</span> <span class="o">*</span> <span class="mi">1000</span> <span class="o">*</span> <span class="mi">1000</span> <span class="k">val</span> <span class="nv">memberCreator</span> <span class="k">=</span> <span class="nv">system</span><span class="o">.</span><span class="py">actorOf</span><span class="o">(</span><span class="nc">Props</span><span class="o">(</span><span class="n">classOf</span><span class="o">[</span><span class="kt">MemberCreator</span><span class="o">],</span> <span class="n">pipeline</span><span class="o">)</span> <span class="o">.</span><span class="py">withRouter</span><span class="o">(</span><span class="nc">RoundRobinRouter</span><span class="o">(</span><span class="n">nrOfInstances</span> <span class="k">=</span> <span class="mi">5</span><span class="o">)))</span> <span class="k">val</span> <span class="nv">futures</span> <span class="k">=</span> <span class="o">(</span><span class="mi">0</span> <span class="n">until</span> <span class="n">memberCount</span><span class="o">)</span> <span class="n">map</span> <span class="o">{</span> <span class="n">id</span> <span class="k">⇒</span> <span class="n">memberCreator</span> <span class="o">?</span> <span class="nc">CreateMember</span><span class="o">(</span><span class="n">id</span><span class="o">)</span> <span class="o">}</span> <span class="nv">Await</span><span class="o">.</span><span class="py">ready</span><span class="o">(</span><span class="nv">Future</span><span class="o">.</span><span class="py">sequence</span><span class="o">(</span><span class="n">futures</span><span class="o">),</span> <span class="n">longTimeout</span><span class="o">)</span> <span class="nf">println</span><span class="o">(</span><span class="s">"setting up members complete"</span><span class="o">)</span> <span class="nv">system</span><span class="o">.</span><span class="py">shutdown</span><span class="o">()</span> <span class="nf">println</span><span class="o">(</span><span class="s">"shutdown complete"</span><span class="o">)</span> <span class="o">}</span> <span class="k">object</span> <span class="nc">MemberCreator</span> <span class="o">{</span> <span class="k">case</span> <span class="k">class</span> <span class="nc">CreateMember</span><span class="o">(</span><span class="n">id</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span> <span class="k">case</span> <span class="k">object</span> <span class="nc">MemberCreated</span> <span class="o">}</span> <span class="k">class</span> <span class="nc">MemberCreator</span><span class="o">(</span><span class="n">pipeline</span><span class="k">:</span> <span class="kt">Future</span><span class="o">[</span><span class="kt">SendReceive</span><span class="o">])</span> <span class="k">extends</span> <span class="nc">Actor</span> <span class="o">{</span> <span class="k">import</span> <span class="nn">MemberCreator._</span> <span class="k">val</span> <span class="nv">longTimeout</span> <span class="k">=</span> <span class="mi">30</span> <span class="n">minutes</span> <span class="k">val</span> <span class="nv">averageTxCountPerMember</span> <span class="k">=</span> <span class="mi">10</span> <span class="k">val</span> <span class="nv">rand</span> <span class="k">=</span> <span class="k">new</span> <span class="nv">java</span><span class="o">.</span><span class="py">util</span><span class="o">.</span><span class="py">Random</span> <span class="k">implicit</span> <span class="k">val</span> <span class="nv">timeout</span> <span class="k">=</span> <span class="nc">Timeout</span><span class="o">(</span><span class="n">longTimeout</span><span class="o">)</span> <span class="k">import</span> <span class="nn">context.dispatcher</span> <span class="k">def</span> <span class="nf">receive</span> <span class="k">=</span> <span class="o">{</span> <span class="k">case</span> <span class="nc">CreateMember</span><span class="o">(</span><span class="n">id</span><span class="o">)</span> <span class="k">⇒</span> <span class="nf">if</span><span class="o">(</span><span class="n">id</span> <span class="o">%</span> <span class="mi">1000</span> <span class="o">==</span> <span class="mi">0</span><span class="o">)</span> <span class="nf">println</span><span class="o">(</span><span class="n">s</span><span class="s">"creating member $id ($self)"</span><span class="o">)</span> <span class="k">val</span> <span class="nv">respondTo</span> <span class="k">=</span> <span class="n">sender</span> <span class="nv">Await</span><span class="o">.</span><span class="py">ready</span><span class="o">(</span><span class="nf">createMember</span><span class="o">(</span><span class="n">id</span><span class="o">),</span> <span class="n">longTimeout</span><span class="o">)</span> <span class="n">respondTo</span> <span class="o">!</span> <span class="nc">MemberCreated</span> <span class="o">}</span> <span class="k">def</span> <span class="nf">createMember</span><span class="o">(</span><span class="n">id</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span><span class="k">:</span> <span class="kt">Future</span><span class="o">[</span><span class="kt">HttpResponse</span><span class="o">]</span> <span class="k">=</span> <span class="o">{</span> <span class="k">val</span> <span class="nv">age</span> <span class="k">=</span> <span class="mi">12</span> <span class="o">+</span> <span class="nv">rand</span><span class="o">.</span><span class="py">nextInt</span><span class="o">(</span><span class="mi">50</span><span class="o">)</span> <span class="k">val</span> <span class="nv">bookCount</span> <span class="k">=</span> <span class="nv">rand</span><span class="o">.</span><span class="py">nextInt</span><span class="o">(</span><span class="n">averageTxCountPerMember</span> <span class="o">*</span> <span class="mi">2</span><span class="o">)</span> <span class="k">val</span> <span class="nv">books</span> <span class="k">=</span> <span class="o">(</span><span class="mi">0</span> <span class="n">until</span> <span class="n">bookCount</span><span class="o">)</span> <span class="n">map</span> <span class="n">createBook</span> <span class="k">val</span> <span class="nv">request</span> <span class="k">=</span> <span class="nc">Put</span><span class="o">(</span><span class="n">s</span><span class="s">"/members/member/$id"</span><span class="o">,</span> <span class="n">s</span><span class="s">""" { "name": "Member $id", "age": $age, "books": [ ${books.mkString(",")} ] } """</span><span class="o">)</span> <span class="n">pipeline</span> <span class="nf">flatMap</span> <span class="o">(</span><span class="nf">_</span><span class="o">(</span><span class="n">request</span><span class="o">))</span> <span class="o">}</span> <span class="k">val</span> <span class="nv">dateFormat</span> <span class="k">=</span> <span class="k">new</span> <span class="nc">SimpleDateFormat</span><span class="o">(</span><span class="s">"yyyy-MM-dd"</span><span class="o">)</span> <span class="k">def</span> <span class="nf">createBook</span><span class="o">(</span><span class="n">id</span><span class="k">:</span> <span class="kt">Int</span><span class="o">)</span> <span class="k">=</span> <span class="o">{</span> <span class="k">val</span> <span class="nv">authors</span> <span class="k">=</span> <span class="nc">List</span><span class="o">(</span><span class="s">"ranicki"</span><span class="o">,</span> <span class="s">"klein"</span><span class="o">,</span> <span class="s">"lessing"</span><span class="o">)</span> <span class="k">def</span> <span class="nf">randomAuthor</span><span class="o">()</span><span class="k">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="nf">authors</span><span class="o">(</span><span class="nv">rand</span><span class="o">.</span><span class="py">nextInt</span><span class="o">(</span><span class="nv">authors</span><span class="o">.</span><span class="py">size</span><span class="o">))</span> <span class="k">def</span> <span class="nf">randomDate</span><span class="o">()</span><span class="k">:</span> <span class="kt">String</span> <span class="o">=</span> <span class="o">{</span> <span class="k">val</span> <span class="nv">cal</span> <span class="k">=</span> <span class="nv">Calendar</span><span class="o">.</span><span class="py">getInstance</span><span class="o">()</span> <span class="nv">cal</span><span class="o">.</span><span class="py">set</span><span class="o">(</span><span class="nv">Calendar</span><span class="o">.</span><span class="py">YEAR</span><span class="o">,</span> <span class="mi">2014</span><span class="o">)</span> <span class="nv">cal</span><span class="o">.</span><span class="py">set</span><span class="o">(</span><span class="nv">Calendar</span><span class="o">.</span><span class="py">DAY_OF_MONTH</span><span class="o">,</span> <span class="mi">1</span> <span class="o">+</span> <span class="nv">rand</span><span class="o">.</span><span class="py">nextInt</span><span class="o">(</span><span class="mi">28</span><span class="o">))</span> <span class="nv">cal</span><span class="o">.</span><span class="py">set</span><span class="o">(</span><span class="nv">Calendar</span><span class="o">.</span><span class="py">MONTH</span><span class="o">,</span> <span class="mi">1</span> <span class="o">+</span> <span class="nv">rand</span><span class="o">.</span><span class="py">nextInt</span><span class="o">(</span><span class="mi">12</span><span class="o">))</span> <span class="nv">dateFormat</span><span class="o">.</span><span class="py">format</span><span class="o">(</span><span class="nv">cal</span><span class="o">.</span><span class="py">getTime</span><span class="o">)</span> <span class="o">}</span> <span class="k">val</span> <span class="nv">author</span> <span class="k">=</span> <span class="nf">randomAuthor</span><span class="o">()</span> <span class="k">val</span> <span class="nv">date</span> <span class="k">=</span> <span class="nf">randomDate</span><span class="o">()</span> <span class="n">s</span><span class="s">"""{ "id": $id, "author": "$author", "borrowedOn": "$date"}"""</span> <span class="o">}</span> <span class="k">def</span> <span class="nf">verifyResponseStatus</span><span class="o">(</span><span class="n">response</span><span class="k">:</span> <span class="kt">HttpResponse</span><span class="o">)</span> <span class="k">=</span> <span class="nv">response</span><span class="o">.</span><span class="py">status</span> <span class="k">match</span> <span class="o">{</span> <span class="k">case</span> <span class="nv">StatusCodes</span><span class="o">.</span><span class="py">OK</span> <span class="o">|</span> <span class="nv">StatusCodes</span><span class="o">.</span><span class="py">Created</span> <span class="k">⇒</span> <span class="c1">//all good</span> <span class="k">case</span> <span class="k">_</span> <span class="k">⇒</span> <span class="nf">println</span><span class="o">(</span><span class="s">"problem!"</span><span class="o">,</span> <span class="n">response</span><span class="o">)</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> </div> <hr /> <div class="footer"> If you liked this post you may want to follow me on <a href="https://mastodontech.de/@mpollmeier">mastodon</a> and subscribe to the <a href="/feed.xml">RSS feed</a>. </div> </article> </div> </div> <script src="/assets/js/jquery.min.js"></script> <script src="/assets/js/jquery.mobilemenu.min.js"></script> <script> $(document).ready(function(){ $('#sidebar nav ul').mobileMenu({'topOptionText': 'Menu', 'prependTo': '#sidebar nav'}); }); </script> </body> </html>