<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Redis | Bits And Music</title>
    <link>https://bitsandmusic.com/tag/redis/</link>
      <atom:link href="https://bitsandmusic.com/tag/redis/index.xml" rel="self" type="application/rss+xml" />
    <description>Redis</description>
    <generator>Source Themes Academic (https://sourcethemes.com/academic/)</generator><language>en-us</language><lastBuildDate>Mon, 15 Jan 2024 00:00:00 +0000</lastBuildDate>
    <image>
      <url>https://bitsandmusic.com/images/icon_hua5672c1e15dce4d511903ad7fb945fd0_28771_512x512_fill_lanczos_center_2.png</url>
      <title>Redis</title>
      <link>https://bitsandmusic.com/tag/redis/</link>
    </image>
    
    <item>
      <title>Say Hello To Microservices: Building a Music Discovery App - 2</title>
      <link>https://bitsandmusic.com/post/building-music-discovery-app-2/</link>
      <pubDate>Mon, 15 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://bitsandmusic.com/post/building-music-discovery-app-2/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer 1:&lt;/strong&gt; &lt;em&gt;This post is in continuation of the &lt;a href=&#34;https://bitsandmusic.com/post/building-music-discovery-app-1/&#34;&gt;last post&lt;/a&gt; about building a music discovery platform based on our paper: &lt;a href=&#34;https://archives.ismir.net/ismir2021/latebreaking/000054.pdf&#34;&gt;Bit Of This, Bit Of That: Revisiting Search and Discovery&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer 2:&lt;/strong&gt; &lt;em&gt;The design choices mentioned in this article are made with a low-cost setup in mind. As a result, some of the design choices may not be the most straightforward.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer 3&lt;/strong&gt; &lt;em&gt;You can explore our music discovery platform, This &amp;amp; That Music, here: &lt;a href=&#34;https://discover.thisandthatmusic.com/&#34;&gt;https://discover.thisandthatmusic.com/&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;h2 id=&#34;brief-summary&#34;&gt;Brief Summary&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Genre-fluid music&lt;/strong&gt; is any musical item (song or a playlist) which contains more than a single genre. Think &lt;a href=&#34;https://www.youtube.com/watch?v=r7qovpFAGrQ&#34;&gt;Old Town Road.&lt;/a&gt; Or &lt;a href=&#34;https://www.youtube.com/watch?v=eVTXPUF4Oz4&#34;&gt;Linkin Park&lt;/a&gt; &lt;a href=&#34;https://en.wikipedia.org/wiki/Nu_metal&#34;&gt;(Nu-Metal genre)&lt;/a&gt;. Genre-fluid music has been &lt;a href=&#34;https://www.waterandmusic.com/tracking-genre-diversity-and-fluidity-in-the-billboard-charts&#34;&gt;gaining popularity over the last few decades.&lt;/a&gt; However, the search interfaces in music apps like Spotify and Apple Music are still designed for single-genre searches. Our paper proposes a platform to discover gene-fluid music through a combination of expressive search and user experience created around the core idea of genre-fluid search.&lt;/p&gt;

&lt;p&gt;&lt;a href=&#34;https://bitsandmusic.com/post/building-music-discovery-app-1/&#34;&gt;Part 1&lt;/a&gt; of this series outlines the initial system architecture (&lt;code&gt;version1&lt;/code&gt;) used to build this platform. In &lt;code&gt;version1&lt;/code&gt;, we use a monolithic architecture with in-memory lookup objects as data sources, &lt;a href=&#34;https://www.postgresql.org/&#34;&gt;PostgreSQL&lt;/a&gt; for keyword search, &lt;a href=&#34;https://github.com/spotify/annoy&#34;&gt;Spotify ANNOY library&lt;/a&gt; for sparse genre-vector search, &lt;a href=&#34;https://radimrehurek.com/gensim/models/word2vec.html&#34;&gt;Gensim Word2vec&lt;/a&gt; for similarity-based search, and finally, package the whole system as a Python Flask application.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&#34;https://bitsandmusic.com/assets/images/discovery-app/version1.drawio.svg&#34;  /&gt;
  &lt;figcaption&gt;
      &lt;p&gt;&lt;b&gt;Previously designed version1 architecture. Monolithic in nature, with in-memory objects as the primary data source.&lt;/b&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt; 

&lt;h2 id=&#34;limitations&#34;&gt;Limitations&lt;/h2&gt;

&lt;p&gt;While the main strength of &lt;code&gt;version1&lt;/code&gt; design is its simplicity of implementation, there are quite a few shortcomings as well. These are as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In-memory litter:&lt;/strong&gt;  Instead of aggregated data sources such as Redis or MongoDB, we have multiple in-memory objects, which give memory a disjointed look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;High Memory Consumption:&lt;/strong&gt; Due to the in-memory objects that cannot be shared across multiple application instances, horizontal scaling becomes difficult.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monolith Problems:&lt;/strong&gt; The whole design set-up as a monolith makes the scaling challenges even worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Significant Search Time:&lt;/strong&gt; &lt;a href=&#34;https://bitsandmusic.com/post/building-music-discovery-app-1/#candidate-aggregation-module&#34;&gt;Sequential search to ANNOY search trees&lt;/a&gt; increases search time, worsening the user experience.&lt;/p&gt;

&lt;h2 id=&#34;design-refactoring&#34;&gt;Design Refactoring&lt;/h2&gt;

&lt;p&gt;The best way to refactor the design would be to consider the abovementioned limitations and make changes accordingly.&lt;/p&gt;

&lt;h3 id=&#34;reducing-memory-consumption&#34;&gt;Reducing Memory Consumption&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;We can start by migrating the in-memory app data (entity data) to Redis. It adds a bit of serialisation/deserialisation overhead, but on the plus side, our in-memory data can now be shared across multiple app instances.&lt;/li&gt;
&lt;li&gt;We can use the &lt;a href=&#34;https://en.wikipedia.org/wiki/Mmap&#34;&gt;mmap mode&lt;/a&gt; for Spotify ANNOY search to reduce memory consumption further. It searches the search tree without loading it into memory, reducing memory consumption. The downside of this is relatively slower (but still okay) search times.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;breaking-the-monolith&#34;&gt;Breaking the Monolith&lt;/h3&gt;

&lt;p&gt;As part of breaking the monolith to make scaling more manageable, we can remove the vector search component from the main application and make it its own service—our own &lt;a href=&#34;https://www.cloudflare.com/learning/ai/what-is-vector-database/&#34;&gt;vector database.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We further divide the main application into two parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The component that contains the core search logic, which we call the &lt;strong&gt;Core Discovery Service&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;public-facing web&lt;/strong&gt; component that forwards requests to the core discovery service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leaves us with three separate services: web, core discovery, and vector search.&lt;/p&gt;

&lt;h3 id=&#34;upgrading-the-query-parser-module&#34;&gt;Upgrading The Query Parser Module&lt;/h3&gt;

&lt;p&gt;We can upgrade the &lt;strong&gt;Query Parser Module&lt;/strong&gt; by using &lt;a href=&#34;https://www.turing.com/kb/a-comprehensive-guide-to-named-entity-recognition&#34;&gt;Named Entity Recognition&lt;/a&gt; as its core component and moving it to the core discovery service from the Javascript side for the final piece of refactoring. The purpose of this module is to automatically identify and extract genres (&lt;strong&gt;Rock Blues&lt;/strong&gt; playlists), their quantifiers (Blues playlists with &lt;strong&gt;a little&lt;/strong&gt; Rock), and other related entities, such as artists, from the user-written query and pass it onto the search workflows.&lt;/p&gt;

&lt;p&gt;With this upgrade, the user no longer needs to specify a search mode for their query explicitly. This module can intelligently decide if the query is to be categorised as a keyword or genre-based search and appropriately forward the request to PostgreSQL or search-related modules.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&#34;https://bitsandmusic.com/assets/images/discovery-app/version2.drawio.svg&#34;  /&gt;
  &lt;figcaption&gt;
      &lt;p&gt;&lt;b&gt;Version2 design architecture. Microservice-based.&lt;/b&gt;&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;services&#34;&gt;Services&lt;/h2&gt;

&lt;h3 id=&#34;web&#34;&gt;Web&lt;/h3&gt;

&lt;p&gt;This service, packaged as &lt;a href=&#34;https://fastapi.tiangolo.com/&#34;&gt;FastAPI,&lt;/a&gt; is the web layer of the system. It takes in the query from the browser and forwards the requests to the core discovery service endpoints. This way, we keep our core discovery service client-agnostic. It performs the following functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input validation&lt;/li&gt;
&lt;li&gt;Request Authentication&lt;/li&gt;
&lt;li&gt;User input conversion to an appropriate payload as accepted by the discovery service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can also customise this layer to add support for multiple client-specific workflows, keeping the discovery service untouched. Also, we can horizontally scale it with ease and keep it behind a reverse proxy solution such as &lt;a href=&#34;https://www.nginx.com/&#34;&gt;NGINX&lt;/a&gt; for even better performance.&lt;/p&gt;

&lt;h3 id=&#34;core-discovery&#34;&gt;Core Discovery&lt;/h3&gt;

&lt;p&gt;This is the main application layer of the system containing core search logic. This is also packaged as a FastAPI application, is not public-facing and only accepts requests from the web layer over HTTP protocol. It has the following data sources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Genre sparse vectors, stored &lt;strong&gt;in memory&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis&lt;/strong&gt; for the application data (entity lookups) needed for scoring and item detail population modules.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; For keyword-based searches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This service can be scaled by using multiple &lt;a href=&#34;https://fastapi.tiangolo.com/deployment/server-workers/?h=workers#gunicorn-with-uvicorn-workers&#34;&gt;Uvicorn workers&lt;/a&gt; or &lt;a href=&#34;https://fastapi.tiangolo.com/deployment/docker/&#34;&gt;packaging it in a Docker container&lt;/a&gt; and using something like &lt;a href=&#34;https://kubernetes.io/&#34;&gt;Kubernetes&lt;/a&gt; to manage multiple Docker containers.&lt;/p&gt;

&lt;h3 id=&#34;vector-search&#34;&gt;Vector Search&lt;/h3&gt;

&lt;p&gt;This is the last layer in our system containing code for vector search. It can be viewed as our custom-implemented vector database. This is also packaged as a FastAPI application accepting vector search requests from the core discovery service using HTTP protocol.&lt;/p&gt;

&lt;p&gt;Each genre vector search spawns two processes to search dot and angular metric trees, combine their result, and return the results to the core discovery service. Spawning of separate processes parallelises the search workflow, cutting the search time in half. This layer can also be scaled using Docker or Uvicorn workers. The ANNOY vector trees can still be shared among multiple processes using mmap.&lt;/p&gt;

&lt;h2 id=&#34;search-workflow&#34;&gt;Search Workflow&lt;/h2&gt;

&lt;p&gt;The search workflow in &lt;code&gt;version2&lt;/code&gt; remains similar to the one discussed in &lt;code&gt;version1&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The web server forwards the user query to the core discovery service.&lt;/li&gt;
&lt;li&gt;The query parser module parses the query, builds a query payload and forwards it to the caching module.&lt;/li&gt;
&lt;li&gt;The caching module checks whether the query results have already been stored in Redis. In case of a cache hit, steps 4–6 are skipped, and the result set is returned to the browser. In case of a cache miss, the query payload is forwarded to the Candidate Aggregation module.&lt;/li&gt;
&lt;li&gt;The candidate aggregation module sends the query to the vector search service, which returns the candidates from the ANNOY search trees.&lt;/li&gt;
&lt;li&gt;The scoring module scores the candidates with respect to the query.&lt;/li&gt;
&lt;li&gt;The filtering module removes duplicate candidates with regard to the item name and primary artist composition.&lt;/li&gt;
&lt;li&gt;The detail population module finally populates the result set candidates.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;thoughts&#34;&gt;Thoughts&lt;/h2&gt;

&lt;h3 id=&#34;the-good&#34;&gt;The Good&lt;/h3&gt;

&lt;h4 id=&#34;memory-consumption&#34;&gt;Memory Consumption&lt;/h4&gt;

&lt;p&gt;We gain over 7 GB of application memory by transferring the in-memory app data to Redis. And around 3 GB memory by using mmap for ANNOY search, although it comes at the expense of some speed.&lt;/p&gt;

&lt;h4 id=&#34;scalability&#34;&gt;Scalability&lt;/h4&gt;

&lt;p&gt;Now that we have broken down the monolith into the web, core discovery, and vector search services, this design version, &lt;code&gt;version2&lt;/code&gt;, renders itself far better for horizontal scaling than the previous version, as we can scale the services independently.&lt;/p&gt;

&lt;h3 id=&#34;the-bad&#34;&gt;The Bad&lt;/h3&gt;

&lt;h4 id=&#34;incomplete-memory-cleanup&#34;&gt;Incomplete Memory Cleanup&lt;/h4&gt;

&lt;p&gt;Since we need the genre vectors for the main scoring module, they must be in memory for the fastest possible retrieval. So, we are still left with some lookup data structures inside memory. This data, as before, cannot be shared with other instances of the core discovery service, making for a suboptimal horizontal scaling.&lt;/p&gt;

&lt;h4 id=&#34;redis-overhead&#34;&gt;Redis Overhead&lt;/h4&gt;

&lt;p&gt;Redis does make our data shareable across instances, but not without some added overhead. And as we move some of our app data from in-memory to Redis, it becomes more evident.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First of these is the serialisation/deserialisation overhead. Compared to in-memory objects, Redis cannot store the data with the same freedom (no support for integer keys, nested objects), leading to Redis serialisation/deserialisation code all over the place.&lt;/li&gt;
&lt;li&gt;Secondly, bulk retrievals can become time-consuming compared to in-memory lookups, especially when storing lists. For example, if we want to store vectors in Redis as lists, there is no way to make bulk calls similar to &lt;a href=&#34;https://redis.io/commands/mget/&#34;&gt;MGET.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&#34;suboptimal-vector-search-service&#34;&gt;Sub-Optimal Vector Search Service&lt;/h4&gt;

&lt;p&gt;The problem with our vector search service is that it is just like a vector database minus all the optimisations provided by the out-of-box solutions. Everything has plenty of scope for improvement, ranging from communication to serialisation/deserialisation protocols, from storage to search implementations.&lt;/p&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;We broke the monolith outlined in the design &lt;code&gt;version1&lt;/code&gt; into three smaller components. This new version enables smoother horizontal scaling, and the memory view seems much more aggregated than the previous version. The vector search service, however, appears as if it has been put together like the Frankenstein monster. Redis overhead is also something that needs to be addressed by replacing it with NoSQL storage. Another scope of improvement is aggregating the search/retrieval sources, including PostgreSQL, Redis, Spotify ANNOY search trees, Gensim word2vec indices, and the core discovery service in memory.&lt;/p&gt;

&lt;p&gt;Until the next iteration.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;em&gt;Ready to explore genre-fluid music? Visit our music discovery platform, This &amp;amp; Thats Music, here: &lt;a href=&#34;https://discover.thisandthatmusic.com/&#34;&gt;https://discover.thisandthatmusic.com/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Of Monoliths &amp; In-Memory Litter: Building A Music Discovery App - 1</title>
      <link>https://bitsandmusic.com/post/building-music-discovery-app-1/</link>
      <pubDate>Wed, 10 Jan 2024 00:00:00 +0000</pubDate>
      <guid>https://bitsandmusic.com/post/building-music-discovery-app-1/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer 1:&lt;/strong&gt; &lt;em&gt;This post is the first post in the series about building a music discovery platform based on our paper: Bit Of This, Bit Of That: Revisiting Search and Discovery&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer 2:&lt;/strong&gt; &lt;em&gt;The design choices mentioned in this article are made keeping in mind a low-cost setup. As a result, some of the design choices may not be the most straightforward ones.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclaimer 3&lt;/strong&gt; &lt;em&gt;You can explore our music discovery platform, This &amp;amp; That Music, here: &lt;a href=&#34;https://discover.thisandthatmusic.com/&#34;&gt;https://discover.thisandthatmusic.com/&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The term &lt;strong&gt;genre-fluid&lt;/strong&gt; music sounds so fancy and sophisticated and something you aren&#39;t supposed to know, yet you do. Even if you didn&#39;t realize you did, you do. After all, it&#39;s all around us. Think &lt;a href=&#34;https://www.youtube.com/watch?v=r7qovpFAGrQ&#34;&gt;Old Town Road&lt;/a&gt; by Lil Nas X. How would you describe its genre? Is it country, or is it hip-hop? Or is it both? And that&#39;s what genre fluidity is: any musical item consisting of more than a single genre.&lt;/p&gt;

&lt;p&gt;Over the past few decades, &lt;a href=&#34;https://www.waterandmusic.com/tracking-genre-diversity-and-fluidity-in-the-billboard-charts/&#34;&gt;genre-fluidity has been gaining prominence in the billboard charts&lt;/a&gt;. However, search interfaces have yet to keep up with this trend and continue to be designed for single-genre searches, while genre fluidity is served mostly via recommendations and curated playlists. This may soon change as recent advancements in AI, specifically &lt;a href=&#34;https://www.computerworld.com/article/3697649/what-are-large-language-models-and-how-are-they-used-in-generative-ai.html&#34;&gt;Large Language Models (LLMs)&lt;/a&gt;, offer more expansive and expressive access to content.&lt;/p&gt;

&lt;p&gt;So why are we talking about genre fluidity and old-town roads? A few years ago, we published a &lt;a href=&#34;https://archives.ismir.net/ismir2021/latebreaking/000054.pdf&#34;&gt;paper&lt;/a&gt; proposing a platform to discover genre-fluid music through a combination of expressive search and a user experience created around the core idea of genre-fluid search. This post explains the initial architecture we used to build that discovery system and the lessons we learned.&lt;/p&gt;

&lt;h2 id=&#34;search&#34;&gt;Search&lt;/h2&gt;

&lt;p&gt;Our proposed system enables users to search for music by combining different genres and specifying the proportion of each genre they want in the results. We call it &lt;strong&gt;genre-fluid search&lt;/strong&gt;. Adopting the design principles of existing apps, we keep the existing &lt;strong&gt;keyword search&lt;/strong&gt; (&amp;quot;Adele playlists&amp;quot;) along with our proposed search to keep the cognitive load as low as possible.&lt;/p&gt;

&lt;figure&gt;
  &lt;img src=&#34;https://bitsandmusic.com/assets/images/discovery-app/design.png&#34;  /&gt;
  &lt;figcaption&gt;
      &lt;p&gt;System search modes: Keyword search mode is the conventional search mode, while genre search mode is the proposed one.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id=&#34;data-summary&#34;&gt;Data Summary&lt;/h2&gt;

&lt;p&gt;Before going into the depth of the architecture, here is a summary of the data used for creating the system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are a total of 88 genres supported by the system.&lt;/li&gt;
&lt;li&gt;There are three entity types: artists, playlists, and tracks.&lt;/li&gt;
&lt;li&gt;The system has around one million playlists, seven million tracks, and a million artists.&lt;/li&gt;
&lt;li&gt;Each entity has demographic data, such as name, popularity, preview audio link, and cover image.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&#34;embeddings&#34;&gt;Embeddings&lt;/h2&gt;

&lt;p&gt;We use neural embeddings for our search and similarity-based recommendation purposes. There are two types of embeddings in our system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Genre-based embeddings&lt;/li&gt;
&lt;li&gt;Similarity-based embeddings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To get the &lt;strong&gt;genre-based embedding&lt;/strong&gt; for each entity, we annotate entities with their associated genres so each entity can be represented as an 88-length sparse vector. This vector consists of real values, meaning the nonzero values range between 0 and 1. In addition to entity-genre annotation, we make use of genre relationships and have those relationships reflected in the entity genre annotation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Similarity-based embeddings&lt;/strong&gt; are obtained by training &lt;a href=&#34;https://en.wikipedia.org/wiki/Word2vec&#34;&gt;word2vec models&lt;/a&gt; on our corpus, treating playlists as sentences and tracks (and artists) as words. These embeddings are 300-length vectors, with values ranging between 0 and 1. Finally, to perform Approximate Nearest Neighbor search on our embeddings, we use the &lt;a href=&#34;https://github.com/spotify/annoy&#34;&gt;Spotify ANNOY library&lt;/a&gt; and store the indexes for each entity on disk.&lt;/p&gt;

&lt;h2 id=&#34;data-structures-and-storage&#34;&gt;Data Structures And Storage&lt;/h2&gt;

&lt;p&gt;The main consideration for our search workflow is retrieval as fast as possible, which means it should be memory-based instead of disk or &lt;a href=&#34;https://en.wikipedia.org/wiki/Mmap&#34;&gt;mmap-based&lt;/a&gt;. We use the Python &lt;a href=&#34;https://docs.python.org/3/library/pickle.html&#34;&gt;pickle module&lt;/a&gt; to store our entity details objects as a dictionary, with entity-id being our key and entity details stored in an array ([name, preview audio link, cover image link]). Popularity lookups are stored in the same format as well. We store the sparse genre embeddings as &lt;a href=&#34;https://docs.fileformat.com/misc/h5/&#34;&gt;H5 files&lt;/a&gt;. Word2vec and Spotify ANNOY data structures are saved as their corresponding supported files.&lt;/p&gt;

&lt;h2 id=&#34;system-modules&#34;&gt;System Modules&lt;/h2&gt;

&lt;p&gt;The main components of this system, packaged as a Python Flask application, are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query Parser Module&lt;/li&gt;
&lt;li&gt;Candidate Aggregation Module&lt;/li&gt;
&lt;li&gt;Scoring Module&lt;/li&gt;
&lt;li&gt;Filtering Module&lt;/li&gt;
&lt;li&gt;Detail Population Module&lt;/li&gt;
&lt;li&gt;Caching Module&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&#34;query-parser-module&#34;&gt;Query Parser Module&lt;/h3&gt;

&lt;p&gt;This module converts the search query typed in natural language (&lt;em&gt;&amp;quot;rock with playlists some country&amp;quot;&lt;/em&gt;) into an 88-genre vector. We apply the genre-relationship rules mentioned in the embeddings section to the query vector.&lt;/p&gt;

&lt;h3 id=&#34;candidate-aggregation-module&#34;&gt;Candidate Aggregation Module&lt;/h3&gt;

&lt;p&gt;Even though we create a tree data structure built with &lt;a href=&#34;https://en.wikipedia.org/wiki/Binary_space_partitioning&#34;&gt;binary space partitioning&lt;/a&gt; using the Spotify ANNOY library from our genre embeddings, we use our scoring module instead of the conventional query-to-candidate distance as the final scoring metric. We create two indexes per entity type for genre embeddings to get the maximum possible candidates for a given query: one using the &lt;a href=&#34;https://en.wikipedia.org/wiki/Angular_distance&#34;&gt;Angular distance&lt;/a&gt; and another using the &lt;a href=&#34;https://en.wikipedia.org/wiki/Dot_product&#34;&gt;Dot Product distance&lt;/a&gt;. We sequentially query the indexes and retrieve &lt;code&gt;N=10,000&lt;/code&gt; candidates from the indexes using the candidate aggregator module.&lt;/p&gt;

&lt;h3 id=&#34;scoring-module&#34;&gt;Scoring Module&lt;/h3&gt;

&lt;p&gt;We use our custom scoring function to calculate a score for each of those 10,000 candidates, where we consider things such as the candidate values corresponding to each query genre, missing genres in a candidate, additional candidate genres not specified in the query, and candidate popularity.&lt;/p&gt;

&lt;h3 id=&#34;filtering-module&#34;&gt;Filtering Module&lt;/h3&gt;

&lt;p&gt;Since our corpus contains user-created playlists, it is possible to have multiple playlists with the same name. Also, many playlists for a given search query can mainly comprise the same artist. To avoid returning duplicate results in their name or primary artist composition, we have a separate module to filter out such items and finally return &lt;code&gt;K=300&lt;/code&gt; result items sorted by their score.&lt;/p&gt;

&lt;h3 id=&#34;detail-population-module&#34;&gt;Detail Population Module&lt;/h3&gt;

&lt;p&gt;This is where we get details for each result item and prepare the final paginated result set to be sent back to the client.&lt;/p&gt;

&lt;h3 id=&#34;caching-module&#34;&gt;Caching Module&lt;/h3&gt;

&lt;p&gt;Since our system serves results without considering user personalization, each query should get the same response. With this in mind, we can cache our query results using &lt;a href=&#34;https://redis.io/&#34;&gt;Redis,&lt;/a&gt; as it is the simplest and the most common solution. We serialize each query as a string and store the indexes returned by the filtering module as the value for each query. Only the query and detail population modules get called for repeated searches, bypassing the rest.&lt;/p&gt;

&lt;h2 id=&#34;overall-system-design&#34;&gt;Overall System Design&lt;/h2&gt;

&lt;figure&gt;
  &lt;img src=&#34;https://bitsandmusic.com/assets/images/discovery-app/version1.drawio.svg&#34;  /&gt;
  &lt;figcaption&gt;
      &lt;p&gt;System Design Architecture `version1`. Monolithic. Simple.&lt;/p&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Here&#39;s how the application design looks, zoomed out.
The query &lt;em&gt;&amp;quot;Rock playlists with blues&amp;quot;&lt;/em&gt; is first entered in a search box in a web application.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The query parser module parses this text and converts it into an 88-length vector.&lt;/li&gt;
&lt;li&gt;The query vector is sent to the server, where the caching module checks whether the query results have already been stored in Redis. In case of a cache hit, steps 3-5 are skipped, and the result set is returned to the browser. In case of a cache miss, the query vector is forwarded to the Candidate Aggregator module.&lt;/li&gt;
&lt;li&gt;The candidate aggregator module uses this query vector to query the Spotify ANNOY indices, retrieves 10000 candidates, and passes it onto the scorer module.&lt;/li&gt;
&lt;li&gt;The scorer module runs our custom scoring function to calculate scores for each of those 10000 candidates.&lt;/li&gt;
&lt;li&gt;The filtering module removes duplicate candidates by name and primary artist. If the resulting set has items under a certain threshold &lt;code&gt;K&lt;/code&gt;, it adds the items removed in the filtering stage.&lt;/li&gt;
&lt;li&gt;Finally, the demographic data is retrieved for the first &lt;code&gt;k=30&lt;/code&gt; candidates to be returned to the browser.&lt;/li&gt;
&lt;li&gt;Candidates calculated are stored in Redis corresponding to the search query.&lt;/li&gt;
&lt;li&gt;In order to support existing keyword-based searches, we use the &lt;a href=&#34;https://www.postgresql.org/&#34;&gt;PostgreSQL database&lt;/a&gt; as it supports full-text search from disk with a decent performance.&lt;br&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&#34;thoughts&#34;&gt;Thoughts&lt;/h2&gt;

&lt;h3 id=&#34;the-good&#34;&gt;The Good&lt;/h3&gt;

&lt;p&gt;The best thing about this setup is its simplicity. Its monolithic design is as simple as possible, adding little coding complexity overhead. The straightforward nature of this design makes it an ideal choice for prototyping. The design is also modular, making it easier to move away from monolithic architecture in the future. Lastly, using PostgreSQL over other full-text solutions, such as Elasticsearch, works fine without needing the memory most memory-based full-text search solutions need.&lt;/p&gt;

&lt;h3 id=&#34;the-bad&#34;&gt;The Bad&lt;/h3&gt;

&lt;h4 id=&#34;inmemory-litter&#34;&gt;In-Memory Litter&lt;/h4&gt;

&lt;p&gt;Using pickle module for serializing data to disk is excellent for entry-level applications and prototypes. They are even more efficient in data storage than other candidates, such as JSON. However, there are better options than pickle when it comes to interoperability, with JSON being one of them. Additionally, moving across different Python versions can also lead to problems.&lt;/p&gt;

&lt;p&gt;Another problem that having in-memory objects creates is high memory consumption. While data access is high-speed and straightforward, it also makes deploying the application more complex, as the memory needed for a single instance of the application equals the size of these in-memory objects. This data tied to an instance&#39;s memory cannot be shared with other application instances.&lt;/p&gt;

&lt;h4 id=&#34;search-time&#34;&gt;Search Time&lt;/h4&gt;

&lt;p&gt;Owing to our sequential search for Spotify ANNOY indices and the scoring function being run over 10k candidates, the search time is well over 5 seconds for a new query, which is far over the ideal response time.&lt;/p&gt;

&lt;h4 id=&#34;caching-at-a-cost&#34;&gt;Caching At A Cost&lt;/h4&gt;

&lt;p&gt;Caching using Redis is great until it is not. It usually starts with simpler string serialized keys and values, which later becomes a whole thing of writing logic to serialize and deserialize data.&lt;/p&gt;

&lt;h4 id=&#34;monolith&#34;&gt;Monolith&lt;/h4&gt;

&lt;p&gt;The monolithic architecture has notable drawbacks, such as the need to manage, test, and deploy all components as one unit. Additionally, the entire system is bound to a single programming language, which may become less than ideal as the application expands and becomes more complex.&lt;/p&gt;

&lt;h3 id=&#34;the-ugly&#34;&gt;The Ugly&lt;/h3&gt;

&lt;p&gt;Deploying an application that requires 32GB of memory presents a challenge due to the complexity and high cost of horizontal scaling. For instance, the monthly cost of an &lt;a href=&#34;https://instances.vantage.sh/aws/ec2/t4g.2xlarge&#34;&gt;AWS t4g.2xlarge instance&lt;/a&gt;, which could support such an application, is approximately $180.&lt;/p&gt;

&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The design mentioned above is a decent place to start as it lets the main application idea take focus, owing to its simplicity. Its limiting factors, however, are quite a few and critical as well. Monolith is all well and good up to a certain point, beyond which it stops making sense. In-memory objects lead to substantial memory consumption, complicating the scaling and deployment processes. As we aim to improve this design, our approach would involve reducing memory usage and transitioning from a monolithic structure to a more modular, component-based design.&lt;/p&gt;

&lt;p&gt;Until the next design.&lt;/p&gt;

&lt;hr&gt;

&lt;p&gt;&lt;em&gt;Ready to explore genre-fluid music? Visit our music discovery platform, This &amp;amp; Thats Music, here: &lt;a href=&#34;https://discover.thisandthatmusic.com/&#34;&gt;https://discover.thisandthatmusic.com/&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
