<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dragonfly</title>
    <description>The latest articles on DEV Community by Dragonfly (@dragonflydbio).</description>
    <link>https://dev.to/dragonflydbio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1080697%2Fb493ba01-6546-42f8-af16-c09cc4b6a948.jpg</url>
      <title>DEV Community: Dragonfly</title>
      <link>https://dev.to/dragonflydbio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dragonflydbio"/>
    <language>en</language>
    <item>
      <title>Dragonfly Cloud: Now Available in AWS Marketplace</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Tue, 26 Nov 2024 16:44:25 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/dragonfly-cloud-now-available-in-aws-marketplace-2o8h</link>
      <guid>https://dev.to/dragonflydbio/dragonfly-cloud-now-available-in-aws-marketplace-2o8h</guid>
      <description>&lt;p&gt;Today we're excited to announce that Dragonfly Cloud is &lt;a href="https://aws.amazon.com/marketplace/pp/prodview-evf32lpo652gq" rel="noopener noreferrer"&gt;available in the AWS Marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Dragonfly Cloud has always enabled customers to deploy data stores in their preferred AWS region and connect to existing AWS infrastructure using VPC peering. Now, you can streamline billing by purchasing Dragonfly Cloud in-memory data stores directly through the AWS Marketplace. Any Dragonfly Cloud usage will be included in your monthly AWS bill, allowing you to use your AWS account credits as well.&lt;/p&gt;

&lt;p&gt;To purchase Dragonfly Cloud through the AWS Marketplace, simply visit the &lt;a href="https://aws.amazon.com/marketplace/pp/prodview-evf32lpo652gq?sr=0-1&amp;amp;ref_=beagle&amp;amp;applicationId=AWSMPContessa" rel="noopener noreferrer"&gt;listing page&lt;/a&gt; and click 'Subscribe'. You will be redirected to create an account on &lt;a href="https://dragonflydb.cloud/" rel="noopener noreferrer"&gt;Dragonfly Cloud&lt;/a&gt;,and once that is complete, all billing will be integrated with the rest of your monthly AWS bill.&lt;/p&gt;

&lt;p&gt;Dragonfly Cloud pricing in the AWS Marketplace is the same as&lt;br&gt;
our &lt;a href="https://www.dragonflydb.io/pricing" rel="noopener noreferrer"&gt;standard pricing&lt;/a&gt;. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Case Study: Migrating from Redis to Dragonfly to Scale IoT Infrastructure</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Wed, 09 Oct 2024 08:56:19 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/case-study-migrating-from-redis-to-dragonfly-to-scale-iot-infrastructure-511e</link>
      <guid>https://dev.to/dragonflydbio/case-study-migrating-from-redis-to-dragonfly-to-scale-iot-infrastructure-511e</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Over the past few decades, globalization has brought societies together faster than ever throughout history.&lt;br&gt;
Social media, of course, has played a starring role in this process.&lt;br&gt;
But even before the internet seeped into most aspects of our lives, the major driving force behind globalization had already taken hold: transportation.&lt;/p&gt;

&lt;p&gt;While the internet has allowed us to seamlessly transport ideas, we still rely heavily on vehicles for people and goods.&lt;br&gt;
This is where &lt;a href="https://www.linkedin.com/company/smartgpsbr/" rel="noopener noreferrer"&gt;SmartGPS&lt;/a&gt; steps up as the fastest-growing vehicle tracking and IoT startup on the market.&lt;br&gt;
With over 300,000 devices connected in Brazil and six other Latin American countries so far, SmartGPS is building a dynamic ecosystem that delivers real-time tracking and data insights to clients.&lt;br&gt;
As they amass larger and larger quantities of data, they hope to contribute to sustainable mobility, helping cities evolve intelligently and reduce traffic congestion and pollution.&lt;/p&gt;

&lt;p&gt;To do this well, SmartGPS needs to be able to quickly process large volumes of data in real-time.&lt;br&gt;
To reduce the load on the primary database, retrieve data quickly, and increase throughput under highly concurrent requests,&lt;br&gt;
it was clear to SmartGPS CTO, Eduardo Malpeli, that a strong in-memory data store would need to be employed.&lt;br&gt;
While he and his team started with Redis Labs, as their business scaled, he realized quickly that the existing solution was limiting their growth.&lt;br&gt;
After evaluating Dragonfly, they discovered that it removed the limitations traditionally associated with in-memory data stores.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redis Provided Speed But Failed to Scale
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Redis was not as performant as we thought it would be.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;According to Malpeli, because of how it records, stores, and flushes data, SmartGPS needed a trustworthy in-memory data store.&lt;br&gt;
Providing real-time tracking and insights requires that they collect, record, and create a key for the data from vehicles to keep it in order and ready to retrieve instantly.&lt;/p&gt;

&lt;p&gt;While they do occasionally flush this data to a primary database, storing and retrieving directly from it for every action would not allow them the speed they need to provide their service to their customers.&lt;/p&gt;

&lt;p&gt;It made sense, therefore, to use an in-memory data store as part of their data infrastructure stack.&lt;br&gt;
Known for its incredible speed, SmartGPS went for Redis via Redis Cloud for their primary in-memory data store.&lt;/p&gt;

&lt;p&gt;'Redis Struggled to Keep Up During Peak Traffic Hours'&lt;/p&gt;

&lt;p&gt;Unfortunately, while Redis provided the speed Malpeli and his team needed, it proved to impede growth. "Redis was not as performant as we thought it would be," he said. As SmartGPS' customer base grew, the volume of data soared, and their traffic patterns became increasingly erratic, Redis struggled to keep up during peak traffic hours, causing performance hiccups.&lt;/p&gt;

&lt;p&gt;After this caused a significant amount of data loss, Malpeli knew SmartGPS needed to find an in-memory data store alternative that could provide the speed of Redis as well as the scale they required.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dragonfly Was the Better Choice
&lt;/h2&gt;

&lt;p&gt;'With Dragonfly, our service was kept up and running smoothly!'&lt;/p&gt;

&lt;p&gt;Desperate for another option, the SmartGPS team stumbled across several options, Dragonfly among them. "Dragonfly was the best because it is compatible with Redis and is the most mature product among the choices," Malpeli said.&lt;/p&gt;

&lt;p&gt;The easy migration from Redis and the trustworthiness of the technology, along with the promise of better scale, meant that they had found their alternative.&lt;/p&gt;

&lt;p&gt;During the evaluation process, SmartGPS conducted performance benchmarks by replicating their traffic patterns in a &lt;a href="https://dragonflydb.cloud/" rel="noopener noreferrer"&gt;Dragonfly Cloud&lt;/a&gt; trial account, ensuring the solution could handle their data traffic with ease.&lt;/p&gt;

&lt;p&gt;The result was impressive—Dragonfly met and exceeded expectations, enabling SmartGPS to manage their peak traffic loads effectively without compromising performance or data integrity.&lt;br&gt;
"With Dragonfly, our service was kept up and running smoothly!"&lt;/p&gt;

&lt;p&gt;'Changing the Data Store Endpoint'&lt;/p&gt;

&lt;p&gt;Once they felt comfortable with the stability of their data, it was decided that they would complete the migration and move Dragonfly into production. Malpeli mentioned specifically how easy the process was. They "...didn't even have to change any code on our end!"&lt;br&gt;
All they needed to do was change the endpoint that their production code was pointing to, "...and it just worked!"&lt;/p&gt;

&lt;p&gt;Malpeli mentioned that the two biggest selling points while evaluating Dragonfly Cloud were compatibility and reliability.&lt;br&gt;
They were able to seamlessly integrate Dragonfly with their existing Redis APIs, providing a familiar framework for their team of developers.&lt;/p&gt;

&lt;p&gt;Their own service's reliability increased dramatically. "With Redis, we would have hiccups that dramatically impacted performance every week or two, we've had none since we migrated to Dragonfly."&lt;/p&gt;

&lt;h2&gt;
  
  
  Success with Dragonfly
&lt;/h2&gt;

&lt;p&gt;'We experimented with multiple options, but nothing came close to Dragonfly.'&lt;/p&gt;

&lt;p&gt;Before migrating to Dragonfly, SmartGPS resorted to throttling new customer onboarding to accommodate their existing infrastructure's struggles with handling all the new devices.&lt;/p&gt;

&lt;p&gt;Now, with Dragonfly supporting speed and scale, they are able to onboard customers confidently and grow without fear of their service going down.&lt;/p&gt;

&lt;p&gt;Malpeli left us with, "I would encourage any developer exploring alternatives to Redis to [give Dragonfly a try (&lt;a href="https://dragonflydb.cloud/" rel="noopener noreferrer"&gt;https://dragonflydb.cloud/&lt;/a&gt;). Test it, set your own benchmarks, and see for yourself. We experimented with multiple options, but nothing came close to Dragonfly.&lt;/p&gt;

&lt;p&gt;The ease of implementation and performance demonstrated by Dragonfly were unparalleled."&lt;/p&gt;

</description>
      <category>casestudy</category>
      <category>migration</category>
      <category>iot</category>
      <category>redis</category>
    </item>
    <item>
      <title>A Preview of Dragonfly Cluster</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Tue, 08 Oct 2024 19:54:36 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/bitmaps-in-dragonfly-compact-data-with-powerful-analytics-4j4d</link>
      <guid>https://dev.to/dragonflydbio/bitmaps-in-dragonfly-compact-data-with-powerful-analytics-4j4d</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Dragonfly excels at high performance and vertical scaling, making it a top choice for demanding modern data workloads.&lt;br&gt;
Soon, Dragonfly Cluster&lt;sup id="fnref1"&gt;1&lt;/sup&gt; will offer horizontal scalability as well, expanding its capabilities even further.&lt;br&gt;
In this article, I want to show you how to run Dragonfly Cluster and provide a concise overview of the Dragonfly Cluster internal processes.&lt;/p&gt;


&lt;h2&gt;
  
  
  Dragonfly Cluster Overview
&lt;/h2&gt;

&lt;p&gt;Like &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/" rel="noopener noreferrer"&gt;Redis Cluster&lt;/a&gt;, Dragonfly Cluster achieves horizontal scalability through sharding.&lt;br&gt;
A cluster comprises one or more shards, each consisting of a master node (primary) and zero or more replicas.&lt;br&gt;
Data is distributed across shards using a slot-based approach.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The hashing space in the cluster is divided into 16,384 slots.&lt;/li&gt;
&lt;li&gt;Each key is hashed into a slot. The hash slot is computed from the key name using &lt;a href="https://en.wikipedia.org/wiki/Cyclic_redundancy_check" rel="noopener noreferrer"&gt;the CRC16 algorithm&lt;/a&gt; and then taking the modulus of 16,384.&lt;/li&gt;
&lt;li&gt;Each node in a cluster is responsible for a subset of these slots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="/assets/blog/a-preview-of-dragonfly-cluster/dragonfly-cluster.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/a-preview-of-dragonfly-cluster/dragonfly-cluster.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dragonfly Cluster supports dynamic rebalancing without downtime.&lt;br&gt;
Hash slots can be seamlessly migrated from one node to another, allowing for the addition, removal, or resizing of nodes without interrupting operations.&lt;/p&gt;

&lt;p&gt;Dragonfly Cluster supports multi-key operations as long as all involved keys (in a multi-key command, in a transaction, or in a Lua script) reside within the same hash slot.&lt;br&gt;
To ensure this, Dragonfly employs hash tags.&lt;br&gt;
With hash tags, the system calculates the hash slot based solely on the content within curly braces &lt;code&gt;{}&lt;/code&gt; of a key.&lt;br&gt;
This mechanism allows users to explicitly control key distribution across shards.&lt;/p&gt;

&lt;p&gt;If a client requests keys from a node, but those keys belong to a hash slot managed by a different node, the client receives a &lt;code&gt;-MOVED&lt;/code&gt; redirection error.&lt;br&gt;
This ensures that the client can always find the correct node handling the requested keys.&lt;/p&gt;

&lt;p&gt;Dragonfly only provides a server but not a control plane to manage cluster deployments.&lt;br&gt;
Node health monitoring, automatic failovers, and slot redistribution are out of the scope of Dragonfly backend functionality&lt;br&gt;
and will be provided as part of the &lt;a href="https://dragonflydb.cloud/" rel="noopener noreferrer"&gt;Dragonfly Cloud&lt;/a&gt; service.&lt;/p&gt;

&lt;p&gt;Dragonfly Cluster offers seamless migration for existing Redis Cluster clients.&lt;br&gt;
It fully adheres to Redis Cluster's client-facing behavior, ensuring zero code changes for applications.&lt;br&gt;
However, Dragonfly takes a fundamentally different approach to cluster management.&lt;/p&gt;

&lt;p&gt;Unlike Redis Cluster's distributed consensus model, Dragonfly adopts a centralized management strategy.&lt;br&gt;
Nodes operate independently, without direct communication or shared state.&lt;br&gt;
This design choice provides a single source of truth and enhances simplicity, reliability, and performance.&lt;/p&gt;


&lt;h2&gt;
  
  
  Cluster Modes
&lt;/h2&gt;

&lt;p&gt;Dragonfly has two cluster modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Emulated Cluster Mode&lt;/strong&gt; (which can be enabled by &lt;code&gt;--cluster_mode=emulated&lt;/code&gt;) is fully compatible with the stand-alone mode,&lt;br&gt;
supporting &lt;a href="https://www.dragonflydb.io/docs/command-reference/server-management/select" rel="noopener noreferrer"&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/a&gt; and multi-key operations while also providing cluster commands like &lt;code&gt;CLUSTER SHARDS&lt;/code&gt;.&lt;br&gt;
It functions as a single-node Dragonfly instance and does not include horizontal scaling, resharding, or certain advanced cluster features.&lt;br&gt;
This mode is ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Development &amp;amp; Testing Environments:&lt;/strong&gt; Provide a simplified setup for rapid iteration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration Phases:&lt;/strong&gt; Serve as an interim solution when transitioning from a stand-alone to a clustered setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-Constrained Scenarios:&lt;/strong&gt; A Dragonfly instance in emulated cluster mode can optimize resource utilization by acting as a replica for multiple shards, allowing a single node to replicate several cluster nodes efficiently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Multi-Node Cluster Mode&lt;/strong&gt; (which can be enabled by &lt;code&gt;--cluster_mode=yes&lt;/code&gt;) is the Dragonfly Cluster we are talking about in this blog post.&lt;br&gt;
It has certain limitations compared to stand-alone or emulated modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.dragonflydb.io/docs/command-reference/server-management/select" rel="noopener noreferrer"&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/a&gt; command is not permitted.&lt;/li&gt;
&lt;li&gt;All keys in a multi-key operation (i.e., multi-key commands, transactions, and Lua scripts) must belong to the same slot. Otherwise, a &lt;code&gt;CROSSSLOT&lt;/code&gt; error is returned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dragonfly supports some &lt;code&gt;CLUSTER&lt;/code&gt; commands for compatibility with Redis Cluster clients. These commands primarily provide informational data about the cluster setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER HELP&lt;/code&gt;: Lists available CLUSTER commands.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER MYID&lt;/code&gt;: Returns the node ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER SHARDS&lt;/code&gt;: Displays information about cluster shards.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER SLOTS&lt;/code&gt;: Lists all slots and their associated nodes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER NODES&lt;/code&gt;: Shows information about all nodes in the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER INFO&lt;/code&gt;: Provides general information about the cluster.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Dragonfly Cluster Management
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;DFLYCLUSTER&lt;/code&gt; Commands
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;DFLYCLUSTER&lt;/code&gt; commands are specific to Dragonfly and offer more advanced cluster management capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt;: Manages node roles, slot assignments, and migration processes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DFLYCLUSTER GETSLOTINFO&lt;/code&gt;: Provides in-depth statistics about slot utilization, including key count, memory usage, and read/write operations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DFLYCLUSTER FLUSHSLOTS&lt;/code&gt;: Efficiently clears data from specific slots.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DFLYCLUSTER SLOT-MIGRATION-STATUS&lt;/code&gt;: Monitors the progress of slot migrations, indicating the current state and completion status.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Cluster Creation
&lt;/h3&gt;

&lt;p&gt;To begin building a Dragonfly cluster, we'll start by launching two separate Dragonfly instances in cluster mode.&lt;br&gt;
Each instance will have a unique ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ./dragonfly &lt;span class="nt"&gt;--cluster_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="nt"&gt;--admin_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;31001 &lt;span class="nt"&gt;--port&lt;/span&gt; 30001
&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ./dragonfly &lt;span class="nt"&gt;--cluster_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="nt"&gt;--admin_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;31002 &lt;span class="nt"&gt;--port&lt;/span&gt; 30002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the instances are running, we need to retrieve their unique IDs by using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 31001 CLUSTER MYID
&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 31002 CLUSTER MYID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, the two unique IDs have prefixes &lt;code&gt;97486c...&lt;/code&gt; and &lt;code&gt;728cf2...&lt;/code&gt; respectively.&lt;br&gt;
Now we can create a cluster config in JSON format (as a string) by plugging in the unique IDs and IP addresses of the two nodes above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"97486c9d7e0507e1edb2dfba4655224d5b61c5e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30001&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16383&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"728cf25ecd4d1230805754ff98939321d72d23ef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30002&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using the &lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt; command, the JSON string is sent to both our nodes and the Dragonfly Cluster is created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slot Migration
&lt;/h3&gt;

&lt;p&gt;Slot migration is a critical operation that can potentially lead to data loss if not executed carefully.&lt;br&gt;
It's essential for adjusting cluster configuration to meet changing demands.&lt;br&gt;
Dragonfly supports concurrent slot migrations, but only one migration can be in progress between any two specific nodes at a given time.&lt;br&gt;
This means multiple migrations can be initiated simultaneously across different node pairs within a cluster.&lt;br&gt;
To initiate and complete a slot migration, use the &lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt; command, specifying an additional &lt;code&gt;migrations&lt;/code&gt; field in the JSON configuration.&lt;br&gt;
In the example below, I decided to move slots &lt;code&gt;[1000, 8000]&lt;/code&gt; to another node, and here's how the JSON configuration string looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"97486c9d7e0507e1edb2dfba4655224d5b61c5e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30001&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16383&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"728cf25ecd4d1230805754ff98939321d72d23ef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30002&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"migrations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"node_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"97486c9d7e0507e1edb2dfba4655224d5b61c5e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31001&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To maintain cluster consistency during slot migrations, the &lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt; command is propagated to all nodes, even those not directly involved.&lt;br&gt;
This ensures that all nodes have an up-to-date view of the cluster configuration, preventing inconsistencies that might arise from concurrent migration processes or failures.&lt;br&gt;
To monitor the progress of a slot migration, use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 31002 DFLYCLUSTER SLOT-MIGRATION-STATUS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the migration status shows &lt;code&gt;FINISHED&lt;/code&gt;, the new cluster configuration (with updated &lt;code&gt;slot_ranges&lt;/code&gt;) can be applied to all nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"97486c9d7e0507e1edb2dfba4655224d5b61c5e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30001&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16383&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"728cf25ecd4d1230805754ff98939321d72d23ef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30002&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upon applying the new cluster configuration, data from migrated slots is permanently erased from the source node.&lt;br&gt;
Migrations can be canceled by removing the migration field from the configuration while preserving slot assignments.&lt;br&gt;
If the updated configuration isn't applied promptly after migration completion, the cluster enters a transitional state where nodes have inconsistent slot information.&lt;br&gt;
Clients may initially be redirected to the source node for migrated slots, but subsequent requests to the source node will be correctly routed to the target node.&lt;br&gt;
Although this temporary inconsistency exists, it doesn't compromise data integrity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicas
&lt;/h3&gt;

&lt;p&gt;Dragonfly replication is configured using the standard &lt;a href="https://www.dragonflydb.io/docs/command-reference/server-management/replicaof" rel="noopener noreferrer"&gt;&lt;code&gt;REPLICAOF&lt;/code&gt;&lt;/a&gt; command, identical to non-clustered setups.&lt;br&gt;
Despite replication details being included in the cluster configuration, replicas function independently, copying data directly from the master node.&lt;br&gt;
This means replicas replicate all data from the master mode, regardless of slot assignment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Slot Migration Process Under the Hood
&lt;/h2&gt;

&lt;p&gt;Dragonfly utilizes a set of internal commands, prefixed with &lt;code&gt;DFLYMIGRATE&lt;/code&gt;, to manage the slot migration process.&lt;br&gt;
The slot migration process involves several carefully coordinated steps to ensure data integrity and seamless transitions between nodes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initiating Migration:&lt;/strong&gt;
The process begins with the &lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt; command sent to the source and target nodes to configure migration parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preparing the Target Node:&lt;/strong&gt;
The source node sends &lt;code&gt;DFLYMIGRATE INIT [SOURCE_NODE_ID, SHARDS_NUM, SLOT_RANGES]&lt;/code&gt; to the target node.
The target node responds &lt;code&gt;OK&lt;/code&gt;, indicating it is ready to receive data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting Up Data Transfer:&lt;/strong&gt;
For each storage thread-shard (a data segment handled by a single thread), the source node sends &lt;code&gt;DFLYMIGRATE FLOW [SOURCE_NODE_ID, FLOW_ID]&lt;/code&gt;.
Each &lt;code&gt;DFLYMIGRATE FLOW&lt;/code&gt; command sets up a connection for data transfer, confirmed by an &lt;code&gt;OK&lt;/code&gt; response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transferring Data:&lt;/strong&gt;
Using the established connections from the &lt;code&gt;DFLYMIGRATE FLOW&lt;/code&gt; commands, the source node serializes and transfers data to the target node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finalizing Migration:&lt;/strong&gt;
After the data transfer, the migrated slots on the source node are blocked to prevent further data changes.
The source node then sends a finalization request for each FLOW connection to conclude the data transfer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completing the Process:&lt;/strong&gt;
The source node issues &lt;code&gt;DFLYMIGRATE ACK [SOURCE_NODE_ID, ATTEMPT_ID]&lt;/code&gt; to the target node to finalize the entire migration.
The target node responds with &lt;code&gt;ATTEMPT_ID&lt;/code&gt;, completing the migration.
The &lt;code&gt;ATTEMPT_ID&lt;/code&gt; is used to handle errors that may arise during the finalization process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While most steps in the migration process are straightforward, step 4 above requires a more detailed explanation due to its complexity.&lt;br&gt;
In Dragonfly, there are two sources of data that need to be sent to the target node: the &lt;strong&gt;snapshot&lt;/strong&gt; and the &lt;strong&gt;journal&lt;/strong&gt;.&lt;br&gt;
We will dive deeper into these two sources of data below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Snapshot Creation
&lt;/h3&gt;

&lt;p&gt;To create a snapshot, Dragonfly iterates through each storage shard, serializing the data.&lt;br&gt;
In the absence of write requests, this is a linear process where each bucket is serialized one by one.&lt;br&gt;
Periodic pauses are incorporated to allow the system to process new requests, ensuring minimal disruption.&lt;/p&gt;

&lt;p&gt;The process becomes more complex when write requests occur during serialization.&lt;br&gt;
Unlike Redis, which uses a fork mechanism to prevent data changes during serialization,&lt;br&gt;
&lt;a href="https://www.dragonflydb.io/blog/balanced-vs-unbalanced" rel="noopener noreferrer"&gt;Dragonfly employs a more sophisticated mechanism&lt;/a&gt;, incorporating versioning and pre-update hooks, to create snapshots without spiking the memory usage or causing latency issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Journal Serialization
&lt;/h3&gt;

&lt;p&gt;While handling the snapshot, Dragonfly also manages a journal, which logs all recent write operations.&lt;br&gt;
These journal entries are serialized and sent to the target node along with the snapshot data.&lt;/p&gt;

&lt;p&gt;Let's look at a small example to illustrate the process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are several data entries: &lt;strong&gt;A&lt;/strong&gt;, &lt;strong&gt;B&lt;/strong&gt;, &lt;strong&gt;C&lt;/strong&gt;, and &lt;strong&gt;D&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Data entries &lt;strong&gt;A&lt;/strong&gt; and &lt;strong&gt;B&lt;/strong&gt; are serialized and sent to the target node.&lt;/li&gt;
&lt;li&gt;A new &lt;code&gt;MSET&lt;/code&gt; command is issued by the client, updating &lt;strong&gt;B&lt;/strong&gt; and &lt;strong&gt;D&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Because &lt;strong&gt;B&lt;/strong&gt; is already serialized previously, we do nothing with it for now.&lt;/li&gt;
&lt;li&gt;Data entry &lt;strong&gt;D&lt;/strong&gt; is serialized and sent to the target node.&lt;/li&gt;
&lt;li&gt;The journal gets the update about &lt;strong&gt;B&lt;/strong&gt; and &lt;strong&gt;D&lt;/strong&gt;, serializes it, and sends it to the target node.&lt;/li&gt;
&lt;li&gt;Finally, data entry &lt;strong&gt;C&lt;/strong&gt; is serialized and sent to the target node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="/assets/blog/a-preview-of-dragonfly-cluster/journal-serialization.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/a-preview-of-dragonfly-cluster/journal-serialization.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By following this process, Dragonfly ensures that the target node receives a consistent version of the source node's data,&lt;br&gt;
including all recent write operations during the slot migration process.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Dragonfly Cluster is a powerful addition to the Dragonfly ecosystem, offering horizontal scalability for even the most demanding workloads.&lt;br&gt;
Modern servers can come equipped with over a hundred cores and several hundred gigabytes of memory.&lt;br&gt;
While Dragonfly Cluster offers significant scalability advancements, vertical scaling should be prioritized if feasible.&lt;br&gt;
Therefore, it is advisable to evaluate the potential for vertical scaling before implementing a cluster.&lt;br&gt;
If you're uncertain about future vertical scaling needs, you can start with an emulated cluster and switch to a real cluster as your requirements grow.&lt;/p&gt;

&lt;p&gt;In the meantime, if you are curious to see how Dragonfly can scale with your needs and workloads, the easiest way to get started is by using the cloud service backed by the Dragonfly core team.&lt;br&gt;
Try &lt;a href="https://dragonflydb.cloud/" rel="noopener noreferrer"&gt;Dragonfly Cloud&lt;/a&gt; today and experience the power of seamless scaling firsthand!&lt;/p&gt;







&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;At the time of writing, Dragonfly Cluster is not officially released yet.&lt;br&gt;
However, many features and cluster-related commands described in this article are already available in the Dragonfly main branch.&lt;br&gt;
We are actively testing and improving this amazing feature. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>engineering</category>
      <category>analytics</category>
      <category>dragonfly</category>
    </item>
    <item>
      <title>Dragonfly's New Sorted Set Implementation</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Fri, 23 Aug 2024 14:03:07 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/dragonflys-new-sorted-set-implementation-4dnk</link>
      <guid>https://dev.to/dragonflydbio/dragonflys-new-sorted-set-implementation-4dnk</guid>
      <description>&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Redis offers a plethora of data types to cater to various use cases. Among these, [sorted set (&lt;a href="https://www.dragonflydb.io/docs/category/sorted-sets" rel="noopener noreferrer"&gt;https://www.dragonflydb.io/docs/category/sorted-sets&lt;/a&gt;) stands out as a unique and powerful data type. Unlike traditional hash tables in Redis, which store unordered collections of strings, sorted sets maintain their elements in ascending order based on a score or lexicographic order. This inherent ordering capability, combined with the flexibility of non-repeating members, makes sorted sets an invaluable tool for tasks like leaderboards, time-series data, and priority queues.&lt;/p&gt;

&lt;p&gt;In the process of working closely with our community and customers to constantly improve Dragonfly, we identified some inefficiencies with the Redis implementation of sorted sets.&lt;/p&gt;

&lt;p&gt;We decided the best way to address this was to rebuild sorted sets from scratch. In this article, I will explain how we went about doing this.&lt;/p&gt;

&lt;h3&gt;
  
  
  TL;DR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;We built a new sorted set implementation based on the B+ tree that significantly reduces memory and improves performance; see benchmark results.&lt;/li&gt;
&lt;li&gt;Starting with Dragonfly v1.9, this was an experimental feature.&lt;/li&gt;
&lt;li&gt;Starting with Dragonfly v1.11, the feature is stable and enabled by default.&lt;/li&gt;
&lt;li&gt;In Dragonfly v1.15, the original sorted set implementation from Redis was removed.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  When Tax is Bigger Than Payment
&lt;/h2&gt;

&lt;p&gt;Around eight months ago, when I was inspecting the memory usage profile for one of our cloud customers,&lt;br&gt;
I noticed their memory usage seemed too large for the number of entries they stored.&lt;br&gt;
On further inspection, I noticed that this particular customer used sorted sets, and specifically, they had many sorted set entries with many thousands of members in each one of them.&lt;/p&gt;

&lt;p&gt;When we first launched Dragonfly, we reused most of the existing Redis data structures and instead focused on design changes first: multi-threading, transactional support, and replication.&lt;br&gt;
Once we achieved stability with our core features, we decided to dive in and analyze the Redis sorted set implementation.&lt;/p&gt;

&lt;p&gt;Underneath, Redis utilizes a data structure called &lt;a href="https://en.wikipedia.org/wiki/Skip_list" rel="noopener noreferrer"&gt;skiplist&lt;/a&gt; to store entries in a sorted set when it surpasses 128 elements.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;br&gt;
During my analysis of Redis's skiplist implementation, I observed that, on average, it requires &lt;a href="https://github.com/dragonflydb/dragonfly/blob/main/src/core/bptree_set_test.cc#L294" rel="noopener noreferrer"&gt;37 bytes per entry&lt;/a&gt;&lt;br&gt;
in addition to the essential 16 bytes for storing the entry itself.&lt;br&gt;
The 16 bytes are essential since for one entry in a sorted set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;member&lt;/code&gt; is a string, which requires an 8-byte string pointer.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;score&lt;/code&gt; is a double-precision floating-point number, which also requires 8 bytes.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using the ZADD command to add a member with its score to a sorted set.&lt;/span&gt;
ZADD key score member
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This can also be observed in the simplified Redis skiplist node structure below.&lt;br&gt;
We will come back to this definition later in the blog post for more details.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Assuming 64-bit systems, the pointer size is 8 bytes.&lt;/span&gt;

&lt;span class="cm"&gt;/* ZSETs use a specialized version of Skiplists */&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;zskiplistNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sds&lt;/span&gt; &lt;span class="n"&gt;ele&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// member (or element), pointer to an SDS string, 8 bytes&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// score, double-precision floating-point number, 8 bytes&lt;/span&gt;
    &lt;span class="c1"&gt;// ...        // other fields, additional metadata&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;zskiplistNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This 16-byte size, as needed to store and point to the actual data, is a theoretical lower bound for a single entry in a sorted set.&lt;br&gt;
On top of that, additional metadata is necessary in the data structure to maintain the sorted order of these entries.&lt;br&gt;
I will refer to the sorted set entry as a &lt;code&gt;(member, score)&lt;/code&gt; pair to emphasize the 16-byte memory usage when necessary.&lt;/p&gt;

&lt;p&gt;For instance, for an entry with a field length of 16 characters, Redis stores 32 bytes of useful data, containing 16 characters of string and the 16-byte &lt;code&gt;(member, score)&lt;/code&gt; pair,&lt;br&gt;
plus an additional 37 bytes for skiplist metadata. This results in more than a 100% tax!&lt;br&gt;
And it is a fairly common use case as well, since when using sorted sets, we tend to store things like IDs in them instead of string blogs that are too large.&lt;/p&gt;

&lt;p&gt;Of course, such overhead isn't problematic if justified and comparable to other implementations' overhead per entry.&lt;br&gt;
Frankly, I wasn't sure what to expect, as I hadn't kept up with the latest developments in this area.&lt;br&gt;
However, a Google search led me to a C++ project named &lt;a href="https://code.google.com/archive/p/cpp-btree/" rel="noopener noreferrer"&gt;cpp-btree&lt;/a&gt;.&lt;br&gt;
My tests showed that this implementation could achieve as little as 2 bytes of overhead per entry, which was promising!&lt;br&gt;
Their design uses a classic &lt;a href="https://en.wikipedia.org/wiki/B-tree" rel="noopener noreferrer"&gt;B-tree&lt;/a&gt;,&lt;br&gt;
but they attained remarkable memory efficiency through 'bucketing'—a technique commonly used in advanced hash tables.&lt;br&gt;
This technique involves grouping multiple entries in a single tree node, significantly reducing the metadata overhead per entry.&lt;br&gt;
This project has since been integrated into the &lt;a href="https://github.com/abseil/abseil-cpp/blob/master/absl/container/btree_set.h" rel="noopener noreferrer"&gt;Abseil C++ library&lt;/a&gt;, which Dragonfly also utilizes.&lt;/p&gt;


&lt;h2&gt;
  
  
  Skiplist vs. B-tree
&lt;/h2&gt;

&lt;p&gt;To understand the differences between skiplist and B-tree approaches, we first need to understand the skiplist design.&lt;br&gt;
A skiplist consists of multiple layers of linked lists.&lt;br&gt;
The bottom layer is a standard linked list containing all items.&lt;br&gt;
The layer above it is sparser, containing approximately half the items.&lt;br&gt;
Each successive layer contains half the number of items as the layer immediately below it.&lt;br&gt;
The additional layers above the bottom one act as express lanes to speed up the lookup operations and to provide &lt;code&gt;O(log N)&lt;/code&gt; complexity on average.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vv0uhsscy43v3r5btvr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3vv0uhsscy43v3r5btvr.png" alt="Image description" width="800" height="313"&gt;&lt;/a&gt;&lt;br&gt;
The complete Redis skiplist node looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Assuming 64-bit systems, the pointer size is 8 bytes.&lt;/span&gt;

&lt;span class="cm"&gt;/* ZSETs use a specialized version of Skiplists */&lt;/span&gt;
&lt;span class="k"&gt;typedef&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;zskiplistNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sds&lt;/span&gt; &lt;span class="n"&gt;ele&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// member (or element), pointer to an SDS string, 8 bytes&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// score, double-precision floating-point number, 8 bytes&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;zskiplistNode&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;backward&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// backward pointer, 8 bytes&lt;/span&gt;
    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;zskiplistLevel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;zskiplistNode&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;forward&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// forward pointer, 8 bytes&lt;/span&gt;
        &lt;span class="kt"&gt;unsigned&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// typically 8 bytes&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;                         &lt;span class="c1"&gt;// variable size, but at least 1 level&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;zskiplistNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The allocation of the &lt;code&gt;level&lt;/code&gt; array is determined by the node's position in the structure, as depicted in the diagram.&lt;br&gt;
A node always requires at least one level, but it may have more to be part of those sparser express lanes.&lt;br&gt;
Essentially, a node comprises a 16-byte &lt;code&gt;(member, score)&lt;/code&gt; pair, a backward pointer for the linked list (8 bytes),&lt;br&gt;
and a &lt;code&gt;level&lt;/code&gt; array (16 bytes x the number of levels it reaches).&lt;br&gt;
To sum up, a node will occupy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A minimum of 40 bytes (i.e., nodes &lt;code&gt;2, 5, 7, 8, 10&lt;/code&gt; in the skiplist diagram)&lt;/li&gt;
&lt;li&gt;Every second node of 56 bytes (i.e., nodes &lt;code&gt;3, 9&lt;/code&gt; in the skiplist diagram)&lt;/li&gt;
&lt;li&gt;Every fourth node of 72 bytes (i.e., nodes &lt;code&gt;4, 6&lt;/code&gt; in the skiplist diagram)&lt;/li&gt;
&lt;li&gt;And so on…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Dragonfly code specifically, the allocator adjusts allocation sizes to multiples of 16 bytes, so the minimum size of a skiplist node in Dragonfly is 48 bytes.&lt;/p&gt;

&lt;p&gt;So where does the average of 37 bytes of overhead come from?&lt;br&gt;
A 37-byte overhead means that the average total size of an entry is 37 bytes + 16 bytes for the &lt;code&gt;(member, score)&lt;/code&gt; pair, or 53 bytes.&lt;br&gt;
Based on the knowledge we have about Redis skiplist nodes,&lt;br&gt;
we can compute the expected weighted average of 4 entries in a skiplist (i.e., nodes &lt;code&gt;2, 3, 4, 5&lt;/code&gt; in the skiplist diagram):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(48 + 56 + 72 + 48) / 4  = 56 bytes per entry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This computation is close enough to the empirical evidence we got.&lt;/p&gt;

&lt;p&gt;In contrast, a B-tree holds multiple elements within each node.&lt;br&gt;
The cpp-btree design utilizes a 256-byte node array capable of containing up to 15 &lt;code&gt;(member, score)&lt;/code&gt; pairs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx4l1dys8h4fnejfutbb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frx4l1dys8h4fnejfutbb.png" alt="Image description" width="800" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This implies that the branching factor for such a tree ranges from 7 to 15.&lt;br&gt;
For example, with 1000 entries, at least 67 leaf nodes are required, plus 5 to 10 inner nodes, which amounts to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;77 nodes x 256 bytes/node ÷ 1000 entries = 19.2 bytes per entry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This translates to an average overhead of 2 to 3 bytes per entry, depending on the load of the inner nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  I Know What You Did Last Summer
&lt;/h2&gt;

&lt;p&gt;Unfortunately, my excitement was a bit premature because the Redis sorted set API requires custom functionality around its ranking API.&lt;br&gt;
This is not something standard B+ trees provide out of the box, and cpp-btree is not an exception.&lt;br&gt;
Without support for the ranking API, I would not be able to implement &lt;a href="https://www.dragonflydb.io/docs/command-reference/sorted-sets/zrank" rel="noopener noreferrer"&gt;&lt;code&gt;ZRANK&lt;/code&gt;&lt;/a&gt; and similar commands that need to compute the rankings of the elements quickly.&lt;/p&gt;

&lt;p&gt;There was no easy way to add the ranking API to cpp-btree without intrusive changes, so I decided to implement the &lt;a href="https://github.com/dragonflydb/dragonfly/pull/1596/" rel="noopener noreferrer"&gt;Dragonfly B+ tree as a side project&lt;/a&gt;.&lt;br&gt;
It went faster than anticipated, since we did not need to implement a fully generic tree. Instead, we only needed an implementation tuned to Dragonfly use-cases.&lt;br&gt;
At the end, &lt;strong&gt;we achieved the expected 2-3 bytes overhead on average per entry&lt;/strong&gt;, compared to 37 bytes overhead with the original skiplist implementation.&lt;br&gt;
Our implementation is also faster, which becomes significant when running queries on really large sorted sets of orders of tens of thousands or even more.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmark Results
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uq52n5i8bcqzzkirsac.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3uq52n5i8bcqzzkirsac.jpeg" alt="Image description" width="640" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We used the &lt;a href="https://github.com/redis-performance/redis-zbench-go/" rel="noopener noreferrer"&gt;&lt;code&gt;redis-zbench-go&lt;/code&gt;&lt;/a&gt; tool to benchmark sorted sets.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;br&gt;
Redis and Dragonfly both have sorted set implementations that employ listpacks for sets with lengths up to 128.&lt;br&gt;
However, when it comes to longer sets, Redis switches to a skiplist implementation, while Dragonfly utilizes its aforementioned B+ tree implementation.&lt;br&gt;
To comprehensively evaluate performance across these configurations, we devised two distinct loadtest profiles: &lt;code&gt;ZADD (10-128)&lt;/code&gt; and &lt;code&gt;ZADD (129-200)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ZADD (10-128)&lt;/code&gt; profile sends 1 million &lt;a href="https://www.dragonflydb.io/docs/command-reference/sorted-sets/zadd" rel="noopener noreferrer"&gt;&lt;code&gt;ZADD&lt;/code&gt;&lt;/a&gt; commands, each containing 10 to 128 elements.&lt;br&gt;
On the other hand, for the &lt;code&gt;ZADD (129-200)&lt;/code&gt; profile, we dispatched 800k commands, with each containing 129 to 200 elements to servers under test.&lt;br&gt;
For both profiles, we first ran them on Redis v7.&lt;br&gt;
Then, we run them on a Dragonfly instance using only one thread, namely &lt;code&gt;Dragonfly-1&lt;/code&gt;, to show how it compares with Redis.&lt;br&gt;
Finally, both profiles were run on Dragonfly with eight threads, namely &lt;code&gt;Dragonfly-8&lt;/code&gt;, demonstrating its vertical scalability when more CPUs are available.&lt;/p&gt;

&lt;p&gt;Please note that the CPU performance of both Redis and Dragonfly sorted set implementations is dominated by other CPU-intensive tasks&lt;br&gt;
that backends need to perform in order to process a request. Hence, the impact of CPU optimizations on overall throughput is limited in these tests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2kphz71f9ondrx62v1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2kphz71f9ondrx62v1f.png" alt="Image description" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, Dragonfly in single-threaded mode can sustain a little bit higher throughput,&lt;br&gt;
but the nice thing about it is how &lt;strong&gt;it scales vertically efficiently, reaching 4-5x on 8 threads.&lt;/strong&gt;&lt;br&gt;
Please note that the initial motivation for building better sorted sets was memory efficiency, and higher QPS is just an added bonus.&lt;br&gt;
The next graph demonstrates the memory usage of all servers after all the commands were sent.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22wt0zgy6l7xj1z19o58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22wt0zgy6l7xj1z19o58.png" alt="Image description" width="800" height="507"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, &lt;code&gt;ZADD (10-128)&lt;/code&gt; does not show any difference in memory usage.&lt;br&gt;
It is expected given that Dragonfly uses the same listpack data structure for small sets like Redis.&lt;br&gt;
However, with large sorted sets, Dragonfly is much more efficient in terms of memory usage.&lt;br&gt;
&lt;strong&gt;One can observe up to 40% memory reduction when using Dragonfly.&lt;/strong&gt;&lt;br&gt;
There is not much difference between using multiple threads and a single thread in this case.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blog post, we've journeyed through the innovations taken by Dragonfly in enhancing the efficiency of sorted sets,&lt;br&gt;
a fundamental data structure that can support various use cases such as leaderboards, time-series data, and priority queues.&lt;/p&gt;

&lt;p&gt;Dragonfly initially concentrated on pivotal architectural design choices, adopting a multi-threaded,&lt;br&gt;
shared-nothing model and leveraging Redis's existing data structures to establish a solid foundation.&lt;br&gt;
The innovation never stopped, and we've evolved beyond these beginnings with the introduction of a new sorted-set implementation based on B+ tree.&lt;br&gt;
This advancement significantly enhances memory efficiency and represents Dragonfly's ongoing commitment to pushing the limits of being the most advanced in-memory data store.&lt;/p&gt;

&lt;p&gt;Take our word for it, but also try it out by &lt;a href="https://www.dragonflydb.io/docs/getting-started" rel="noopener noreferrer"&gt;deploying a Dragonfly instance&lt;/a&gt;&lt;br&gt;
to see for yourself how Dragonfly is setting new standards in data structure optimization and performance enhancement.&lt;/p&gt;






&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;```shell
# Redis, ZADD (10-128)
redis-zbench-go -mode load -r 1000000 -p 6379 -key-elements-min=10 -key-elements-max=128 # Load
redis-zbench-go -mode query -r 1000000 -p 6379 -n 10000000 -query zrange-byscore         # Query

# Redis, ZADD (129-200)
redis-zbench-go -mode load -r 800000 -p 6379 -key-elements-min=129 -key-elements-max=200 # Load
redis-zbench-go -mode query -r 1000000 -p 6379 -n 10000000 -query zrange-byscore         # Query

# Dragonfly, 1 Thread, ZADD (10-128)
redis-zbench-go -mode load -r 1000000 -p 6379 -key-elements-min=10 -key-elements-max=128 # Load
redis-zbench-go -mode query -r 1000000 -p 6379 -n 10000000 -query zrange-byscore         # Query

# Dragonfly, 1 Thread, ZADD (129-200)
redis-zbench-go -mode load -r 800000 -p 6379 -key-elements-min=129 -key-elements-max=200 # Load
redis-zbench-go -mode query -r 1000000 -p 6379 -n 10000000 -query zrange-byscore         # Query

# Dragonfly, 8 Threads, ZADD (10-128)
redis-zbench-go -mode load -r 1000000 -p 6379 -key-elements-min=10 -key-elements-max=128 -c 160 # Load
redis-zbench-go -mode query -r 1000000 -p 6379 -n 10000000 -query zrange-byscore -c 160         # Query

# Dragonfly, 8 Threads, ZADD (129-200)
redis-zbench-go -mode load -r 800000 -p 6379 -key-elements-min=129 -key-elements-max=200 -c 160 # Load
redis-zbench-go -mode query -r 1000000 -p 6379 -n 10000000 -query zrange-byscore -c 200         # Query
```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;For small collections (hashes, lists, sorted sets), Redis uses a very memory-efficient encoding called &lt;a href="https://github.com/redis/redis/blob/unstable/src/listpack.c" rel="noopener noreferrer"&gt;listpack&lt;/a&gt;&lt;br&gt;
that just stores all the elements of a collection in a single blob, serialized linearly one after another.&lt;br&gt;
Listpack is indeed very memory efficient, but it has terrible &lt;code&gt;O(N)&lt;/code&gt; access complexity, thus fitting only for collections with a small number of elements.&lt;br&gt;
Before &lt;a href="https://github.com/redis/redis/releases/tag/7.0-rc1" rel="noopener noreferrer"&gt;Redis 7.0&lt;/a&gt;, another encoding called&lt;br&gt;
&lt;a href="https://github.com/redis/redis/blob/unstable/src/ziplist.c" rel="noopener noreferrer"&gt;ziplist&lt;/a&gt; was used for small hashes, lists, and sorted sets. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;The string pointer in this context is a pointer to the Redis Simple Dynamic String (SDS) data structure,&lt;br&gt;
which is a much more efficient and flexible alternative to the standard C string. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Commands for benchmarking using &lt;a href="https://github.com/redis-performance/redis-zbench-go/" rel="noopener noreferrer"&gt;&lt;code&gt;redis-zbench-go&lt;/code&gt;&lt;/a&gt;: ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
    </item>
    <item>
      <title>Preview of Dragonfly Cluster</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Fri, 23 Aug 2024 13:43:15 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/preview-of-dragonfly-cluster-46g8</link>
      <guid>https://dev.to/dragonflydbio/preview-of-dragonfly-cluster-46g8</guid>
      <description>&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; At the time of writing, Dragonfly Cluster is not officially released yet.&lt;br&gt;
However, many features and cluster-related commands described in this article are already available in the Dragonfly main branch.&lt;br&gt;
We are actively testing and improving this amazing feature.&lt;/p&gt;


&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Dragonfly excels at high performance and vertical scaling, making it a top choice for demanding modern data workloads.&lt;br&gt;
Soon, Dragonfly Cluster will offer horizontal scalability as well, expanding its capabilities even further.&lt;br&gt;
In this article, I want to show you how to run Dragonfly Cluster and provide a concise overview of the Dragonfly Cluster internal processes.&lt;/p&gt;


&lt;h2&gt;
  
  
  Dragonfly Cluster Overview
&lt;/h2&gt;

&lt;p&gt;Like &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/" rel="noopener noreferrer"&gt;Redis Cluster&lt;/a&gt;, Dragonfly Cluster achieves horizontal scalability through sharding.&lt;br&gt;
A cluster comprises one or more shards, each consisting of a master node (primary) and zero or more replicas.&lt;br&gt;
Data is distributed across shards using a slot-based approach.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The hashing space in the cluster is divided into 16384 slots.&lt;/li&gt;
&lt;li&gt;Each key is hashed into a slot. The hash slot is computed from the key name using &lt;a href="https://en.wikipedia.org/wiki/Cyclic_redundancy_check" rel="noopener noreferrer"&gt;the CRC16 algorithm&lt;/a&gt; and then taking the modulus of 16384.&lt;/li&gt;
&lt;li&gt;Each node in a cluster is responsible for a subset of these slots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlhi60v4jkmxjfke3swk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxlhi60v4jkmxjfke3swk.png" alt="Image description" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Dragonfly Cluster supports dynamic rebalancing without downtime.&lt;br&gt;
Hash slots can be seamlessly migrated from one node to another, allowing for the addition, removal, or resizing of nodes without interrupting operations.&lt;/p&gt;

&lt;p&gt;Dragonfly Cluster supports multi-key operations as long as all involved keys (in a multi-key command, in a transaction, or in a Lua script) reside within the same hash slot.&lt;br&gt;
To ensure this, Dragonfly employs hash tags.&lt;br&gt;
With hash tags, the system calculates the hash slot based solely on the content within curly braces &lt;code&gt;{}&lt;/code&gt; of a key.&lt;br&gt;
This mechanism allows users to explicitly control key distribution across shards.&lt;/p&gt;

&lt;p&gt;If a client requests keys from a node, but those keys belong to a hash slot managed by a different node, the client receives a &lt;code&gt;-MOVED&lt;/code&gt; redirection error.&lt;br&gt;
This ensures that the client can always find the correct node handling the requested keys.&lt;/p&gt;

&lt;p&gt;Dragonfly only provides a server but not a control plane to manage cluster deployments.&lt;br&gt;
Node health monitoring, automatic failovers, and slot redistribution are out of the scope of Dragonfly backend functionality&lt;br&gt;
and will be provided as part of the &lt;a href="https://dragonflydb.cloud/" rel="noopener noreferrer"&gt;Dragonfly Cloud&lt;/a&gt; service.&lt;/p&gt;

&lt;p&gt;Dragonfly Cluster offers seamless migration for existing Redis Cluster clients.&lt;br&gt;
It fully adheres to Redis Cluster's client-facing behavior, ensuring zero code changes for applications.&lt;br&gt;
However, Dragonfly takes a fundamentally different approach to cluster management.&lt;/p&gt;

&lt;p&gt;Unlike Redis Cluster's distributed consensus model, Dragonfly adopts a centralized management strategy.&lt;br&gt;
Nodes operate independently, without direct communication or shared state.&lt;br&gt;
This design choice provides a single source of truth and enhances simplicity, reliability, and performance.&lt;/p&gt;


&lt;h2&gt;
  
  
  Cluster Modes
&lt;/h2&gt;

&lt;p&gt;Dragonfly has two cluster modes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Emulated Cluster Mode&lt;/strong&gt; (which can be enabled by &lt;code&gt;--cluster_mode=emulated&lt;/code&gt;) is fully compatible with the stand-alone mode,&lt;br&gt;
supporting &lt;a href="https://www.dragonflydb.io/docs/command-reference/server-management/select" rel="noopener noreferrer"&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/a&gt; and multi-key operations while also providing cluster commands like &lt;code&gt;CLUSTER SHARDS&lt;/code&gt;.&lt;br&gt;
It functions as a single-node Dragonfly instance and does not include horizontal scaling, resharding, or certain advanced cluster features.&lt;br&gt;
This mode is ideal for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Development &amp;amp; Testing Environments:&lt;/strong&gt; Provide a simplified setup for rapid iteration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration Phases:&lt;/strong&gt; Serve as an interim solution when transitioning from a stand-alone to a clustered setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource-Constrained Scenarios:&lt;/strong&gt; A Dragonfly instance in emulated cluster mode can optimize resource utilization by acting as a replica for multiple shards, allowing a single node to replicate several cluster nodes efficiently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Multi-Node Cluster Mode&lt;/strong&gt; (which can be enabled by &lt;code&gt;--cluster_mode=yes&lt;/code&gt;) is the Dragonfly Cluster we are talking about in this blog post.&lt;br&gt;
It has certain limitations compared to stand-alone or emulated modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;a href="https://www.dragonflydb.io/docs/command-reference/server-management/select" rel="noopener noreferrer"&gt;&lt;code&gt;SELECT&lt;/code&gt;&lt;/a&gt; command is not permitted.&lt;/li&gt;
&lt;li&gt;All keys in a multi-key operation (i.e., multi-key commands, transactions, and Lua scripts) must belong to the same slot. Otherwise, a &lt;code&gt;CROSSSLOT&lt;/code&gt; error is returned.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dragonfly supports some &lt;code&gt;CLUSTER&lt;/code&gt; commands for compatibility with Redis Cluster clients. These commands primarily provide informational data about the cluster setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER HELP&lt;/code&gt;: Lists available CLUSTER commands.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER MYID&lt;/code&gt;: Returns the node ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER SHARDS&lt;/code&gt;: Displays information about cluster shards.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER SLOTS&lt;/code&gt;: Lists all slots and their associated nodes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER NODES&lt;/code&gt;: Shows information about all nodes in the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;CLUSTER INFO&lt;/code&gt;: Provides general information about the cluster.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Dragonfly Cluster Management
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;DFLYCLUSTER&lt;/code&gt; Commands
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;DFLYCLUSTER&lt;/code&gt; commands are specific to Dragonfly and offer more advanced cluster management capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt;: Manages node roles, slot assignments, and migration processes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DFLYCLUSTER GETSLOTINFO&lt;/code&gt;: Provides in-depth statistics about slot utilization, including key count, memory usage, and read/write operations.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DFLYCLUSTER FLUSHSLOTS&lt;/code&gt;: Efficiently clears data from specific slots.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DFLYCLUSTER SLOT-MIGRATION-STATUS&lt;/code&gt;: Monitors the progress of slot migrations, indicating the current state and completion status.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Cluster Creation
&lt;/h3&gt;

&lt;p&gt;To begin building a Dragonfly cluster, we'll start by launching two separate Dragonfly instances in cluster mode.&lt;br&gt;
Each instance will have a unique ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ./dragonfly &lt;span class="nt"&gt;--cluster_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="nt"&gt;--admin_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;31001 &lt;span class="nt"&gt;--port&lt;/span&gt; 30001
&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ./dragonfly &lt;span class="nt"&gt;--cluster_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt; &lt;span class="nt"&gt;--admin_port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;31002 &lt;span class="nt"&gt;--port&lt;/span&gt; 30002
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the instances are running, we need to retrieve their unique IDs by using the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 31001 CLUSTER MYID
&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 31002 CLUSTER MYID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my case, the two unique IDs have prefixes &lt;code&gt;97486c...&lt;/code&gt; and &lt;code&gt;728cf2...&lt;/code&gt; respectively.&lt;br&gt;
Now we can create a cluster config in JSON format by plugging in the unique IDs and IP addresses of the two nodes above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"97486c9d7e0507e1edb2dfba4655224d5b61c5e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30001&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16383&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"728cf25ecd4d1230805754ff98939321d72d23ef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30002&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using the &lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt; command, the file is sent to both our nodes and the Dragonfly Cluster is created.&lt;/p&gt;

&lt;h3&gt;
  
  
  Slot Migration
&lt;/h3&gt;

&lt;p&gt;Slot migration is a critical operation that can potentially lead to data loss if not executed carefully.&lt;br&gt;
It's essential for adjusting cluster configuration to meet changing demands.&lt;br&gt;
Dragonfly supports concurrent slot migrations, but only one migration can be in progress between any two specific nodes at a given time.&lt;br&gt;
This means multiple migrations can be initiated simultaneously across different node pairs within a cluster.&lt;br&gt;
To initiate and complete a slot migration, use the &lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt; command, specifying an additional &lt;code&gt;migrations&lt;/code&gt; field in the JSON configuration.&lt;br&gt;
In the example below, I decided to move slots &lt;code&gt;[1000, 8000]&lt;/code&gt; to another node, and here's how the JSON configuration file looks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;999&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"97486c9d7e0507e1edb2dfba4655224d5b61c5e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30001&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16383&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"728cf25ecd4d1230805754ff98939321d72d23ef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30002&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"migrations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"node_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"97486c9d7e0507e1edb2dfba4655224d5b61c5e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;31001&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To maintain cluster consistency during slot migrations, the &lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt; command is propagated to all nodes, even those not directly involved.&lt;br&gt;
This ensures that all nodes have an up-to-date view of the cluster configuration, preventing inconsistencies that might arise from concurrent migration processes or failures.&lt;br&gt;
To monitor the progress of a slot migration, use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; redis-cli &lt;span class="nt"&gt;-p&lt;/span&gt; 31002 DFLYCLUSTER SLOT-MIGRATION-STATUS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the migration status shows &lt;code&gt;FINISHED&lt;/code&gt;, the new cluster configuration (with updated &lt;code&gt;slot_ranges&lt;/code&gt;) can be applied to all nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"97486c9d7e0507e1edb2dfba4655224d5b61c5e2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30001&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"slot_ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"end"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16383&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"master"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"728cf25ecd4d1230805754ff98939321d72d23ef"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ip"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"localhost"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30002&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"replicas"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upon applying the new cluster configuration, data from migrated slots is permanently erased from the source node.&lt;br&gt;
Migrations can be canceled by removing the migration field from the configuration while preserving slot assignments.&lt;br&gt;
If the updated configuration isn't applied promptly after migration completion, the cluster enters a transitional state where nodes have inconsistent slot information.&lt;br&gt;
Clients may initially be redirected to the source node for migrated slots, but subsequent requests to the source node will be correctly routed to the target node.&lt;br&gt;
Although this temporary inconsistency exists, it doesn't compromise data integrity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replicas
&lt;/h3&gt;

&lt;p&gt;Dragonfly replication is configured using the standard &lt;a href="https://www.dragonflydb.io/docs/command-reference/server-management/replicaof" rel="noopener noreferrer"&gt;&lt;code&gt;REPLICAOF&lt;/code&gt;&lt;/a&gt; command, identical to non-clustered setups.&lt;br&gt;
Despite replication details being included in the cluster configuration, replicas function independently, copying data directly from the master node.&lt;br&gt;
This means replicas replicate all data from the master mode, regardless of slot assignment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Slot Migration Process Under the Hood
&lt;/h2&gt;

&lt;p&gt;Dragonfly utilizes a set of internal commands, prefixed with &lt;code&gt;DFLYMIGRATE&lt;/code&gt;, to manage the slot migration process.&lt;br&gt;
The slot migration process involves several carefully coordinated steps to ensure data integrity and seamless transitions between nodes.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Initiating Migration:&lt;/strong&gt;
The process begins with the &lt;code&gt;DFLYCLUSTER CONFIG&lt;/code&gt; command sent to the source and target nodes to configure migration parameters.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preparing the Target Node:&lt;/strong&gt;
The source node sends &lt;code&gt;DFLYMIGRATE INIT [SOURCE_NODE_ID, SHARDS_NUM, SLOT_RANGES]&lt;/code&gt; to the target node.
The target node responds &lt;code&gt;OK&lt;/code&gt;, indicating it is ready to receive data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Setting Up Data Transfer:&lt;/strong&gt;
For each storage thread-shard (a data segment handled by a single thread), the source node sends &lt;code&gt;DFLYMIGRATE FLOW [SOURCE_NODE_ID, FLOW_ID]&lt;/code&gt;.
Each &lt;code&gt;DFLYMIGRATE FLOW&lt;/code&gt; command sets up a connection for data transfer, confirmed by an &lt;code&gt;OK&lt;/code&gt; response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transferring Data:&lt;/strong&gt;
Using the established connections from the &lt;code&gt;DFLYMIGRATE FLOW&lt;/code&gt; commands, the source node serializes and transfers data to the target node.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Finalizing Migration:&lt;/strong&gt;
After the data transfer, the migrated slots on the source node are blocked to prevent further data changes.
The source node then sends a finalization request for each FLOW connection to conclude the data transfer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completing the Process:&lt;/strong&gt;
The source node issues &lt;code&gt;DFLYMIGRATE ACK [SOURCE_NODE_ID, ATTEMPT_ID]&lt;/code&gt; to the target node to finalize the entire migration.
The target node responds with &lt;code&gt;ATTEMPT_ID&lt;/code&gt;, completing the migration.
The &lt;code&gt;ATTEMPT_ID&lt;/code&gt; is used to handle errors that may arise during the finalization process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While most steps in the migration process are straightforward, step 4 above requires a more detailed explanation due to its complexity.&lt;br&gt;
In Dragonfly, there are two sources of data that need to be sent to the target node: the &lt;strong&gt;snapshot&lt;/strong&gt; and the &lt;strong&gt;journal&lt;/strong&gt;.&lt;br&gt;
We will dive deeper into these two sources of data below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Snapshot Creation
&lt;/h3&gt;

&lt;p&gt;To create a snapshot, Dragonfly iterates through each storage shard, serializing the data.&lt;br&gt;
In the absence of write requests, this is a linear process where each bucket is serialized one by one.&lt;br&gt;
Periodic pauses are incorporated to allow the system to process new requests, ensuring minimal disruption.&lt;/p&gt;

&lt;p&gt;The process becomes more complex when write requests occur during serialization.&lt;br&gt;
Unlike Redis, which uses a fork mechanism to prevent data changes during serialization,&lt;br&gt;
&lt;a href="https://www.dragonflydb.io/blog/balanced-vs-unbalanced" rel="noopener noreferrer"&gt;Dragonfly employs a more sophisticated mechanism&lt;/a&gt;, incorporating versioning and pre-update hooks, to create snapshots without spiking the memory usage or causing latency issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  Journal Serialization
&lt;/h3&gt;

&lt;p&gt;While handling the snapshot, Dragonfly also manages a journal, which logs all recent write operations.&lt;br&gt;
These journal entries are serialized and sent to the target node along with the snapshot data.&lt;/p&gt;

&lt;p&gt;Let's look at a small example to illustrate the process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There are several data entries: &lt;strong&gt;A&lt;/strong&gt;, &lt;strong&gt;B&lt;/strong&gt;, &lt;strong&gt;C&lt;/strong&gt;, and &lt;strong&gt;D&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Data entries &lt;strong&gt;A&lt;/strong&gt; and &lt;strong&gt;B&lt;/strong&gt; are serialized and sent to the target node.&lt;/li&gt;
&lt;li&gt;A new &lt;code&gt;MSET&lt;/code&gt; command is issued by the client, updating &lt;strong&gt;B&lt;/strong&gt; and &lt;strong&gt;D&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Because &lt;strong&gt;B&lt;/strong&gt; is already serialized previously, we do nothing with it for now.&lt;/li&gt;
&lt;li&gt;Data entry &lt;strong&gt;D&lt;/strong&gt; is serialized and sent to the target node.&lt;/li&gt;
&lt;li&gt;The journal gets the update about &lt;strong&gt;B&lt;/strong&gt; and &lt;strong&gt;D&lt;/strong&gt;, serializes it, and sends it to the target node.&lt;/li&gt;
&lt;li&gt;Finally, data entry &lt;strong&gt;C&lt;/strong&gt; is serialized and sent to the target node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffaof6plcy4ff35img28d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffaof6plcy4ff35img28d.png" alt="Image description" width="800" height="474"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By following this process, Dragonfly ensures that the target node receives a consistent version of the source node's data,&lt;br&gt;
including all recent write operations during the slot migration process.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Dragonfly Cluster is a powerful addition to the Dragonfly ecosystem, offering horizontal scalability for even the most demanding workloads.&lt;br&gt;
Modern servers can come equipped with over a hundred cores and several hundred gigabytes of memory.&lt;br&gt;
While Dragonfly Cluster offers significant scalability advancements, vertical scaling should be prioritized if feasible.&lt;br&gt;
Therefore, it is advisable to evaluate the potential for vertical scaling before implementing a cluster.&lt;br&gt;
If you're uncertain about future vertical scaling needs, you can start with an emulated cluster and switch to a real cluster as your requirements grow.&lt;/p&gt;

&lt;p&gt;In the meantime, if you are curious to see how Dragonfly can scale with your needs and workloads, the easiest way to get started is by using the cloud service backed by the Dragonfly core team.&lt;br&gt;
Try &lt;a href="https://dragonflydb.cloud/" rel="noopener noreferrer"&gt;Dragonfly Cloud&lt;/a&gt; today and experience the power of seamless scaling firsthand!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>2024 New Year, New Number: New Benchmarks Show Dragonfly Achieves 6.43 Million Ops/Sec on an AWS Graviton3E Instance</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Thu, 25 Jan 2024 17:00:00 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/2024-new-year-new-number-new-benchmarks-show-dragonfly-achieves-643-million-opssec-on-an-aws-graviton3e-instance-ehm</link>
      <guid>https://dev.to/dragonflydbio/2024-new-year-new-number-new-benchmarks-show-dragonfly-achieves-643-million-opssec-on-an-aws-graviton3e-instance-ehm</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Last year, we published &lt;a href="https://www.dragonflydb.io/blog/scaling-performance-redis-vs-dragonfly" rel="noopener noreferrer"&gt;benchmark results&lt;/a&gt; showing that Dragonfly can achieve 4 million ops/sec on an AWS &lt;a href="https://instances.vantage.sh/aws/ec2/c6gn.16xlarge" rel="noopener noreferrer"&gt;&lt;code&gt;c6gn.16xlarge&lt;/code&gt;&lt;/a&gt; instance.&lt;br&gt;
To put that into perspective, it's like every person in Los Angeles (as of 2020, roughly 3.9 million residents) asking Dragonfly a question, and Dragonfly answers instantly, all within that single second.&lt;/p&gt;

&lt;p&gt;However, we have exciting news from our latest benchmarks:&lt;br&gt;
&lt;strong&gt;by operating on a new AWS &lt;a href="https://instances.vantage.sh/aws/ec2/c7gn.16xlarge" rel="noopener noreferrer"&gt;&lt;code&gt;c7gn.16xlarge&lt;/code&gt;&lt;/a&gt; single instance, we've now achieved 6.43 million ops/second with Dragonfly.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Number of Dragonfly Threads&lt;/th&gt;
&lt;th&gt;Max Ops/Second (P99.9 &amp;lt; 10ms)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;302,603&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;329,755&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;744,708&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;1,370,980&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2,749,539&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;4,263,950&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;6,432,982&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctut3dbrdtw9xoh4w51e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fctut3dbrdtw9xoh4w51e.png" alt="Benchmark" width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Beyond the Number - Dragonfly Advances with Hardware
&lt;/h2&gt;

&lt;p&gt;This latest benchmark achievement goes beyond just the number.&lt;br&gt;
It's about how Dragonfly leverages hardware advancements to boost performance.&lt;br&gt;
Normally, a 60.75% increase in throughput — going from 4 million to 6.43 million operations per second — might suggest major code optimizations or architectural changes.&lt;br&gt;
But that's not the case for Dragonfly.&lt;/p&gt;

&lt;p&gt;First, let's take a look at the hardware advancements.&lt;br&gt;
At the beginning of 2023, we used the &lt;a href="https://instances.vantage.sh/aws/ec2/c6gn.16xlarge" rel="noopener noreferrer"&gt;&lt;code&gt;c6gn.16xlarge&lt;/code&gt;&lt;/a&gt; instance for throughput benchmarking.&lt;br&gt;
Around June 2023, AWS announced the &lt;a href="https://instances.vantage.sh/aws/ec2/c7gn.16xlarge" rel="noopener noreferrer"&gt;&lt;code&gt;c7gn&lt;/code&gt;&lt;/a&gt; series,&lt;br&gt;
which are powered by ARM-based AWS Graviton3E processors that deliver up to 25% better performance over Graviton2-based &lt;code&gt;c6gn&lt;/code&gt; instances.&lt;br&gt;
They are ideal for a large variety of compute-intensive workloads, including in-memory data stores like Dragonfly.&lt;br&gt;
Network-wise, the &lt;code&gt;c7gn&lt;/code&gt; instances deliver up to 200Gbps of network bandwidth and up to 2x higher packet processing performance than the previous-generation &lt;code&gt;c6gn&lt;/code&gt; instances.&lt;br&gt;
Below is a quick comparison of the two instances.&lt;br&gt;
It is notable that the price of the &lt;code&gt;c7gn&lt;/code&gt; instance is ~44% higher than the &lt;code&gt;c6gn&lt;/code&gt; instance,&lt;br&gt;
but the Dragonfly throughput gain is ~60.75%, which demonstrates how cost-efficient Dragonfly is.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;c6gn.16xlarge&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;c7gn.16xlarge&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;vCPUs&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory&lt;/td&gt;
&lt;td&gt;128GiB&lt;/td&gt;
&lt;td&gt;128GiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Physical Processor&lt;/td&gt;
&lt;td&gt;AWS Graviton2 Processor&lt;/td&gt;
&lt;td&gt;AWS Graviton3 Processor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clock Speed (GHz)&lt;/td&gt;
&lt;td&gt;2.5&lt;/td&gt;
&lt;td&gt;2.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network Performance (Gibps)&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Price (us-east-1, January 2024)&lt;/td&gt;
&lt;td&gt;$2.765/hour&lt;/td&gt;
&lt;td&gt;$3.994/hour&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Over the past year, we've certainly refined Dragonfly's performance.&lt;br&gt;
For instance, we enhanced the performance of the &lt;a href="https://github.com/dragonflydb/dragonfly/pull/2212" rel="noopener noreferrer"&gt;&lt;code&gt;MGET&lt;/code&gt; command&lt;/a&gt;,&lt;br&gt;
improved the overall performance and memory efficiency of the &lt;a href="https://github.com/dragonflydb/dragonfly/releases/tag/v1.9.0" rel="noopener noreferrer"&gt;&lt;code&gt;Sorted-Set&lt;/code&gt; data type&lt;/a&gt;,&lt;br&gt;
optimized multiple aspects to &lt;a href="https://www.dragonflydb.io/blog/running-bullmq-with-dragonfly-part-2-optimization" rel="noopener noreferrer"&gt;get 30x throughput with BullMQ&lt;/a&gt;, and many more.&lt;br&gt;
Yet, the core design and architecture remain unchanged.&lt;br&gt;
And this is key: &lt;strong&gt;Dragonfly's architecture is designed to scale vertically with hardware improvements.&lt;/strong&gt;&lt;br&gt;
It's not just about tweaking the codebase for better throughput; it's about the fundamental architecture that inherently capitalizes on the evolving capabilities of the hardware it runs on.&lt;br&gt;
Thus, we are confident to say that &lt;strong&gt;the existing Dragonfly codebase, robust and efficient as it is, is ready to see further performance gains as hardware advances in the future.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One of the key design elements that allows Dragonfly to take advantage of advancements in hardware is its multi-threaded shared-nothing architecture.&lt;br&gt;
Below, we will dive deeper into how this architecture automatically unlocks performance from advancements in hardware.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dragonfly Architecture Overview
&lt;/h2&gt;

&lt;p&gt;Dragonfly runs as a single process with multiple threads, and it is designed to run on multi-core servers without any special configurations or optimizations.&lt;br&gt;
Dragonfly's in-memory data store keyspace is sharded into &lt;code&gt;N&lt;/code&gt; parts, where &lt;code&gt;N&lt;/code&gt; is less than or equal to the number of CPU logical cores in the system.&lt;br&gt;
Each shard is owned and managed by a single Dragonfly thread, establishing a &lt;a href="https://en.wikipedia.org/wiki/Shared-nothing_architecture" rel="noopener noreferrer"&gt;&lt;strong&gt;shared-nothing architecture&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To put it simply, Dragonfly's thread-per-core model and shared-nothing architecture make it like an already perfectly orchestrated group of Redis processes without the overhead of cluster management.&lt;br&gt;
This is why Dragonfly can automatically achieve higher throughput performance when put on more powerful machines:&lt;br&gt;
&lt;strong&gt;more Dragonfly threads can be created if there are more CPU logical cores available,&lt;br&gt;
and when each core is more powerful, each Dragonfly thread can inherently handle more operations per second.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the meantime, atomicity is crucial for Dragonfly key-value operations.&lt;br&gt;
It is not possible to use mutexes or spinlocks to orchestrate threads at the rates described above, as they would immediately cause contention.&lt;br&gt;
Much like a busy high-speed multi-lane road being regulated by a traffic light that only permits one car to pass at a time, the presence of such a bottleneck leads to considerable congestion.&lt;br&gt;
To provide atomicity guarantees for multi-key operations in the shared-nothing architecture, we incorporate recent academic research.&lt;br&gt;
Specifically, we've based our transactional framework for Dragonfly on the paper &lt;a href="https://www.cs.umd.edu/~abadi/papers/vldbj-vll.pdf" rel="noopener noreferrer"&gt;"VLL: a lock manager redesign for main memory database systems"&lt;/a&gt;.&lt;br&gt;
By adopting a shared-nothing architecture and VLL, we can achieve multi-key operations without relying on mutexes or spinlocks.&lt;/p&gt;

&lt;p&gt;Let's take a look at some examples.&lt;br&gt;
When Dragonfly receives a simple &lt;code&gt;GET&lt;/code&gt; or &lt;code&gt;SET&lt;/code&gt; command, it will first calculate the hash of the key and then find the corresponding shard.&lt;br&gt;
Then the data manipulation will be performed by the specific Dragonfly thread that owns the shard, while other threads are free to process more commands without heavy congestion.&lt;br&gt;
A more complex example would be a &lt;a href="https://www.dragonflydb.io/blog/announcing-dragonfly-search" rel="noopener noreferrer"&gt;Dragonfly Search&lt;/a&gt; command, which is a multi-key operation.&lt;br&gt;
When Dragonfly receives a search command, it will first parse the command to build a query execution tree.&lt;br&gt;
Then Dragonfly descends to all the threads, where each thread has a separate index that knows about all the indexed values in the corresponding shard.&lt;br&gt;
Multiple threads will execute the query tree efficiently in parallel, and the results will be merged and returned to the client.&lt;br&gt;
The shared-nothing architecture and VLL guarantee that the multi-key operations are atomic, consistent, and highly efficient.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefz7sont0xvm96y4o8ww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fefz7sont0xvm96y4o8ww.png" alt="Search" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Repeat the Benchmark Results
&lt;/h2&gt;

&lt;p&gt;As shown in the table and chart above, Dragonfly is able to reach 6.43 million ops/sec with 64 Dragonfly threads on a single instance,&lt;br&gt;
while maintaining a &lt;strong&gt;P50 latency of 0.3ms&lt;/strong&gt;, a &lt;strong&gt;P99 latency of 1.1ms&lt;/strong&gt;, and &lt;strong&gt;a P99.9 latency of 1.5ms&lt;/strong&gt;.&lt;br&gt;
The benchmark was conducted with multiple Dragonfly thread configurations and &lt;a href="https://github.com/RedisLabs/memtier_benchmark" rel="noopener noreferrer"&gt;&lt;code&gt;memtier_benchmark&lt;/code&gt;&lt;/a&gt; thread &amp;amp; connection configurations,&lt;br&gt;
and we take the maximum ops/sec value with a P99.9 latency of less than 10ms as the final result set.&lt;br&gt;
Here are the detailed steps to repeat the benchmark results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use two AWS &lt;a href="https://instances.vantage.sh/aws/ec2/c7gn.16xlarge" rel="noopener noreferrer"&gt;&lt;code&gt;c7gn.16xlarge&lt;/code&gt;&lt;/a&gt; instances,
one for the server (i.e., Dragonfly) and one for the client (i.e., the &lt;code&gt;memtier_benchmark&lt;/code&gt; CLI).
The &lt;code&gt;c7gn.16xlarge&lt;/code&gt; instance has 64 vCPUs, 128GiB memory, and is equipped with the AWS Graviton3 processor.&lt;/li&gt;
&lt;li&gt;Make sure the network configuration between the two instances is correct, so that the client can connect to the server.&lt;/li&gt;
&lt;li&gt;For the Dragonfly server, use the following configuration arguments:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--proactor_threads=64&lt;/code&gt;, which specifies the number of Dragonfly threads.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Fill the Dragonfly server with 10 million keys, to simulate a data store that is in use.
&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; DEBUG POPULATE 10000000 key 550
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Use the following &lt;code&gt;memtier_benchmark&lt;/code&gt; command on the client instance to perform benchmark and collect results:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  &lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; memtier_benchmark &lt;span class="nt"&gt;-p&lt;/span&gt; 6380 &lt;span class="nt"&gt;--ratio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;get_set_ration&amp;gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# 1:0 for GET, 0:1 for SET&lt;/span&gt;
     &lt;span class="nt"&gt;--hide-histogram&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--threads&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;number_of_client_threads&amp;gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# 1,2,4,...,64&lt;/span&gt;
     &lt;span class="nt"&gt;--clients&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;number_of_client_connections&amp;gt; &lt;span class="se"&gt;\ &lt;/span&gt;&lt;span class="c"&gt;# 10, 25, 40, 50&lt;/span&gt;
     &lt;span class="nt"&gt;--requests&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200000 &lt;span class="nt"&gt;--distinct-client-seed&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--data-size&lt;/span&gt; 256 &lt;span class="nt"&gt;--expiry-range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;500-500 &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-s&lt;/span&gt; &amp;lt;host_of_dragonfly_server&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can plug in different &lt;code&gt;--proactor_threads&lt;/code&gt; values for Dragonfly and various &lt;code&gt;--threads&lt;/code&gt; and &lt;code&gt;--clients&lt;/code&gt; values for &lt;code&gt;memtier_benchmark&lt;/code&gt; to repeat the benchmark.&lt;br&gt;
We reached 6.43 million ops/sec with 64 Dragonfly threads, 64 &lt;code&gt;memtier_benchmark&lt;/code&gt; threads, and 40 client connections.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blog post, we have discussed how Dragonfly can automatically and fully utilize the hardware it operates on by&lt;br&gt;
leveraging the thread-per-core model and shared-nothing architecture.&lt;br&gt;
With the latest benchmark results, Dragonfly again stakes its claim as the most performant in-memory data store on earth.&lt;br&gt;
Not just the number, but the overall design and architecture give developers even more confidence in Dragonfly's ability to handle the most demanding workloads.&lt;/p&gt;

&lt;p&gt;Feel free to check out our &lt;a href="https://github.com/dragonflydb/dragonfly" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;, &lt;a href="https://www.dragonflydb.io/docs" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;,&lt;br&gt;
and get started with Dragonfly by &lt;a href="https://www.dragonflydb.io/docs/getting-started/docker" rel="noopener noreferrer"&gt;running it locally with Docker using one single command&lt;/a&gt;.&lt;br&gt;
Happy building, and stay tuned for more updates in 2024!&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>aws</category>
    </item>
    <item>
      <title>Scaling Real-Time Leaderboards with Dragonfly</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Fri, 19 Jan 2024 17:00:00 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/scaling-real-time-leaderboards-with-dragonfly-2h4p</link>
      <guid>https://dev.to/dragonflydbio/scaling-real-time-leaderboards-with-dragonfly-2h4p</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today's digital age, leaderboards have become an integral part of many applications, providing a dynamic way to display user scores and rankings.&lt;br&gt;
To build gamification features for any application (i.e., games, educational platforms), leaderboards serve as a powerful tool to engage and motivate users.&lt;br&gt;
In this blog post, we're going to delve into the process of building a practical and realistic leaderboard system.&lt;/p&gt;

&lt;p&gt;Our journey will involve leveraging the capabilities of &lt;a href="https://github.com/dragonflydb/dragonfly" rel="noopener noreferrer"&gt;Dragonfly&lt;/a&gt;, a highly efficient drop-in replacement for Redis,&lt;br&gt;
known for its ultra-high throughput and multi-threaded share-nothing architecture.&lt;br&gt;
Specifically, we'll be utilizing two of Dragonfly's data types: &lt;code&gt;Sorted-Set&lt;/code&gt; and &lt;code&gt;Hash&lt;/code&gt;.&lt;br&gt;
These data structures are perfect for handling real-time data and ranking systems, making them ideal for our leaderboards.&lt;/p&gt;

&lt;p&gt;Moreover, to ensure that our leaderboards are not just real-time but also persistent, we will be integrating a SQL database (PostgreSQL) into our system.&lt;br&gt;
This approach allows us to maintain a comprehensive record of user scores over different time frames.&lt;br&gt;
As a result, we'll be capable of showcasing three distinct types of leaderboards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An all-time leaderboard&lt;/strong&gt; that reflects overall user scores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A current-week leaderboard&lt;/strong&gt; that captures the most recent user activities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leaderboards for previous weeks&lt;/strong&gt;, giving users insights into past trends and performances, potentially also providing rewards and prizes for top performers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Through this implementation, we aim to demonstrate how Dragonfly, in conjunction with traditional SQL databases,&lt;br&gt;
can be utilized to create robust, scalable, and efficient leaderboard systems. So, let's dive in and start building!&lt;/p&gt;


&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Database Schema
&lt;/h3&gt;

&lt;p&gt;In the implementation of our leaderboard system, a carefully designed SQL database schema plays a pivotal role.&lt;br&gt;
At the core of this schema is the &lt;code&gt;users&lt;/code&gt; table, which is essential for storing basic user information.&lt;br&gt;
This table includes fields like &lt;code&gt;id&lt;/code&gt; (a unique identifier for each user, automatically incremented as &lt;code&gt;BIGSERIAL&lt;/code&gt;),&lt;br&gt;
&lt;code&gt;email&lt;/code&gt; (a unique field to prevent duplicate registrations), &lt;code&gt;password&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;,&lt;br&gt;
and timestamps &lt;code&gt;created_at&lt;/code&gt; and &lt;code&gt;updated_at&lt;/code&gt; to track the creation and last update of each user record.&lt;br&gt;
Note that the &lt;code&gt;password&lt;/code&gt; field should store the hashed or encrypted version of the user's password for security purposes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;      &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt;   &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt;   &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;         &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;         &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we have the &lt;code&gt;user_score_transactions&lt;/code&gt; table, which logs all score transactions for users.&lt;br&gt;
It consists of an &lt;code&gt;id&lt;/code&gt; as a unique transaction identifier, &lt;code&gt;user_id&lt;/code&gt; linking to the users table,&lt;br&gt;
&lt;code&gt;score_added&lt;/code&gt; representing the score change, &lt;code&gt;reason&lt;/code&gt; for the score change (such as winning a game or completing a task),&lt;br&gt;
and a &lt;code&gt;created_at&lt;/code&gt; timestamp for the transaction record.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;user_score_transactions&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;          &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;     &lt;span class="nb"&gt;BIGINT&lt;/span&gt;       &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;score_added&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;          &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reason&lt;/span&gt;      &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt;  &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, the &lt;code&gt;user_total_scores&lt;/code&gt; table is dedicated to maintaining the cumulative scores of each user.&lt;br&gt;
It contains an &lt;code&gt;id&lt;/code&gt; for each record, &lt;code&gt;user_id&lt;/code&gt; to reference the users table, &lt;code&gt;total_score&lt;/code&gt; indicating the user's overall score,&lt;br&gt;
and an &lt;code&gt;updated_at&lt;/code&gt; timestamp for the last score update.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;user_total_scores&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;          &lt;span class="n"&gt;BIGSERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt;     &lt;span class="nb"&gt;BIGINT&lt;/span&gt;      &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;total_score&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;         &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt;  &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This schema is particularly effective due to its emphasis on normalization, which reduces redundancy by segregating user&lt;br&gt;
information, score transactions, and total scores into distinct tables.&lt;br&gt;
It ensures scalability with the use of &lt;code&gt;BIGSERIAL&lt;/code&gt; and &lt;code&gt;BIGINT&lt;/code&gt; data types, accommodating a large volume of records.&lt;br&gt;
Additionally, the separate &lt;code&gt;user_score_transactions&lt;/code&gt; table offers valuable insights into the score history for each user,&lt;br&gt;
which is beneficial for analytics and audit trails. We will also create materialized views to further support leaderboards for previous weeks as we will see later.&lt;br&gt;
By isolating the total scores in the &lt;code&gt;user_total_scores&lt;/code&gt; table, the system can swiftly access and update a user's total score, enhancing performance.&lt;br&gt;
This well-structured schema thus forms the backbone of our leaderboard system, supporting both real-time updates and a comprehensive score history.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Dragonfly Keys &amp;amp; Data Types
&lt;/h3&gt;

&lt;p&gt;With the database schema in place, we can now focus on the Dragonfly key-value pairs that will be used to store the leaderboard data.&lt;br&gt;
The &lt;code&gt;Sorted-Set&lt;/code&gt; data type is ideal for storing user scores and rankings, while the &lt;code&gt;Hash&lt;/code&gt; data type is perfect for storing user information that is needed for display purposes.&lt;br&gt;
Here are the keys and data types that we will be using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;leaderboard:user_scores:all_time&lt;/code&gt; (Sorted-Set): Stores the user IDs and scores for the all-time leaderboard.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;leaderboard:user_scores:week_of_{monday_of_the_week}&lt;/code&gt; (Sorted-Set): Stores the user IDs and scores for a specific week.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;leaderboard:users:{user_id}&lt;/code&gt; (HASH): Stores the user information for a specific user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An example of the key space would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; KEYS leaderboard:&lt;span class="k"&gt;*&lt;/span&gt;
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"leaderboard:user_scores:all_time"&lt;/span&gt;           &lt;span class="c"&gt;# Sorted-Set&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"leaderboard:user_scores:week_of_2024_01_15"&lt;/span&gt; &lt;span class="c"&gt;# Sorted-Set&lt;/span&gt;
3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"leaderboard:users:1"&lt;/span&gt;                        &lt;span class="c"&gt;# Hash&lt;/span&gt;
4&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"leaderboard:users:2"&lt;/span&gt;                        &lt;span class="c"&gt;# Hash&lt;/span&gt;
5&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"leaderboard:users:3"&lt;/span&gt;                        &lt;span class="c"&gt;# Hash&lt;/span&gt;
6&lt;span class="o"&gt;)&lt;/span&gt; ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. All-Time &amp;amp; Current-Week Leaderboards
&lt;/h3&gt;

&lt;p&gt;In the implementation of the all-time leaderboard and current-week leaderboard, we focus on how scores are updated for a user and how the top 100 users are queried from these leaderboards.&lt;/p&gt;

&lt;p&gt;To update scores, we first record the score transaction in the &lt;code&gt;user_score_transactions&lt;/code&gt; table and then update the &lt;code&gt;user_total_scores&lt;/code&gt; table.&lt;br&gt;
This operation should be wrapped in a database transaction to ensure data integrity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Record score transaction for user with ID 1.&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;user_score_transactions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;score_added&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'WINNING_A_GAME'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Update total score for user with ID 1.&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;user_total_scores&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;total_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total_score&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we update the all-time leaderboard and current-week leaderboard in Dragonfly.&lt;br&gt;
Note that the operations are better pipelined to reduce the number of round-trips between the application and Dragonfly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ZINCRBY leaderboard:user_scores:all_time 100 1
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ZINCRBY leaderboard:user_scores:week_of_2024_01_15 100 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have persisted with the score change in the database and updated the values in Dragonfly as well,&lt;br&gt;
when querying the top 100 users from a leaderboard (all-time or current-week), we can simply use the &lt;code&gt;ZREVRANGE&lt;/code&gt; command&lt;br&gt;
to retrieve the top users from the &lt;code&gt;Sorted-Set&lt;/code&gt;, and then use the &lt;code&gt;HGETALL&lt;/code&gt; commands to retrieve user details from the &lt;code&gt;Hash&lt;/code&gt; keys.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ZREVRANGE leaderboard:user_scores:all_time 0 99 WITHSCORES
 1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt;    &lt;span class="c"&gt;# user_id = 1&lt;/span&gt;
 2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"1000"&lt;/span&gt; &lt;span class="c"&gt;# score for user_id = 1&lt;/span&gt;
 3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"2"&lt;/span&gt;    &lt;span class="c"&gt;# user_id = 2&lt;/span&gt;
 4&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"900"&lt;/span&gt;  &lt;span class="c"&gt;# score for user_id = 2&lt;/span&gt;
 5&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"3"&lt;/span&gt;
 6&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"800"&lt;/span&gt;
 7&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"4"&lt;/span&gt;
 8&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"700"&lt;/span&gt;
 9&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"5"&lt;/span&gt;
10&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"600"&lt;/span&gt;
&lt;span class="c"&gt;# ...&lt;/span&gt;

dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HGETALL leaderboard:users:1
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HGETALL leaderboard:users:2
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HGETALL leaderboard:users:3
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HGETALL leaderboard:users:4
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HGETALL leaderboard:users:5
&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depending on how many users are recorded in the &lt;code&gt;leaderboard:user_scores:all_time&lt;/code&gt; key,&lt;br&gt;
we need to use 1 &lt;code&gt;ZREVRANGE&lt;/code&gt; command and potentially 100 &lt;code&gt;HGETALL&lt;/code&gt; commands to retrieve the top users.&lt;br&gt;
This may sound like a lot of commands, but once again, we can pipeline these commands to reduce the number of round-trips between the application and Dragonfly.&lt;br&gt;
In fact, the top user scores with their details can be retrieved in a single round-trip, and the response time should still be within a few milliseconds.&lt;br&gt;
On the other hand, we completely avoid the need to query the database for the top users, which is a much more expensive operation.&lt;br&gt;
This is why we are confident in saying that Dragonfly is providing a real-time experience for leaderboard retrieval.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. Leaderboards for Previous Weeks
&lt;/h3&gt;

&lt;p&gt;For the implementation of leaderboards for previous weeks, we adopted a strategy that efficiently balances database querying with caching.&lt;br&gt;
The process involves two main steps: creating materialized views and leveraging Dragonfly's caching capabilities.&lt;/p&gt;

&lt;p&gt;We utilize the &lt;code&gt;user_score_transactions&lt;/code&gt; table to generate materialized views for each past week's leaderboard.&lt;br&gt;
Materialized views are essentially snapshots of the query results, stored for efficient access.&lt;br&gt;
These views are created by aggregating the scores from the &lt;code&gt;user_score_transactions&lt;/code&gt; table for each user over a specific week.&lt;br&gt;
An example SQL statement to create a materialized view for a specific week might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;MATERIALIZED&lt;/span&gt; &lt;span class="k"&gt;VIEW&lt;/span&gt; &lt;span class="n"&gt;leaderboard_week_of_2024_01_15&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ust&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;score_added&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;weekly_score&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;user_score_transactions&lt;/span&gt; &lt;span class="n"&gt;ust&lt;/span&gt;
         &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ust&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;ust&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&lt;/span&gt; &lt;span class="s1"&gt;'2024-01-15 00:00:00'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="s1"&gt;'2024-01-21 23:59:59'&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;weekly_score&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the materialized view for a week's leaderboard is created, we can cache its results in Dragonfly to facilitate quick retrieval.&lt;br&gt;
We utilize Dragonfly's &lt;code&gt;String&lt;/code&gt; data type to store the serialized form of the leaderboard, which can be in JSON, XML, or any other format.&lt;br&gt;
The reason is that past leaderboards cannot be changed anymore, and the order is preserved in the materialized view, so we can simply cache the results as-is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;leaderboard_week_of_2024_01_15&lt;/span&gt; &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; SET leaderboard:cache_top_100:week_of_2024_01_15 &lt;span class="s1"&gt;'serialized_leaderboard_data'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Other Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Calculating the Start of the Week
&lt;/h3&gt;

&lt;p&gt;For the weekly leaderboards, it's essential to have a consistent method to determine the start of each week, commonly set as Monday.&lt;br&gt;
This calculation is vital because it impacts both the naming conventions of keys in Dragonfly and the logic for creating and refreshing materialized views in the database.&lt;br&gt;
Implementing helper methods in the application code that accurately calculate the Monday of any given week is necessary.&lt;br&gt;
This consistency ensures that both the database views and the Dragonfly keys are synchronized in terms of the time periods they represent.&lt;br&gt;
Such an implementation in Go might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// MondayOfTime returns the Monday of the week of the given time.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;MondayOfTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Weekday&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Monday&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;daysToSubtract&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weekday&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Monday&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddDate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daysToSubtract&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// MondayOfTimeStr returns the Monday of the week of the given time in string format.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;MondayOfTimeStr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;MondayOfTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2006_01_02"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Management of Dragonfly Keys
&lt;/h3&gt;

&lt;p&gt;The all-time leaderboard data, represented by a &lt;code&gt;Sorted-Set&lt;/code&gt; key in Dragonfly, is a long-term data set that can be kept indefinitely.&lt;br&gt;
This key does not require an expiration as it continuously accumulates user scores over time.&lt;/p&gt;

&lt;p&gt;Conversely, the current-week &lt;code&gt;Sorted-Set&lt;/code&gt; key in Dragonfly should be managed with an expiration policy.&lt;br&gt;
Setting an expiry time point for this key, preferably at the beginning of the next week, ensures that the data does not become stale and reflects only the current week's scores.&lt;br&gt;
This practice helps in maintaining the relevance and accuracy of the current-week leaderboard.&lt;/p&gt;

&lt;p&gt;And finally, the user-detail &lt;code&gt;Hash&lt;/code&gt; keys in Dragonfly, shared across all-time and current-week leaderboards, can also be kept indefinitely.&lt;br&gt;
However, it's crucial to keep the data in these user-detail &lt;code&gt;Hash&lt;/code&gt; keys up-to-date with the corresponding records in the database.&lt;br&gt;
Whenever a user's details change in the database, these changes should be promptly reflected in the Hash keys in Dragonfly.&lt;br&gt;
This synchronization ensures that the leaderboards always display the most current and accurate user information.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Key Naming Conventions
&lt;/h3&gt;

&lt;p&gt;It's important to adopt a clear and distinct naming convention for different types of data stored in Dragonfly.&lt;br&gt;
Specifically, the key names for the current-week &lt;code&gt;Sorted-Set&lt;/code&gt; and the cached materialized view (&lt;code&gt;String&lt;/code&gt; data type) should be different to prevent confusion.&lt;br&gt;
A clear naming strategy helps avoid accidental operations on the wrong Dragonfly data type.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blog, we explored how Dragonfly can be used in conjunction with a SQL database to build a robust and efficient leaderboard system for gaming and other applications.&lt;br&gt;
We discussed various data types and techniques that can be easily utilized to create real-time leaderboards with minimal update and retrieval latency.&lt;/p&gt;

&lt;p&gt;We have a recorded workshop session, "Scaling Real-Time Leaderboards", that you can watch &lt;a href="https://www.dragonflydb.io/events/2023-12-06-workshop-scaling-real-time-leaderboards" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;br&gt;
Code snippets in this blog post can be found in the Dragonfly &lt;a href="https://github.com/dragonflydb/dragonfly-examples" rel="noopener noreferrer"&gt;examples repository&lt;/a&gt;.&lt;br&gt;
Finally, we encourage you to &lt;a href="https://www.dragonflydb.io/docs/getting-started" rel="noopener noreferrer"&gt;try Dragonfly out for yourself&lt;/a&gt;,&lt;br&gt;
experience its capabilities firsthand, and build amazing applications with it!&lt;/p&gt;

</description>
      <category>database</category>
      <category>gamedev</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>Dragonfly 2023 in Review and Exciting Glimpses of 2024</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Wed, 03 Jan 2024 17:00:00 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/dragonfly-2023-in-review-and-exciting-glimpses-of-2024-3o68</link>
      <guid>https://dev.to/dragonflydbio/dragonfly-2023-in-review-and-exciting-glimpses-of-2024-3o68</guid>
      <description>&lt;p&gt;As we edge closer to the start of a new year, we would like to express our sincere appreciation for the exceptional community that has been instrumental in shaping Dragonfly.&lt;br&gt;
Your consistent support and constructive feedback motivate us to strive for greater achievements, and we look forward to navigating 2024 and beyond together.&lt;/p&gt;

&lt;p&gt;Our commitment to the development of open features that serve the Redis community and the wider developer ecosystem remains firm.&lt;br&gt;
We welcome and prioritize community-driven feature requests and bug fixes, recognizing the invaluable contributions that&lt;br&gt;
help us achieve the goal of building the most efficient in-memory data store, designed with developers' needs in mind.&lt;/p&gt;




&lt;h2&gt;
  
  
  Looking Back at 2023
&lt;/h2&gt;

&lt;p&gt;We released 19 new versions of Dragonfly in 2023, each packed with new features, bug fixes, and performance improvements.&lt;br&gt;
Back in March, we hit our first big milestone with the release of &lt;a href="https://www.dragonflydb.io/blog/dragonfly-production-ready" rel="noopener noreferrer"&gt;Dragonfly v1.0&lt;/a&gt;,&lt;br&gt;
demonstrating our commitment to delivering a production-ready database that is ready to scale with your applications.&lt;br&gt;
Besides the release of v1.0, there are a few more highlights from the past year that we'd like to share with you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lightning-Fast Performance and High Availability
&lt;/h3&gt;

&lt;p&gt;We ignited a performance revolution early in the year.&lt;br&gt;
Dive into our blog post on &lt;a href="https://www.dragonflydb.io/blog/scaling-performance-redis-vs-dragonfly" rel="noopener noreferrer"&gt;turbocharging Dragonfly&lt;/a&gt; to explore how we designed Dragonfly to&lt;br&gt;
deliver an exhilarating experience with lightning-fast response times and tremendously high throughput.&lt;br&gt;
In the meantime, we also introduced snapshots and replications to ensure data durability and &lt;a href="https://www.dragonflydb.io/blog/replication-for-high-availability" rel="noopener noreferrer"&gt;high availability&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Seamless Integrations with Popular Frameworks
&lt;/h3&gt;

&lt;p&gt;Connecting to the broad Redis community was a major objective for us this year.&lt;br&gt;
It serves our quest for compatibility and demonstrates how simple it is to scale and boost performance with Dragonfly.&lt;br&gt;
Look back at the ways we grew over the past year through &lt;a href="https://www.dragonflydb.io/blog/running-bullmq-with-dragonfly-part-1-announcement" rel="noopener noreferrer"&gt;integrations with popular frameworks such as BullMQ&lt;/a&gt;.&lt;br&gt;
BullMQ is a popular, robust, and fast Node.js library for creating and processing background jobs that uses Redis as a backend.&lt;br&gt;
With high compatibility, Dragonfly can be used as a backend data store for BullMQ with no code changes.&lt;br&gt;
Beyond this, we also &lt;a href="https://www.dragonflydb.io/blog/running-bullmq-with-dragonfly-part-2-optimization" rel="noopener noreferrer"&gt;optimized Dragonfly for BullMQ&lt;/a&gt;,&lt;br&gt;
achieving an exceptional 30x throughput increase from the baseline.&lt;/p&gt;

&lt;p&gt;Our latest integration looked at how we &lt;a href="https://www.dragonflydb.io/blog/using-laravel-with-dragonfly-part-01-cache-and-queue" rel="noopener noreferrer"&gt;integrated with Laravel&lt;/a&gt;,&lt;br&gt;
one of the most popular web frameworks not just in PHP but across all languages, as the cache, session, and queue backend.&lt;br&gt;
This again highlights Dragonfly's compatibility.&lt;br&gt;
By simply changing the Redis connection string, you can use Dragonfly as a drop-in replacement for Redis in Laravel,&lt;br&gt;
instantly benefiting from its high performance and throughput, low memory usage, and efficient snapshotting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud-Native Deployment and Management
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.dragonflydb.io/blog/announcing-kubernetes-operator-general-availability" rel="noopener noreferrer"&gt;Dragonfly Kubernetes Operator&lt;/a&gt;&lt;br&gt;
release makes it extremely simple to deploy Dragonfly in Kubernetes environments.&lt;br&gt;
This release offers benefits such as high availability with custom failover strategies, direct snapshots to cloud storage (e.g., S3),&lt;br&gt;
seamless integration with cloud-native monitoring tools (e.g., Prometheus and Grafana), and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Revolution
&lt;/h3&gt;

&lt;p&gt;2023 marked the beginning of the AI revolution with GPT-4 and other LLM models that will affect the future of our lives.&lt;br&gt;
To connect businesses to the revolution, we &lt;a href="https://www.dragonflydb.io/blog/announcing-dragonfly-search" rel="noopener noreferrer"&gt;introduced Dragonfly Search&lt;/a&gt;,&lt;br&gt;
which allows the storage, retrieval, and searching of AI-generated embeddings utilizing vector similarity search (VSS) capabilities.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Glimpse of Dragonfly in 2024
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Dragonfly Cloud
&lt;/h3&gt;

&lt;p&gt;Exciting developments await in the cloud!&lt;br&gt;
We will continue to add features and functionality to &lt;a href="https://www.dragonflydb.io/cloud" rel="noopener noreferrer"&gt;Dragonfly Cloud&lt;/a&gt; with the goal of giving developers the most scalable&lt;br&gt;
and cost-effective managed Dragonfly service across multiple cloud providers.&lt;br&gt;
Currently, Dragonfly Cloud is onboarding customers on a waitlist basis.&lt;br&gt;
We aim to open up this highly anticipated service to the public in 2024.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hardware Efficiency and Persistence
&lt;/h3&gt;

&lt;p&gt;We designed Dragonfly's multi-threaded architecture to be able to utilize modern cloud hardware much more efficiently than legacy solutions.&lt;br&gt;
Recent advancements in SSD performance allow us to take efficiency to new levels.&lt;br&gt;
We plan to deliver a solution where data is seamlessly shared between memory and SSD while still preserving sub-millisecond guarantees.&lt;br&gt;
This will allow developers great flexibility while dramatically reducing the associated costs of maintaining their Dragonfly instances.&lt;/p&gt;

&lt;h3&gt;
  
  
  Community Centric Development
&lt;/h3&gt;

&lt;p&gt;In 2024, we're launching an array of community initiatives, including monthly office hours every second Wednesday of each month&lt;br&gt;
and online workshops for you to learn and get up and running.&lt;br&gt;
We want to hear your feedback, answer your questions, and collaborate on making Dragonfly the best it can be.&lt;/p&gt;

&lt;h3&gt;
  
  
  Meet Us at Events Near You!
&lt;/h3&gt;

&lt;p&gt;Dragonfly is taking to the skies in 2024, with a series of events around the globe.&lt;br&gt;
Stay tuned for event announcements on our &lt;a href="https://www.dragonflydb.io/events" rel="noopener noreferrer"&gt;events page&lt;/a&gt;,&lt;br&gt;
and let's make 2024 a year of in-person connections, insights, and shared enthusiasm.&lt;/p&gt;




&lt;h2&gt;
  
  
  Thank You
&lt;/h2&gt;

&lt;p&gt;As we stand at the threshold of a new year, we're filled with gratitude for the amazing community that has made Dragonfly what it is today.&lt;br&gt;
Your support and feedback drive us to reach new heights.&lt;br&gt;
We can't wait to continue on this thrilling journey with the goal of building the most efficient in-memory data store in the universe.&lt;br&gt;
Here's to innovating faster and building a future where Dragonfly continues to empower developers worldwide.&lt;/p&gt;

&lt;p&gt;Farewell to 2023. Now, let's fasten our seatbelts and embark on the journey to make 2024 another year to remember!&lt;/p&gt;

</description>
      <category>database</category>
      <category>redis</category>
    </item>
    <item>
      <title>Using Laravel with Dragonfly</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Mon, 18 Dec 2023 16:54:30 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/using-laravel-with-dragonfly-1p0</link>
      <guid>https://dev.to/dragonflydbio/using-laravel-with-dragonfly-1p0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Dragonfly is a drop-in Redis replacement that delivers far better performance with far fewer servers.&lt;br&gt;
A single node can handle millions of queries per second and up to 1TB of in-memory data.&lt;br&gt;
In this blog post, we will explore how to use Dragonfly with Laravel, one of the most widely used and well-known web frameworks.&lt;/p&gt;

&lt;p&gt;Dragonfly maintains full compatibility with the Redis interface,&lt;br&gt;
meaning Laravel developers can integrate it as a cache and queue driver without a single line of code change.&lt;br&gt;
This seamless integration offers an effortless upgrade path with substantial benefits.&lt;/p&gt;

&lt;p&gt;So, whether you are a seasoned Laravel veteran or just starting out, join us as we step into the world of Dragonfly and Laravel.&lt;/p&gt;


&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Let's start by setting up a new Dragonfly instance.&lt;br&gt;
Visit our documentation &lt;a href="https://www.dragonflydb.io/docs/getting-started" rel="noopener noreferrer"&gt;here&lt;/a&gt; to download an image or the binary and have a Dragonfly instance up and running in no time.&lt;br&gt;
Once the Dragonfly instance is operational and reachable, integrating it with your Laravel project is a breeze.&lt;br&gt;
Luckily, Laravel already has full support for Redis, so all of its drivers can be reused.&lt;br&gt;
To use Dragonfly in your Laravel application, start by updating the &lt;code&gt;.env&lt;/code&gt; file with the following configurations.&lt;/p&gt;

&lt;p&gt;For caching and session management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;CACHE_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis
&lt;span class="nv"&gt;SESSION_DRIVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To integrate Dragonfly as the queue driver as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;QUEUE_CONNECTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;redis
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though we are using &lt;code&gt;redis&lt;/code&gt; as the driver value, Dragonfly is designed to be a direct replacement for Redis, so no additional driver installation is required.&lt;br&gt;
With the driver set, the next step is to ensure Laravel can communicate with the Dragonfly instance.&lt;br&gt;
This involves updating the &lt;code&gt;.env&lt;/code&gt; file again with the correct connection details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;REDIS_HOST&lt;/code&gt;: The hostname or IP address of the Dragonfly server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REDIS_PORT&lt;/code&gt;: The port on which the Dragonfly instance is running.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;REDIS_PASSWORD&lt;/code&gt;: The password for the Dragonfly instance, if set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;REDIS_HOST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1 &lt;span class="c"&gt;# Replace with Dragonfly host&lt;/span&gt;
&lt;span class="nv"&gt;REDIS_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6379      &lt;span class="c"&gt;# Replace with Dragonfly port&lt;/span&gt;
&lt;span class="nv"&gt;REDIS_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;null  &lt;span class="c"&gt;# Replace with Dragonfly password if applicable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After updating these settings, verify the connection by running a simple operation like &lt;code&gt;INFO&lt;/code&gt; in Laravel.&lt;br&gt;
If you encounter any connectivity issues, double-check the host, port, and password values.&lt;br&gt;
Also, ensure that the Dragonfly server is running and accessible from your Laravel application's environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Redis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Run the INFO command and print the Dragonfly version.&lt;/span&gt;
&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s2"&gt;"dragonfly_version"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Higher Efficiency as a Cache
&lt;/h2&gt;

&lt;p&gt;Caching commonly accessed values is one of the primary uses of in-memory databases like Dragonfly and Redis due to their fast response times.&lt;br&gt;
This is where Dragonfly shines, especially in scenarios involving a large number of keys and clients, typical as a central cache of multi-node systems or microservices.&lt;/p&gt;

&lt;p&gt;Dragonfly's prowess is not just in its speed but also in its intelligent memory management.&lt;br&gt;
A standout feature is the &lt;strong&gt;cache mode&lt;/strong&gt;, designed specifically for scenarios where maintaining a lean memory footprint is as crucial as performance.&lt;br&gt;
In this mode, Dragonfly smartly evicts the least recently accessed values when it detects low memory availability, ensuring efficient memory usage without sacrificing speed.&lt;br&gt;
You can read more about the eviction algorithm in the &lt;a href="https://www.dragonflydb.io/blog/dragonfly-cache-design" rel="noopener noreferrer"&gt;Dragonfly Cache Design blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Activating the cache mode is straightforward.&lt;br&gt;
Here are the flags you would use to run Dragonfly in this mode, with a memory cap of 12GB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./dragonfly &lt;span class="nt"&gt;--cache_mode&lt;/span&gt; &lt;span class="nt"&gt;--maxmemory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;12G
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consider a scenario where your application needs to handle a high volume of requests with a vast dataset.&lt;br&gt;
In such cases, the Dragonfly cache mode can efficiently manage memory usage while providing rapid access to data, ensuring your application remains responsive and agile.&lt;/p&gt;

&lt;p&gt;API-wise, all functionality of the Laravel Cache facade should be supported.&lt;br&gt;
For example, to store a given key and value with a specific expiration time, the following snippet can be used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Facades\Cache&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Store a value with a 10 minute expiration time.&lt;/span&gt;
&lt;span class="nc"&gt;Cache&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Memory Usage
&lt;/h3&gt;

&lt;p&gt;One of the benefits of using Dragonfly as a cache is its &lt;strong&gt;measurably lower memory usage&lt;/strong&gt; for most use cases.&lt;br&gt;
Let's conduct a simple experiment and fill both Redis and Dragonfly with random strings, measuring their total memory usage after filling them with data.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dataset&lt;/th&gt;
&lt;th&gt;Dragonfly&lt;/th&gt;
&lt;th&gt;Redis&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3 Million Values of Length 1000&lt;/td&gt;
&lt;td&gt;2.75GB&lt;/td&gt;
&lt;td&gt;3.17GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15 Million Values of Length 200&lt;/td&gt;
&lt;td&gt;3.8GB&lt;/td&gt;
&lt;td&gt;4.6GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;After conducting the experiment, we've observed that Dragonfly's memory usage is up to 20% lower compared to Redis under similar conditions.&lt;br&gt;
This allows you to store significantly more useful data with the same memory requirements, making the cache more efficient and achieving higher coverage.&lt;br&gt;
You can read more about Dragonfly throughput benchmarks and memory usage in the &lt;a href="https://www.dragonflydb.io/blog/scaling-performance-redis-vs-dragonfly" rel="noopener noreferrer"&gt;Redis vs. Dragonfly Scalability and Performance blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Snapshotting
&lt;/h3&gt;

&lt;p&gt;Beyond just lower memory usage, Dragonfly also demonstrates remarkable stability during snapshotting processes.&lt;br&gt;
Snapshotting, particularly in busy instances, can be a challenge in terms of memory management.&lt;br&gt;
With Redis, capturing a snapshot on a highly active instance might lead to increased memory usage.&lt;br&gt;
This happens because Redis needs to copy memory pages, even those that have only been partially overwritten, resulting in a spike in memory usage.&lt;/p&gt;

&lt;p&gt;Dragonfly, in contrast, takes a more adaptive approach to snapshotting.&lt;br&gt;
It intelligently adjusts the order of snapshotting based on incoming requests, effectively preventing any unexpected surges in memory usage.&lt;br&gt;
This means that even during intensive operations like snapshotting, Dragonfly maintains a stable memory footprint, ensuring consistent performance without the risk of sudden memory spikes.&lt;br&gt;
You can read more about the Dragonfly snapshotting algorithm in the &lt;a href="https://www.dragonflydb.io/blog/balanced-vs-unbalanced" rel="noopener noreferrer"&gt;Balanced vs. Unbalanced blog post&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Key Stickiness
&lt;/h3&gt;

&lt;p&gt;Dragonfly also introduces a new feature with its custom &lt;a href="https://www.dragonflydb.io/docs/command-reference/generic/stick" rel="noopener noreferrer"&gt;&lt;code&gt;STICK&lt;/code&gt;&lt;/a&gt; command.&lt;br&gt;
This command is particularly useful in instances running in cache mode.&lt;br&gt;
&lt;strong&gt;It enables specific keys to be marked as non-evicting, irrespective of their access frequency.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This functionality is especially handy for storing seldom-accessed yet important data.&lt;br&gt;
For example, you can reliably keep auxiliary information, like dynamic configuration values, directly on your Dragonfly instance.&lt;br&gt;
This eliminates the need for a separate datastore for infrequently used but crucial data, streamlining your data management process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Storing a value in the Dragonfly instance with stickiness.&lt;/span&gt;
&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Redis&lt;/span&gt; &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'server-dynamic-configuration-key'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'...'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'STICK'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'server-dynamic-configuration-key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="c1"&gt;// Will always return a value since the key cannot be evicted.&lt;/span&gt;
&lt;span class="nv"&gt;$redis&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'server-dynamic-configuration-key'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Enhanced Throughput in Queue Management
&lt;/h2&gt;

&lt;p&gt;Dragonfly, much like Redis, is adept at managing queues and jobs.&lt;br&gt;
As you might have already guessed, the transition to using Dragonfly for this purpose is seamless, requiring no code modifications.&lt;br&gt;
Consider the following example in Laravel, where a podcast processing job is dispatched:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Jobs\ProcessPodcast&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$podcast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Podcast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;ProcessPodcast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatchSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$podcast&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both Dragonfly and Redis are capable of handling tens of thousands of jobs per second with ease.&lt;/p&gt;

&lt;p&gt;For those aiming to maximize performance, it's important to note that using a single job queue won't yield significant performance gains.&lt;br&gt;
To truly leverage Dragonfly's capabilities, multiple queues should be utilized.&lt;br&gt;
This approach distributes the load across multiple Dragonfly threads, enhancing overall throughput.&lt;/p&gt;

&lt;p&gt;However, a common challenge arises when keys from the same queue end up on different threads, leading to increased latency.&lt;br&gt;
To counter this, Dragonfly offers the use of &lt;a href="https://redis.io/docs/reference/cluster-spec/#hash-tags" rel="noopener noreferrer"&gt;hashtags&lt;/a&gt; in queue names.&lt;br&gt;
These hashtags ensure that jobs in the same queue (which uses the same hashtag) are automatically assigned to specific threads,&lt;br&gt;
much like in a Redis Cluster environment, thereby reducing latency and optimizing performance.&lt;br&gt;
To learn more about hashtags, check out the &lt;a href="https://www.dragonflydb.io/blog/running-bullmq-with-dragonfly-part-1-announcement" rel="noopener noreferrer"&gt;Running BullMQ with Dragonfly blog post&lt;/a&gt;,&lt;br&gt;
which has a detailed explanation of hashtags and their benefits, while Dragonfly is used as a backing store for message queue systems.&lt;/p&gt;

&lt;p&gt;As a quick example, to optimize your queue management with Dragonfly, start by launching Dragonfly with specific flags that enable hashtag-based locking and emulated cluster mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./dragonfly &lt;span class="nt"&gt;--lock_on_hashtags&lt;/span&gt; &lt;span class="nt"&gt;--cluster_mode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;emulated
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once Dragonfly is running with these settings, incorporate hashtags into your queue names in Laravel. Here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nc"&gt;ProcessPodcast&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;dispatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$podcast&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;onQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{podcast_queue}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using hashtags in queue names, you ensure that all messages belonging to the same queue are processed by the same thread in Dragonfly.&lt;br&gt;
This approach not only keeps related messages together, enhancing efficiency, but also allows Dragonfly to maximize throughput by distributing different queues across multiple threads.&lt;/p&gt;

&lt;p&gt;This method is particularly effective for systems that rely on Dragonfly as a message queue backing store,&lt;br&gt;
as it leverages Dragonfly's multi-threaded architecture to handle a higher volume of messages more efficiently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Dragonfly emerges as a powerful and efficient alternative to Redis.&lt;br&gt;
Its ability to handle massive workloads with lower memory usage and its multi-threaded architecture make it a compelling choice for modern web applications.&lt;br&gt;
Throughout this post, we've explored how Dragonfly seamlessly integrates with Laravel, &lt;strong&gt;requiring minimal to no code changes&lt;/strong&gt;, whether it's for &lt;strong&gt;caching&lt;/strong&gt;, &lt;strong&gt;session management&lt;/strong&gt;, or &lt;strong&gt;queue management&lt;/strong&gt;.&lt;br&gt;
The unique features like cache mode, key stickiness, and hashtag-based thread balancing further illustrate Dragonfly's innovative approach as an in-memory data store.&lt;/p&gt;

&lt;p&gt;And as always, we encourage you to &lt;a href="https://www.dragonflydb.io/docs/getting-started" rel="noopener noreferrer"&gt;get started&lt;/a&gt; with Dragonfly within just a few minutes.&lt;br&gt;
Subscribe to our newsletter below and get connected with us on &lt;a href="https://github.com/dragonflydb/dragonfly" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://discord.gg/HsPjXGVH85" rel="noopener noreferrer"&gt;Discord&lt;/a&gt; to stay up-to-date with the latest developments.&lt;/p&gt;

</description>
      <category>database</category>
      <category>redis</category>
      <category>laravel</category>
    </item>
    <item>
      <title>Announcing Dragonfly Search</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Thu, 07 Dec 2023 17:00:00 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/announcing-dragonfly-search-4c72</link>
      <guid>https://dev.to/dragonflydbio/announcing-dragonfly-search-4c72</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;2023 has been a year with remarkable advancements in AI capabilities, and at Dragonfly, we are thrilled to power new use cases with our latest release: Dragonfly Search.&lt;br&gt;
This new feature set, debuting in &lt;a href="https://github.com/dragonflydb/dragonfly/releases/tag/v1.13.0" rel="noopener noreferrer"&gt;Dragonfly v1.13&lt;/a&gt;, is a &lt;a href="https://www.dragonflydb.io/docs/command-reference/search/ft.search" rel="noopener noreferrer"&gt;subset of RediSearch-compatible commands&lt;/a&gt; implemented natively in Dragonfly,&lt;br&gt;
allowing for both &lt;em&gt;vector search&lt;/em&gt; and &lt;em&gt;faceted search&lt;/em&gt; use cases in the highly scalable and performant Dragonfly in-memory data store.&lt;/p&gt;

&lt;p&gt;In this post, we will guide you through building a simple recommendation system utilizing OpenAI's embeddings in conjunction with Dragonfly's vector search capabilities.&lt;br&gt;
Additionally, we'll explore how Dragonfly can serve as a versatile document store, demonstrating its flexibility and efficiency in handling diverse data management tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dragonfly Search is being released in Beta.&lt;/strong&gt;&lt;br&gt;
We are excited about its development and future potential, but we do not encourage its use in production environments at the time of this writing.&lt;br&gt;
Your feedback is immensely valuable to us, and it plays a critical role in shaping and improving Dragonfly Search as we progress towards a more stable version.&lt;br&gt;
If you have any feedback, please create a &lt;a href="https://github.com/dragonflydb/dragonfly/issues" rel="noopener noreferrer"&gt;GitHub issue&lt;/a&gt; or drop us a link in &lt;a href="https://discord.com/invite/HsPjXGVH85" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you want to learn more about Dragonfly Search, &lt;a href="https://www.dragonflydb.io/office-hours-2023-12-13" rel="noopener noreferrer"&gt;please register for our Community Office Hours&lt;/a&gt;,&lt;br&gt;
where the team will give a technical presentation and take questions.&lt;/p&gt;


&lt;h2&gt;
  
  
  Fundamentals of Dragonfly Search
&lt;/h2&gt;

&lt;p&gt;Dragonfly Search enables the creation of indexes for selected &lt;code&gt;HASH&lt;/code&gt; and &lt;code&gt;JSON&lt;/code&gt; values.&lt;br&gt;
Entries stored within or associated with an index are often referred to as documents.&lt;br&gt;
Each index is constructed based on a specific schema, defining the fields within the indexed values and the way they should be interpreted.&lt;br&gt;
Once established, this index facilitates filtering and sorting documents by various properties, much like a traditional database manages conditional queries.&lt;/p&gt;

&lt;p&gt;Let's suppose we use Dragonfly to store information about the world's largest cities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx3n90d9cuo0y3b564jr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx3n90d9cuo0y3b564jr.png" alt="docs-and-indexes" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For each city, we store key information including its &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;population&lt;/code&gt;, and &lt;code&gt;continent&lt;/code&gt;. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HSET city:1 name London population 8.8 continent Europe
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HSET city:2 name Athens population 3.1 continent Europe
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HSET city:3 name Tel-Aviv population 1.3 continent Asia
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HSET city:4 name Hyderabad population 9.8 continent Asia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To build an index, we use the &lt;code&gt;FT.CREATE&lt;/code&gt; command.&lt;br&gt;
Firstly, we define the index name and the subset of values to index, such as those with keys prefixed with &lt;code&gt;city:&lt;/code&gt;.&lt;br&gt;
And then, we outline our schema attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;name&lt;/code&gt; attribute of type &lt;code&gt;TEXT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;population&lt;/code&gt; attribute as a &lt;code&gt;NUMERIC&lt;/code&gt; type with sorting enabled.&lt;/li&gt;
&lt;li&gt;Finally, the &lt;code&gt;continent&lt;/code&gt; attribute as a &lt;code&gt;TAG&lt;/code&gt; type. Read more about &lt;code&gt;TAG&lt;/code&gt; fields &lt;a href="https://redis.io/docs/interact/search-and-query/advanced-concepts/tags/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; FT.CREATE cities PREFIX 1 city: SCHEMA name TEXT population NUMERIC SORTABLE continent TAG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;After creating the index, the &lt;code&gt;FT.INFO&lt;/code&gt; command can be used to inspect its details.&lt;br&gt;
As shown below, the index conforms to the schema we defined, and it contains the hash documents we created earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; FT.INFO cities
1&lt;span class="o"&gt;)&lt;/span&gt; index_name
2&lt;span class="o"&gt;)&lt;/span&gt; cities
3&lt;span class="o"&gt;)&lt;/span&gt; fields
4&lt;span class="o"&gt;)&lt;/span&gt; 1&lt;span class="o"&gt;)&lt;/span&gt; 1&lt;span class="o"&gt;)&lt;/span&gt; identifier
      2&lt;span class="o"&gt;)&lt;/span&gt; name
      3&lt;span class="o"&gt;)&lt;/span&gt; attribute
      4&lt;span class="o"&gt;)&lt;/span&gt; name
      5&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;type
      &lt;/span&gt;6&lt;span class="o"&gt;)&lt;/span&gt; TEXT
   &lt;span class="c"&gt;# schema for 'population' and 'continent' omitted for brevity...&lt;/span&gt;
5&lt;span class="o"&gt;)&lt;/span&gt; num_docs
6&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt; 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Moving on to querying!&lt;/p&gt;

&lt;p&gt;Our first example query will focus on cities in Europe.&lt;br&gt;
We'll sort them by population in descending order and select only the top one document without skipping any.&lt;br&gt;
The query is also constructed to return only two fields for each result: &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;population&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The response contains the total number of documents matched, regardless of the &lt;code&gt;LIMIT&lt;/code&gt; option, and the documents themselves.&lt;br&gt;
In this case, only London will be returned, displaying first its key and then the selected fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; FT.SEARCH cities &lt;span class="s1"&gt;'@continent:{Europe}'&lt;/span&gt; SORTBY population DESC LIMIT 0 1 RETURN 2 name population
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt; 2 &lt;span class="c"&gt;# total number of documents matched&lt;/span&gt;
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"city:1"&lt;/span&gt;    &lt;span class="c"&gt;# document key (i.e. the key to the HASH document)&lt;/span&gt;
3&lt;span class="o"&gt;)&lt;/span&gt; 1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;   &lt;span class="c"&gt;# selected fields and their values&lt;/span&gt;
   2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"London"&lt;/span&gt;
   3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"population"&lt;/span&gt;
   4&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"8.8"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our second example query aims to display all cities with a population under 5 million that are situated in Asia as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; FT.SEARCH cities &lt;span class="s1"&gt;'@population:[0 5] @continent:{Asia}'&lt;/span&gt; RETURN 1 name
1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;integer&lt;span class="o"&gt;)&lt;/span&gt; 1
2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"city:3"&lt;/span&gt;
3&lt;span class="o"&gt;)&lt;/span&gt; 1&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt;
   2&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"Tel-Aviv"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For detailed information on the query syntax, refer to our &lt;a href="https://www.dragonflydb.io/docs/command-reference/search/ft.search" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The index is dynamic; it automatically updates as document values are added or removed.&lt;br&gt;
In a later section of this blog post, we will delve into the storage of JSON values.&lt;br&gt;
Contrary to simple hashes, JSON documents can store nested values and arrays, enabling the indexing of more complex data structures.&lt;/p&gt;


&lt;h2&gt;
  
  
  Vector Search: Finding the Closest Match
&lt;/h2&gt;

&lt;p&gt;After exploring how to create and query indices in the previous chapter, we now turn our attention to the use of the &lt;code&gt;VECTOR&lt;/code&gt; field type.&lt;br&gt;
This section will demonstrate building a simple recommendation engine using &lt;a href="https://openai.com/" rel="noopener noreferrer"&gt;OpenAI&lt;/a&gt;'s embeddings.&lt;/p&gt;

&lt;p&gt;Vector fields can be used for vector similarity search where the goal is to find documents with vector fields most similar to a given vector.&lt;br&gt;
Vectors are extremely powerful, as they can encode various complex objects like text, images, and music.&lt;br&gt;
The underlying models aim for a fundamental principle: the closer the vectors, the greater the similarity between the original objects.&lt;br&gt;
These vectors are colloquially called embeddings, as they embed the original objects into a vector space.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp08wfev2nthx1xzb26v7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp08wfev2nthx1xzb26v7.png" alt="vector-space-representation" width="800" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the realm of modern applications, vector databases are crucial for executing vector similarity searches.&lt;br&gt;
Our example illustrates building a simple service to recommend blog articles to users based on their interests.&lt;br&gt;
To convert the text of our blog posts into vectors, we'll utilize OpenAI's service.&lt;/p&gt;

&lt;p&gt;The preliminary step of gathering all our blog posts along with their embeddings in a CSV file &lt;code&gt;blog-with-embeddings.csv&lt;/code&gt; has been completed,&lt;br&gt;
which can be found in our &lt;a href="https://github.com/dragonflydb/dragonfly-examples" rel="noopener noreferrer"&gt;dragonfly-examples repository&lt;/a&gt;.&lt;br&gt;
Now, let's begin by loading this file using the &lt;code&gt;pandas&lt;/code&gt; Python library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;blog-with-embeddings.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delimiter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quotechar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;converters&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The table shows that each document contains a few fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;title&lt;/code&gt; field is the blog post title.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;content&lt;/code&gt; field is the blog post content.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;embedding&lt;/code&gt; field is the vectorized content.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The following step involves initializing Dragonfly, then connecting to it using the official Python Redis client to create our index.&lt;br&gt;
We don't need the raw content to be indexed, as we will index the vectorized content instead.&lt;/p&gt;

&lt;p&gt;Note that the &lt;code&gt;VectorField&lt;/code&gt; constructor accepts additional parameters, such as the algorithm type and the vector dimensions.&lt;br&gt;
&lt;code&gt;FLAT&lt;/code&gt; is the selected algorithm type and represents brute-force search.&lt;br&gt;
An alternative, &lt;code&gt;HNSW&lt;/code&gt; (Hierarchical Navigable Small World), is also available.&lt;br&gt;
While &lt;code&gt;HNSW&lt;/code&gt; can provide approximate results with reduced computational demands,&lt;br&gt;
it consumes more memory and provides faster search speed on larger datasets.&lt;/p&gt;

&lt;p&gt;The configuration options also define the vector dimensions, in this case, 1536 dimensions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;redis.commands.search.field&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VectorField&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;redis.commands.search.indexDefinition&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;IndexDefinition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IndexType&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Redis&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;posts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;create_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;VectorField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FLAT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DIM&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1536&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})],&lt;/span&gt;
        &lt;span class="n"&gt;definition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;IndexDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;index_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;IndexType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HASH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our blog posts are represented using the &lt;code&gt;HASH&lt;/code&gt; data type in Dragonfly.&lt;br&gt;
When using hashes, vectors must be encoded in a binary format.&lt;br&gt;
For this purpose, we'll employ the &lt;code&gt;numpy&lt;/code&gt; Python library.&lt;br&gt;
It's important to note that Dragonfly currently supports only the &lt;code&gt;float32&lt;/code&gt; data type.&lt;br&gt;
This means each vector should be encoded using 4 bytes per number.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iterrows&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;embedding_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tobytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;post-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mapping&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;embedding_bytes&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've managed to set everything up with just a few lines of code!&lt;br&gt;
The final step involves converting user queries into vectors and then querying Dragonfly with these vectors.&lt;br&gt;
Note that in order to perform the following step, an OpenAI API key is required.&lt;br&gt;
Learn more about obtaining an API key &lt;a href="https://platform.openai.com/docs/quickstart" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For vector similarity queries, a special syntax is used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;KNN 3 @embedding &lt;span class="nv"&gt;$query_vector&lt;/span&gt; AS vector_score]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;*&lt;/code&gt; part represents the filter expression, which can limit the documents considered for the vector similarity search. Using just &lt;code&gt;*&lt;/code&gt; selects all documents.&lt;/li&gt;
&lt;li&gt;The number &lt;code&gt;3&lt;/code&gt; specifies that the three closest vectors will be computed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@embedding&lt;/code&gt; denotes the document field where the vectors are stored.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$query_vector&lt;/code&gt; is the parameter name containing the target vector.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AS vector_score&lt;/code&gt; indicates the name under which the vector distance will be returned.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;redis.commands.search.query&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Query&lt;/span&gt;

&lt;span class="c1"&gt;# How to get an OpenAI API key: https://platform.openai.com/docs/api-reference/introduction
# NOTE: Do not share your API key with anyone, do not commit it to git, do not hardcode it in your code.
&lt;/span&gt;&lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{YOUR_OPENAI_API_KEY}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;EMBEDDING_MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text-embedding-ada-002&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Convert query text to vector using the OpenAI API.
&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How to switch from a multi node redis setup to Dragonfly&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;query_vec&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;EMBEDDING_MODEL&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;embedding&lt;/span&gt;

&lt;span class="c1"&gt;# Build a search query for Dragonfly.
&lt;/span&gt;&lt;span class="n"&gt;query_expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*=&amp;gt;[KNN 3 @embedding $query_vector AS vector_score]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;return_fields&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vector_score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;paging&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;query_vector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_vec&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;float32&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;tobytes&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;

&lt;span class="c1"&gt;# Execute the query and print results.
&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;posts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_expr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vector_score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# === Output ===
# 1 0.562158 Zero Downtime Migration from Redis to Dragonfly using Redis Sentinel
# 2 0.568551 Migrating from a Redis Cluster to Dragonfly on a single node
# 3 0.606661 We're Ready for You Now: Dragonfly In-Memory DB Now Supports Replication for High Availability
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown above, with a few simple steps, we've managed to build a simple recommendation system using Dragonfly Search and OpenAI's embeddings.&lt;br&gt;
Given that &lt;a href="https://github.com/langchain-ai/langchain" rel="noopener noreferrer"&gt;LangChain&lt;/a&gt; is based on OpenAI and Vector Similarity Search (VSS) technologies, Dragonfly Search is compatible with it as well.&lt;br&gt;
This compatibility enhances the range of applications and functionalities Dragonfly Search can support, tapping into the advanced capabilities of Large Language Models (LLMs).&lt;/p&gt;


&lt;h2&gt;
  
  
  Querying JSON Documents
&lt;/h2&gt;

&lt;p&gt;In this final part, we demonstrate how to build an issue tracker using Dragonfly.&lt;br&gt;
We'll be using JavaScript, one of the most commonly used programming languages.&lt;br&gt;
To simplify document management, we'll utilize the &lt;a href="https://github.com/redis/redis-om-node" rel="noopener noreferrer"&gt;redis-om-node&lt;/a&gt; library, which provides an object-mapping interface for Node.js.&lt;br&gt;
Again, as Dragonfly is highly compatible with Redis, we can use the same library to interact with Dragonfly.&lt;/p&gt;

&lt;p&gt;Let's take a look at a sample issue object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Production error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1701203321&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bug&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;important&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bob&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wow, did this really happen?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1701203648&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;caren&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;We should fix this immediately!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1701203954&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll store issue objects like above as &lt;code&gt;JSON&lt;/code&gt; values within Dragonfly.&lt;br&gt;
The advantage of indexing JSON values is that a schema field can map to not just a root-level object field, but to an entire JSONPath.&lt;br&gt;
JSONPaths are incredibly useful for selecting values from nested structures and arrays.&lt;/p&gt;

&lt;p&gt;Now, let's define our schema using redis-om:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;EntityId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;redis-om&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Create client and connect to Dragonfly.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dragonfly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;dragonfly&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Build the schema.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;issue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.author&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sortable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;

          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string[]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$.tags[*]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string[]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;$..author&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;

          &lt;span class="na"&gt;num_comments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;length($.comments)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;sortable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="na"&gt;last_updated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;number&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;max($.comments[*].updated)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;sortable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;dataStructure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;JSON&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Build repository using the schema and Dragonfly client.&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;issueRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dragonfly&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Create index for the repository.&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;issueRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createIndex&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use the repository to save the 'issue' object we defined earlier into Dragonfly.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;issueRepository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the schema definition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first few fields, &lt;code&gt;author&lt;/code&gt;, &lt;code&gt;title&lt;/code&gt;, and &lt;code&gt;created&lt;/code&gt;, select values directly from the root-level object using the &lt;code&gt;$.field&lt;/code&gt; syntax.&lt;/li&gt;
&lt;li&gt;As each post may include multiple tags, the &lt;code&gt;tags&lt;/code&gt; field is used to select an array.&lt;/li&gt;
&lt;li&gt;To track all participants in an issue, including those who comment, we use the &lt;code&gt;$..author&lt;/code&gt; JSONPath.
This path selects the &lt;code&gt;author&lt;/code&gt; fields from all objects, including comments.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;num_comments&lt;/code&gt; and &lt;code&gt;last_updated&lt;/code&gt; fields illustrate the usage of simple aggregation functions within JSONPaths.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With the schema in place and a few entries created, we can now leverage the query builder to formulate more intricate queries.&lt;/p&gt;

&lt;p&gt;Imagine we want to create a dashboard for Alice's homepage on our issue tracker website.&lt;br&gt;
We can achieve this by selecting all issues authored by &lt;code&gt;alice&lt;/code&gt;, tagged as &lt;code&gt;important&lt;/code&gt;, and sorting them to display the most recently updated ones first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Search for issues:&lt;/span&gt;
&lt;span class="c1"&gt;//  - authored by 'alice'&lt;/span&gt;
&lt;span class="c1"&gt;//  - tagged as 'important'&lt;/span&gt;
&lt;span class="c1"&gt;//  - sort results by 'last_updated'&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;issues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;issueRepository&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;author&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tags&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;important&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sortDescending&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;last_updated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issues&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As shown above, with storing JSON documents in Dragonfly, building index schema utilizing JSONPaths,&lt;br&gt;
and using the query builder, we can easily leverage Dragonfly Search capabilities to build applications that require complex data management.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Dragonfly Search represents a significant leap forward in data management and search capabilities for our in-memory data store.&lt;br&gt;
It blends the flexibility of traditional database queries with the advanced features of modern AI technologies.&lt;br&gt;
However, Dragonfly Search is currently in Beta.&lt;br&gt;
As Dragonfly Search progresses, our vision for its evolution is clear and ambitious.&lt;br&gt;
We recognize current limitations as opportunities for growth and innovation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster Updates&lt;/strong&gt;: Though query performance is robust, we are actively working on speeding up the update process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GeoSearch&lt;/strong&gt;: We will support the &lt;code&gt;GEO&lt;/code&gt; field type and its related command options.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command Options&lt;/strong&gt;: More &lt;code&gt;FT.CREATE&lt;/code&gt; and &lt;code&gt;FT.SEARCH&lt;/code&gt; options will be supported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoring and Full-Text Search&lt;/strong&gt;: Implementing scoring mechanisms and full-text search functionalities are key objectives as well.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, with existing features, we've already seen how Dragonfly Search simplifies complex tasks, from creating efficient indexes to harnessing the power of vector similarity searches with OpenAI embeddings.&lt;br&gt;
Our exploration into using Dragonfly for diverse applications, such as building a recommendation system or an issue tracker, demonstrates its versatility and ease of use.&lt;br&gt;
If you want to learn more about Dragonfly Search, &lt;a href="https://www.dragonflydb.io/office-hours-2023-12-13" rel="noopener noreferrer"&gt;please register for our Community Office Hours&lt;/a&gt;, where the team will give a technical presentation and take questions.&lt;/p&gt;

&lt;p&gt;And as always, we encourage you to &lt;a href="https://www.dragonflydb.io/docs/getting-started" rel="noopener noreferrer"&gt;get started&lt;/a&gt;, dive in, experiment, and discover the full potential of Dragonfly Search in your own projects.&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix - Useful Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.dragonflydb.io/docs/command-reference/search/ft.search" rel="noopener noreferrer"&gt;Dragonfly Search Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dragonflydb/dragonfly/releases/tag/v1.13.0" rel="noopener noreferrer"&gt;Dragonfly v1.13 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The OpenAI + vector search example is available in the &lt;a href="https://github.com/dragonflydb/dragonfly-examples" rel="noopener noreferrer"&gt;dragonfly-examples repository&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>redis</category>
      <category>database</category>
      <category>vectordatabase</category>
    </item>
    <item>
      <title>How We Optimized Dragonfly to Get 30x Throughput with BullMQ</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Thu, 23 Nov 2023 17:00:00 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/how-we-optimized-dragonfly-to-get-30x-throughput-with-bullmq-4mjn</link>
      <guid>https://dev.to/dragonflydbio/how-we-optimized-dragonfly-to-get-30x-throughput-with-bullmq-4mjn</guid>
      <description>&lt;p&gt;Howdy! I am Shahar, a software engineer at DragonflyDB.&lt;br&gt;
Today, I'm thrilled to share an exciting journey we embarked on — optimizing &lt;a href="https://www.dragonflydb.io/" rel="noopener noreferrer"&gt;Dragonfly&lt;/a&gt; for &lt;a href="https://bullmq.io/" rel="noopener noreferrer"&gt;BullMQ&lt;/a&gt;, which resulted in a staggering 30x increase in throughput.&lt;br&gt;
While I won't be delving into code snippets here (you can check out all the changes on our &lt;a href="https://github.com/dragonflydb/dragonfly" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;), I'll take you through the high-level optimizations that made this achievement possible.&lt;br&gt;
We believe these enhancements not only mark a significant milestone for Dragonfly but also represent great performance benefits for users of BullMQ.&lt;/p&gt;

&lt;p&gt;So, if you're interested in the behind-the-scenes of database performance tuning and what it takes to achieve such a massive throughput improvement, you're in the right place.&lt;br&gt;
Let's get started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;BullMQ is a popular, robust, and fast Node.js library for creating and processing background jobs that uses Redis as a backend.&lt;br&gt;
Redis backends are a common choice for frameworks, providing low latency, comprehensive data structures, replication, and other useful features.&lt;br&gt;
However, applications using Redis are limited by its throughput. Dragonfly is a drop-in replacement for Redis designed for much higher throughput and easier deployments.&lt;/p&gt;

&lt;p&gt;In a &lt;a href="https://www.dragonflydb.io/blog/running-bullmq-with-dragonfly-part-1-announcement" rel="noopener noreferrer"&gt;previous blog post&lt;/a&gt;,&lt;br&gt;
we announced the full compatibility of Dragonfly with BullMQ and showed how to run BullMQ with Dragonfly efficiently in a few simple steps.&lt;br&gt;
In this post, we'll elaborate the optimizations we made to achieve the stunning 30x performance improvements.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmark Baseline
&lt;/h2&gt;

&lt;p&gt;Before we optimize anything, it's essential to establish a reliable benchmarking baseline.&lt;br&gt;
For BullMQ, which offers its users a robust queue API for adding and processing jobs, our focus was on optimizing the &lt;strong&gt;throughput&lt;/strong&gt; for the &lt;code&gt;add-job&lt;/code&gt; operation.&lt;br&gt;
Collaborating closely with the BullMQ team, we developed a &lt;a href="https://github.com/taskforcesh/bullmq-concurrent-bench" rel="noopener noreferrer"&gt;benchmarking tool&lt;/a&gt;.&lt;br&gt;
This tool is tailored to measure the rate at which messages can be added to queues per second — a crucial metric for understanding the performance of BullMQ while running on different backends.&lt;/p&gt;

&lt;h3&gt;
  
  
  Our Benchmarking Approach
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;We focus on adding messages (i.e., &lt;code&gt;add-job&lt;/code&gt; operations).
The rationale is straightforward: additions are less influenced by the fluctuating state of the queue.
Unlike reading from queues, which can be delayed if queues are temporarily empty, adding messages offers a more stable and measurable performance indicator.&lt;/li&gt;
&lt;li&gt;To push Dragonfly to its limits, we employed Node.js's worker threads, enabling us to leverage real OS threads to run on multiple CPUs in parallel.
This approach simulates a high-load environment more effectively than a single-threaded setup.&lt;/li&gt;
&lt;li&gt;Another key aspect of our testing environment was the hardware configuration.
We intentionally used a more powerful machine for the client code (running BullMQ) compared to the machine running Dragonfly.
This ensures that the client side is never the bottleneck, allowing us to accurately assess Dragonfly's raw performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, if your workload requires higher throughput than shown in this post, you could use stronger machines.&lt;br&gt;
Dragonfly is all about scaling, both vertically and horizontally.&lt;br&gt;
As a baseline performance, let's benchmark how many &lt;code&gt;add-jobs/sec&lt;/code&gt; Redis can achieve:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;&lt;code&gt;add-jobs/sec&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 6.2&lt;/td&gt;
&lt;td&gt;71,351&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7.2&lt;/td&gt;
&lt;td&gt;76,773&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Benchmark with Redis Cluster
&lt;/h3&gt;

&lt;p&gt;Scaling in Redis typically involves setting up a Redis Cluster.&lt;br&gt;
Note that minor changes are needed to get the benchmark to work against the Redis Cluster, as one has to use a cluster-aware Redis client.&lt;br&gt;
We conducted our tests using an 8-node Redis Cluster, all hosted on a single machine equipped with 8 CPUs.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;&lt;code&gt;add-jobs/sec&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 6.2&lt;/td&gt;
&lt;td&gt;71,351&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7.2&lt;/td&gt;
&lt;td&gt;76,773&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis Cluster&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;194,533&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As shown above, the Redis Cluster reaches much higher throughput than a single node.&lt;/p&gt;




&lt;h2&gt;
  
  
  Global Locks
&lt;/h2&gt;

&lt;p&gt;Running BullMQ on Dragonfly doesn't work right away, but with &lt;a href="https://www.dragonflydb.io/docs/integrations/bullmq" rel="noopener noreferrer"&gt;a few steps&lt;/a&gt;, you can get it up and running smoothly.&lt;br&gt;
To grasp this integration challenge, let's look at how BullMQ uses Redis.&lt;br&gt;
BullMQ leverages Lua scripts for executing Redis commands efficiently.&lt;br&gt;
These scripts are advantageous because they reduce network latency and allow multiple commands to execute atomically.&lt;/p&gt;

&lt;p&gt;However, the way BullMQ executes these scripts presents a hurdle.&lt;br&gt;
Despite &lt;a href="https://redis.io/docs/interact/programmability/lua-api/#the-keys-global-variable" rel="noopener noreferrer"&gt;Redis requiring that Lua script invocations specify all the keys used by the script&lt;/a&gt;, it does not enforce this requirement,&lt;br&gt;
and accessing such undeclared keys from Lua scripts "just works" in Redis.&lt;br&gt;
This flexibility in Redis contrasts sharply with Dragonfly's design.&lt;/p&gt;

&lt;p&gt;Dragonfly's architecture is fundamentally different.&lt;br&gt;
It adopts a multi-threaded, shared-nothing approach, meaning &lt;strong&gt;keys are spread and not shared across Dragonfly threads&lt;/strong&gt;.&lt;br&gt;
We have a state of the art transactional framework built on top of this architecture, which allows running operations on keys that belong to different threads.&lt;/p&gt;

&lt;p&gt;The challenge arises with Lua scripts from BullMQ that contain undeclared keys.&lt;br&gt;
Dragonfly's mechanism involves locking all declared keys &lt;strong&gt;prior&lt;/strong&gt; to executing a Lua script.&lt;br&gt;
It then strictly prohibits accessing undeclared keys during script execution, as they could be part of other parallel transactions.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/01-transactions.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/01-transactions.png" alt="transactions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's consider an example to illustrate the challenge with Dragonfly and undeclared keys.&lt;br&gt;
As shown above, imagine a transaction, &lt;code&gt;Tx1&lt;/code&gt;, which is set to access keys &lt;code&gt;key0&lt;/code&gt;, &lt;code&gt;key1&lt;/code&gt;, and &lt;code&gt;key2&lt;/code&gt;.&lt;br&gt;
In Dragonfly's architecture, &lt;code&gt;Tx1&lt;/code&gt; cannot access other keys, such as &lt;code&gt;key3&lt;/code&gt;, because it needs to ensure they are not locked by another transaction like &lt;code&gt;Tx2&lt;/code&gt;.&lt;br&gt;
Attempting to access an undeclared key like &lt;code&gt;key3&lt;/code&gt; could disrupt the integrity of &lt;code&gt;Tx2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Accessing undeclared keys is common, and many Redis-based frameworks rely on this practice.&lt;br&gt;
To accommodate this, Dragonfly can be configured to handle these scenarios.&lt;br&gt;
By using the &lt;code&gt;--default_lua_flags=allow-undeclared-keys&lt;/code&gt; flag, Dragonfly treats Lua scripts as global transactions.&lt;br&gt;
This means the script has access to the entire datastore, locking it completely for its duration.&lt;/p&gt;

&lt;p&gt;However, this solution comes with its own set of challenges.&lt;br&gt;
While it allows scripts to access all keys (including the undeclared ones), it also restricts parallelism.&lt;br&gt;
No other commands or scripts can run alongside a global transaction, even if they involve completely different keys.&lt;br&gt;
Moreover, the situation is further complicated by Dragonfly's multi-threaded nature.&lt;br&gt;
One might think that, using this mode, Dragonfly will have similar performance characteristics to Redis.&lt;br&gt;
However, because keys in Dragonfly are owned by different threads, Dragonfly has to schedule the work between their respective threads (we call these &lt;strong&gt;"hops"&lt;/strong&gt;), which adds significant latency.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/02-global-locks.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/02-global-locks.png" alt="global-locks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here are the results of running Dragonfly with global locks:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;&lt;code&gt;add-jobs/sec&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 6.2&lt;/td&gt;
&lt;td&gt;71,351&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7.2&lt;/td&gt;
&lt;td&gt;76,773&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cluster&lt;/td&gt;
&lt;td&gt;194,533&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly w/ Global Locks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;7,697&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly Baseline&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As demonstrated, using global locks in Dragonfly, while necessary for compatibility with certain Lua scripts, leads to a noticeable drop in throughput.&lt;br&gt;
However, this is just the start of our journey, let's start optimizing!&lt;/p&gt;




&lt;h2&gt;
  
  
  Rollback Mechanism: A Path Not Taken
&lt;/h2&gt;

&lt;p&gt;One of the initial thoughts we considered was a new approach for running Lua scripts, which we called the "try-lock-rollback" mode.&lt;br&gt;
In this mode, Lua scripts would work normally until they attempted to access an undeclared key.&lt;br&gt;
When they do, Dragonfly will try to lock that key.&lt;br&gt;
If the lock is successful, meaning no other command or script is using the key, the script would proceed as intended.&lt;/p&gt;

&lt;p&gt;However, the challenge arises if the lock attempt does &lt;strong&gt;not&lt;/strong&gt; succeed.&lt;br&gt;
If we can't acquire the lock (because another command or script is using the key), Dragonfly would then roll back any changes made by the script so far.&lt;br&gt;
Following the rollback, it would attempt to run the script again from the beginning, this time possibly pre-locking the keys that caused the failure in the previous attempt.&lt;/p&gt;

&lt;p&gt;This is an elegant solution that provides a generic way for running all Lua scripts (BullMQ or otherwise), but it has two major disadvantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Performance Impact on the Happy Path&lt;/strong&gt;:
Implementing this would require tracking every change made by every script, just in case a rollback is needed. 
This tracking would be necessary even for scripts that don't access undeclared keys or where undeclared keys are successfully locked.
Essentially, this means slowing down the usual, rollback-free operations, which is known as the "common path" or "happy path".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk of Snowball Effect &amp;amp; Deadlocks&lt;/strong&gt;:
In scenarios where keys are frequently contended, this approach could lead to a series of rollbacks and retries, creating a bottleneck and significantly impacting performance.
Under extreme conditions, this could even lead to deadlocks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given these considerable disadvantages, we ultimately decided not to pursue this option.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hashtags to the Rescue
&lt;/h2&gt;

&lt;p&gt;While we might not know every specific key a script will use, we do know something about their structure.&lt;br&gt;
In the case of BullMQ, all keys share a common string pattern.&lt;br&gt;
Users can define a prefix, like the default &lt;code&gt;bull:&lt;/code&gt;, followed by a queue name.&lt;br&gt;
This forms a consistent prefix for all keys related to a specific queue (for instance, &lt;code&gt;bull:queue:123&lt;/code&gt;).&lt;br&gt;
Our initial thought was to implement some form of prefix locking based on this pattern.&lt;br&gt;
However, we then considered a more refined solution: &lt;a href="https://redis.io/docs/reference/cluster-spec/#hash-tags" rel="noopener noreferrer"&gt;hashtags&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Hashtags are a Redis Cluster feature.&lt;br&gt;
They involve wrapping a part of a key in curly braces &lt;code&gt;{}&lt;/code&gt;, which ensures that all keys with the same hashtag are located on the same cluster node.&lt;br&gt;
For example, keys &lt;code&gt;{user1}:name&lt;/code&gt; and &lt;code&gt;{user1}:email&lt;/code&gt; are guaranteed to reside on the same node, allowing them to be efficiently used together in commands or scripts.&lt;/p&gt;

&lt;p&gt;Recognizing that BullMQ already utilizes hashtags for Redis Cluster operations, we adopted this concept for Dragonfly as well.&lt;br&gt;
We introduced a new server flag (&lt;code&gt;--lock_on_hashtags&lt;/code&gt;) where Dragonfly locks based on the hashtag rather than the entire key.&lt;br&gt;
This approach allows us to maintain the atomicity and isolation of script executions while avoiding the performance penalties associated with global locks or the complexities of the rollback mechanism.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/03-hashtags.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/03-hashtags.png" alt="hashtags"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Implementing the hashtag-locking method in Dragonfly has several key advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ease of Integration for BullMQ&lt;/strong&gt;:
It allows BullMQ to work with Dragonfly by not specifying the exact keys which will be used, but only the queue name itself, which is always known.
This simplification greatly streamlines the integration process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced Cross-Thread Coordination&lt;/strong&gt;:
By ensuring that all keys associated with a particular queue are handled by the same thread in Dragonfly, the need for cross-thread coordination is significantly diminished.
We will cover more on this in the following sections.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, there is a trade-off to consider, which is the &lt;strong&gt;limitation on parallelization within a single queue&lt;/strong&gt;.&lt;br&gt;
While individual Lua scripts run serially, two different scripts can usually run in parallel if they involve keys managed by different threads.&lt;br&gt;
Under the hashtag-locking system, all keys of a specific queue are allocated to the same thread in Dragonfly.&lt;br&gt;
This means that parallel execution of operations within the same queue is not possible.&lt;br&gt;
However, we saw that all common BullMQ operations use a specific subset of keys, so they couldn't be parallelized anyway.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;&lt;code&gt;add-jobs/sec&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 6.2&lt;/td&gt;
&lt;td&gt;71,351&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7.2&lt;/td&gt;
&lt;td&gt;76,773&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cluster&lt;/td&gt;
&lt;td&gt;194,533&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Global Locks&lt;/td&gt;
&lt;td&gt;7,697&lt;/td&gt;
&lt;td&gt;Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly w/ Hashtag Locks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;17,403&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~2.26x Dragonfly Baseline&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Our first optimization gave us around 126% increase, which is a nice start.&lt;br&gt;
Also, it's notable that we decided to disable hashtag-locking by default, but Dragonfly users can turn it on via the &lt;code&gt;lock_on_hashtags&lt;/code&gt; flag.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reducing Number of Hops
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reducing Number of Hops for Commands
&lt;/h3&gt;

&lt;p&gt;As mentioned a couple of times, Dragonfly is multi-threaded.&lt;br&gt;
We also handle incoming connections using multiple threads, where each connection is assigned a single thread randomly.&lt;br&gt;
This means that when BullMQ connects to a Dragonfly instance with 8 threads, it has a 1/8 chance of "landing" on the thread that owns its queue.&lt;br&gt;
In 7/8 of cases, the connection thread (internally called the coordinator thread) will attempt to run each command on the target thread separately.&lt;br&gt;
A script that tries to run 100 commands will require a "hop" to the target thread to lock the key, another 100 hops to run each of the commands, and another final hop to unlock the key.&lt;br&gt;
Each hop has a latency cost (as well as some minimal coordination overhead), which adds up.&lt;br&gt;
To mitigate that, we added a check to see if all the operations of a script are being done on a single (remote) thread.&lt;br&gt;
If they are, we perform a single hop to the target thread and run the script there.&lt;br&gt;
This turns those 100 hops, 1 per command, to 1 hop for all commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/04-reduce-hops-1.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/04-reduce-hops-1.png" alt="reduce-hops-1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;&lt;code&gt;add-jobs/sec&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 6.2&lt;/td&gt;
&lt;td&gt;71,351&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7.2&lt;/td&gt;
&lt;td&gt;76,773&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cluster&lt;/td&gt;
&lt;td&gt;194,533&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Global Locks&lt;/td&gt;
&lt;td&gt;7,697&lt;/td&gt;
&lt;td&gt;Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Hashtag Locks&lt;/td&gt;
&lt;td&gt;17,403&lt;/td&gt;
&lt;td&gt;~2.26x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly w/ Reduced Hops (Commands)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;53,011&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~6.98x Dragonfly Baseline&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;With this optimization, Dragonfly achieved another 200% increase on top of hashtag-locking and reached 6.98x the baseline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reducing Hops Further for Scripts
&lt;/h3&gt;

&lt;p&gt;After reducing hops for each command, we looked at the rest of the hops.&lt;br&gt;
We had 3 hops for each &lt;strong&gt;script invocation&lt;/strong&gt;, no matter how many commands it issued:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Lock keys.&lt;/li&gt;
&lt;li&gt;Run the Lua script to read/modify keys.&lt;/li&gt;
&lt;li&gt;Unlock keys.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We then modified our Lua invocation flow to run all steps under a single hop (lock, run, unlock).&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/05-reduce-hops-2.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/05-reduce-hops-2.png" alt="reduce-hops-2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;&lt;code&gt;add-jobs/sec&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 6.2&lt;/td&gt;
&lt;td&gt;71,351&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7.2&lt;/td&gt;
&lt;td&gt;76,773&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cluster&lt;/td&gt;
&lt;td&gt;194,533&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Global Locks&lt;/td&gt;
&lt;td&gt;7,697&lt;/td&gt;
&lt;td&gt;Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Hashtag Locks&lt;/td&gt;
&lt;td&gt;17,403&lt;/td&gt;
&lt;td&gt;~2.26x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Reduced Hops (Commands)&lt;/td&gt;
&lt;td&gt;53,011&lt;/td&gt;
&lt;td&gt;~6.98x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly w/ Reduced Hops  (Scripts)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;122,890&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~15.97x Dragonfly Baseline&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Again, by reducing the number of hops on the script level, we achieved another 132% increase, reaching 15.97x the baseline.&lt;/p&gt;




&lt;h2&gt;
  
  
  Connection Migration
&lt;/h2&gt;

&lt;p&gt;Previously, I highlighted that, for an 8-thread Dragonfly server, the probability of a connection hitting the target thread is 1/8, and this likelihood decreases as the number of threads increases.&lt;br&gt;
When a connection tries to execute a script (or even a simple command) on a remote thread, it has to request that thread to run some code.&lt;br&gt;
Then it waits for that thread to become free, which may take some time.&lt;/p&gt;

&lt;p&gt;To improve this situation, we've developed a connection migration mechanism.&lt;br&gt;
Currently, this feature is specifically tailored to BullMQ, where each queue typically doesn't share its connection with others.&lt;br&gt;
However, it holds potential benefits for other frameworks as well.&lt;/p&gt;

&lt;p&gt;Migrating connections to other threads is a subtle process, as Dragonfly uses thread-local variables quite intensively,&lt;br&gt;
but this saves the last hop, getting us to a place where connections &lt;strong&gt;seamlessly&lt;/strong&gt; use their target threads.&lt;br&gt;
We like this feature so much that we even enabled it by default.&lt;br&gt;
It could, however, be disabled by running Dragonfly with &lt;code&gt;--migrate_connections=false&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;&lt;code&gt;add-jobs/sec&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 6.2&lt;/td&gt;
&lt;td&gt;71,351&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7.2&lt;/td&gt;
&lt;td&gt;76,773&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cluster&lt;/td&gt;
&lt;td&gt;194,533&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Global Locks&lt;/td&gt;
&lt;td&gt;7,697&lt;/td&gt;
&lt;td&gt;Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Hashtag Locks&lt;/td&gt;
&lt;td&gt;17,403&lt;/td&gt;
&lt;td&gt;~2.26x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Reduced Hops (Commands)&lt;/td&gt;
&lt;td&gt;53,011&lt;/td&gt;
&lt;td&gt;~6.98x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Reduced Hops  (Scripts)&lt;/td&gt;
&lt;td&gt;122,890&lt;/td&gt;
&lt;td&gt;~15.97x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly w/ Connection Migration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;189,756&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~24.65x Dragonfly Baseline&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Round Robin Key Placement
&lt;/h2&gt;

&lt;p&gt;Here comes the final optimization we have made so far in this journey.&lt;br&gt;
In Dragonfly, the distribution of keys (or queues) across threads is determined by their hash values, akin to a random distribution.&lt;br&gt;
This approach typically ensures an even load distribution when there are many keys, as it balances the computational load across all threads.&lt;/p&gt;

&lt;p&gt;However, consider a situation where an 8-thread Dragonfly server is managing just 8 queues.&lt;br&gt;
In an ideal scenario, each thread would handle one queue, leading to a perfectly balanced load and optimal performance.&lt;br&gt;
But due to the random nature of key distribution based on hashing, achieving such an even distribution is &lt;a href="https://en.wikipedia.org/wiki/Balls_into_bins_problem" rel="noopener noreferrer"&gt;very unlikely&lt;/a&gt;.&lt;br&gt;
When the distribution of queues across threads is uneven, it results in inefficient use of resources: some threads may be idle while others become bottlenecks, leading to suboptimal performance.&lt;/p&gt;

&lt;p&gt;That is exactly why we implemented a very cool feature we call "shard round-robin".&lt;br&gt;
By using &lt;code&gt;--shard_round_robin_prefix=queue&lt;/code&gt;, keys that start with &lt;code&gt;queue&lt;/code&gt; will be distributed between the threads one by one, guaranteeing a near-even distribution of workloads.&lt;br&gt;
This feature is relatively new, and you should note that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Currently, this feature is only available for keys using hashtags, so in the example above, the key &lt;code&gt;bull:{queue1}&lt;/code&gt; will use round-robin, while &lt;code&gt;queue1&lt;/code&gt; will &lt;strong&gt;not&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;This feature should &lt;strong&gt;usually&lt;/strong&gt; be disabled. It is useful only in cases of a small number of hashtags (like BullMQ queues) which are highly contended.
If you use many keys (like in most Dragonfly use cases), do not use the feature, as it will in fact hurt performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/06-round-robin.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/06-round-robin.png" alt="round-robin"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By this point, we've achieved a 30x increase in throughput from the baseline, which is a huge improvement!&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Backend&lt;/th&gt;
&lt;th&gt;&lt;code&gt;add-jobs/sec&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Redis 6.2&lt;/td&gt;
&lt;td&gt;71,351&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis 7.2&lt;/td&gt;
&lt;td&gt;76,773&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis Cluster&lt;/td&gt;
&lt;td&gt;194,533&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Global Locks&lt;/td&gt;
&lt;td&gt;7,697&lt;/td&gt;
&lt;td&gt;Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Hashtag Locks&lt;/td&gt;
&lt;td&gt;17,403&lt;/td&gt;
&lt;td&gt;~2.26x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Reduced Hops (Commands)&lt;/td&gt;
&lt;td&gt;53,011&lt;/td&gt;
&lt;td&gt;~6.98x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Reduced Hops  (Scripts)&lt;/td&gt;
&lt;td&gt;122,890&lt;/td&gt;
&lt;td&gt;~15.97x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dragonfly w/ Connection Migration&lt;/td&gt;
&lt;td&gt;189,756&lt;/td&gt;
&lt;td&gt;~24.65x Dragonfly Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dragonfly w/ Shard Round Robin&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;253,075&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~32.87x Dragonfly Baseline&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To put this chart into visualizations, it is notable that Dragonfly outperforms an 8-instance Redis Cluster on the same hardware.&lt;br&gt;
In the meantime, this optimization is much harder to implement using a Redis Cluster, as the key distribution is built into both Redis and Redis Cluster client libraries.&lt;br&gt;
Instead, users of Redis may have to move cluster slots between the nodes to enforce an even distribution.&lt;/p&gt;

&lt;p&gt;&lt;a href="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/07-benchmarks.png" class="article-body-image-wrapper"&gt;&lt;img src="/assets/blog/running-bullmq-with-dragonfly-part-2-optimization/07-benchmarks.png" alt="benchmarks"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this post, we've taken a glimpse into our journey of integrating BullMQ with Dragonfly with a series of optimizations.&lt;br&gt;
Each step on this path was guided by our commitment to achieving exceptional performance, ensuring that Dragonfly stands ready to handle the most demanding loads from BullMQ users.&lt;/p&gt;

&lt;p&gt;The journey has been both challenging and rewarding, leading to developments that not only benefit BullMQ but also have the potential to enhance the performance of other Redis-based frameworks.&lt;br&gt;
Dragonfly is committed to embracing the open-source community and broadening the ecosystem.&lt;br&gt;
More integrations and frameworks will be tested with Dragonfly and released in the future.&lt;br&gt;
As always, &lt;a href="https://www.dragonflydb.io/docs/getting-started" rel="noopener noreferrer"&gt;start trying Dragonfly in just a few steps&lt;/a&gt; and build amazing applications!&lt;/p&gt;




&lt;h2&gt;
  
  
  Appendix - Benchmark Setup &amp;amp; Details
&lt;/h2&gt;

&lt;p&gt;Here are some technical details for those who wish to reproduce the benchmarks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All benchmarks in this post are done on the same machine, one after the other.
We chose an AWS EC2 &lt;code&gt;c7i.2xlarge&lt;/code&gt; 8-CPU instance for running Dragonfly or Redis, and a monstrous &lt;code&gt;c7i.16xlarge&lt;/code&gt; 64-CPU instance for running BullMQ.&lt;/li&gt;
&lt;li&gt;Operating system: Ubuntu 23.04, Linux kernel 6.2.0&lt;/li&gt;
&lt;li&gt;All client-side invocations use &lt;a href="https://github.com/taskforcesh/bullmq-concurrent-bench" rel="noopener noreferrer"&gt;this tool&lt;/a&gt; with the following command,
which uses 8 queues and 16 threads on the BullMQ side:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  tsc index.ts &amp;amp;&amp;amp; node index.js -h &amp;lt;server_ip&amp;gt; -p 7000 -d 30 -r 0 -w 16 -q 8
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>redis</category>
      <category>database</category>
    </item>
    <item>
      <title>Building E-Commerce Applications with Dragonfly</title>
      <dc:creator>Dragonfly</dc:creator>
      <pubDate>Tue, 21 Nov 2023 17:00:00 +0000</pubDate>
      <link>https://dev.to/dragonflydbio/building-e-commerce-applications-with-dragonfly-27l9</link>
      <guid>https://dev.to/dragonflydbio/building-e-commerce-applications-with-dragonfly-27l9</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the high-octane world of e-commerce applications, both response speed and data accuracy are crucial.&lt;br&gt;
Customers expect seamless access to searched items, past orders, recently viewed products, and personalized recommendations.&lt;br&gt;
In the meantime, these applications often experience fluctuating traffic, especially during peak periods like the Christmas season or Black Friday.&lt;br&gt;
High-traffic events furthermore introduce significant challenges, requiring rapid response and precise data handling.&lt;br&gt;
Addressing these variations often demands a scalable and robust in-memory data storage solution.&lt;/p&gt;

&lt;p&gt;Dragonfly, an ultra-performant in-memory data store, utilizes a multi-threaded, shared-nothing architecture that pushes hardware to its limits,&lt;br&gt;
supporting up to 4 million ops/sec and 1 TB of memory on a single instance.&lt;br&gt;
This can drastically reduce operational complexities while providing a high-performance solution for e-commerce applications.&lt;br&gt;
For even more demanding scenarios, Dragonfly also offers cluster mode on top of the stunning single-node performance.&lt;br&gt;
This adaptability makes Dragonfly an ideal choice for e-commerce platforms that contend with unpredictable and varied traffic patterns.&lt;/p&gt;

&lt;p&gt;In this blog, we will explore how Dragonfly can be used in various ways to elevate your e-commerce platform's performance and user experience, particularly in the following areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Caching&lt;/strong&gt; - &lt;strong&gt;String&lt;/strong&gt;, &lt;strong&gt;Hash&lt;/strong&gt;, and &lt;strong&gt;JSON&lt;/strong&gt; data types are ideal for caching.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Personalization&lt;/strong&gt; - &lt;strong&gt;Sorted-Set&lt;/strong&gt; is perfect for tracking user preferences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-Traffic Flash Sales&lt;/strong&gt; - &lt;strong&gt;Atomic Operations&lt;/strong&gt; and &lt;strong&gt;Distributed Locks&lt;/strong&gt; can be used to manage inventory verification and deduction under extremely demanding situations.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Caching
&lt;/h2&gt;

&lt;p&gt;Caching is a powerful strategy in web technology, particularly important for e-commerce applications.&lt;br&gt;
It enables quicker data retrieval by storing the results of a database query or an API request in a cache.&lt;br&gt;
When an identical request is made, the data can be swiftly served from the cache (in this case, Dragonfly) avoiding the need for time-consuming interactions with the primary database.&lt;/p&gt;

&lt;p&gt;Selecting the appropriate data type for caching is crucial to easing the implementation and optimizing the performance of your e-commerce platform.&lt;br&gt;
The most accessible data type is a &lt;code&gt;String&lt;/code&gt;, ideal for caching a blob of data.&lt;br&gt;
It's versatile and safe to store various formats, whether text or binary, like JSON strings, MessagePacks, or Protocol Buffers.&lt;br&gt;
For instance, a user's recent order summary could be cached as a JSON string.&lt;br&gt;
However, the downside of using the &lt;code&gt;String&lt;/code&gt; data type is the difficulty in manipulating individual fields within cached data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using the 'String' data type for caching.&lt;/span&gt;

&lt;span class="c"&gt;# To cache an order:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; SET order_string:&amp;lt;order_id&amp;gt; &lt;span class="s1"&gt;'{"id": "&amp;lt;order_id&amp;gt;", "items": [{"id": "001", "name": "Laptop", "quantity": 1}], "total": 1799.99}'&lt;/span&gt;

&lt;span class="c"&gt;# To retrieve the entire cached order:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; GET order_string:&amp;lt;order_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, a &lt;code&gt;Hash&lt;/code&gt; data type, which is a single-level string-to-string flat hashmap, is suitable for storing field/value pairs.&lt;br&gt;
This can be used to cache specific attributes of a user's order, like item IDs and quantities, allowing for quicker access and updates.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using the 'Hash' data type for caching.&lt;/span&gt;

&lt;span class="c"&gt;# To cache quantities for different items and the total price in the order:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HSET order_hash:&amp;lt;order_id&amp;gt; item_001 5 item_002 6 item_003 7 total 2799.99

&lt;span class="c"&gt;# To retrieve the quantity of a specific item:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HGET order_hash:&amp;lt;order_id&amp;gt; item_003

&lt;span class="c"&gt;# To retrieve the entire cached order:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HGETALL order_hash:&amp;lt;order_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lastly, for more complex data structures, the &lt;code&gt;JSON&lt;/code&gt; data type in Dragonfly natively and fully supports the &lt;a href="https://www.json.org/json-en.html" rel="noopener noreferrer"&gt;JSON&lt;/a&gt; specification&lt;br&gt;
and the &lt;a href="https://github.com/json-path/JsonPath" rel="noopener noreferrer"&gt;JSONPath&lt;/a&gt; syntax, enabling easy manipulation of individual fields.&lt;br&gt;
This is particularly useful for detailed order information, where each aspect of an order (such as product details, pricing, and shipping info)&lt;br&gt;
can be individually accessed and modified, providing both flexibility and efficiency in data handling.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using the 'JSON' data type for caching.&lt;/span&gt;

&lt;span class="c"&gt;# To cache an order as native JSON:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; JSON.SET order_json:&amp;lt;order_id&amp;gt; &lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="s1"&gt;'{"id": "&amp;lt;order_id&amp;gt;", "items": [{"id": "001", "name": "Laptop", "quantity": 1}], "total": 1799.99}'&lt;/span&gt;

&lt;span class="c"&gt;# To update the quantity of a specific item:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; JSON.SET order_json:&amp;lt;order_id&amp;gt; &lt;span class="nv"&gt;$.&lt;/span&gt;items[0].quantity 2

&lt;span class="c"&gt;# To retrieve the total price of the order:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; JSON.GET order_json:&amp;lt;order_id&amp;gt; &lt;span class="nv"&gt;$.&lt;/span&gt;total

&lt;span class="c"&gt;# To retrieve the entire cached order:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; JSON.GET order_json:&amp;lt;order_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more caching-related techniques, check out our previous blog posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.dragonflydb.io/blog/developing-with-dragonfly-part-01-cache-aside" rel="noopener noreferrer"&gt;Developing with Dragonfly: Cache-Aside&lt;/a&gt; to follow along with a step-by-step tutorial on how to implement a cache-aside pattern using Dragonfly.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.dragonflydb.io/blog/developing-with-dragonfly-part-02-solve-caching-problems" rel="noopener noreferrer"&gt;Developing with Dragonfly: Solve Caching Problems&lt;/a&gt; to learn how to solve the 3 common caching problems (Penetration, Breakdown, and Avalanche) with Dragonfly.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.dragonflydb.io/blog/dragonfly-cache-design" rel="noopener noreferrer"&gt;Dragonfly Cache Design&lt;/a&gt; to learn more about the internal eviction algorithm of Dragonfly.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Personalization
&lt;/h2&gt;

&lt;p&gt;In e-commerce applications, personalizing the user experience is key.&lt;br&gt;
One effective way to achieve this is by showcasing prioritized items, such as the most-viewed product categories for a particular user or the top items viewed globally on the application for the day.&lt;br&gt;
&lt;code&gt;Sorted-Set&lt;/code&gt;, a data structure available in Dragonfly, is perfectly suited for this task.&lt;br&gt;
It's a collection of unique elements, each associated with a score, which determines the order of the elements.&lt;/p&gt;

&lt;p&gt;For instance, to track a user's most-viewed product categories, we can use a &lt;code&gt;Sorted-Set&lt;/code&gt; where each category is a member and the number of times the user views that category is the score.&lt;br&gt;
Every time a user views a category, the score is incremented, ensuring the set always reflects the user's current preferences.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using the 'Sorted-Set' data type to track user preferences.&lt;/span&gt;

&lt;span class="c"&gt;# To increment the view count for a category for a user:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ZINCRBY viewed_product_categories_by_user_id:&amp;lt;user_id&amp;gt; 1 &lt;span class="s2"&gt;"electronics"&lt;/span&gt;

&lt;span class="c"&gt;# To retrieve the top 5 viewed categories for a user:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; ZREVRANGE viewed_product_categories_by_user_id:&amp;lt;user_id&amp;gt; 0 4 WITHSCORES
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similarly, for global views, we can maintain a &lt;code&gt;Sorted-Set&lt;/code&gt; for the entire application, where each view of a product category by any user increments the category's score.&lt;br&gt;
This approach allows for dynamic, real-time ranking of categories or items based on popularity, providing valuable insights for both users and the platform.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Sorted-Set&lt;/code&gt; an important data structure in many applications, particularly for scenarios like those mentioned above.&lt;br&gt;
Redis has long been celebrated for its robust implementation of this data structure, facilitating efficient data sorting and retrieval.&lt;br&gt;
However, starting from v1.11, Dragonfly introduces a &lt;a href="https://en.wikipedia.org/wiki/B%2B_tree" rel="noopener noreferrer"&gt;B+ Tree&lt;/a&gt;-based implementation.&lt;br&gt;
This new implementation not only enhances performance but also improves memory efficiency in terms of size, making it an excellent choice for handling large-scale data sorting and ranking tasks.&lt;br&gt;
We plan to explore this topic in greater depth in a future blog post.&lt;/p&gt;


&lt;h2&gt;
  
  
  High-Traffic Flash Sales
&lt;/h2&gt;

&lt;p&gt;In our earlier discussion, we highlighted the challenges e-commerce platforms face with fluctuating traffic, particularly during high-profile events like Black Friday flash sales.&lt;br&gt;
During these peak periods, Dragonfly can play a pivotal role, especially in managing inventory verification and deduction.&lt;br&gt;
While these tasks can be performed using a traditional SQL database, the simultaneous attempts by numerous users to purchase limited-stock items can quickly overwhelm the primary database.&lt;br&gt;
This is where the capabilities of Dragonfly, as an ultra-performant in-memory data store become invaluable.&lt;/p&gt;

&lt;p&gt;Dragonfly can address this challenge with two mechanisms: &lt;strong&gt;Atomic Operations&lt;/strong&gt; and &lt;strong&gt;Distributed Locks&lt;/strong&gt;.&lt;br&gt;
Each mechanism can in turn be implemented in different ways, which we will explore in detail below.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Atomic Operations with &lt;code&gt;INCR&lt;/code&gt; or &lt;code&gt;DECR&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Dragonfly's atomicity ensures that each command, such as incrementing or decrementing a value, is executed entirely and independently, without interference from other operations.&lt;br&gt;
Consider a scenario where we have a limited stock of a product for a flash sale, we can initialize the inventory count in Dragonfly by setting the quantity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Assuming the flash sale has 100 units for a particular item.&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; SET item_on_sale:&amp;lt;item_id&amp;gt; 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For simplicity, we assume that each request is for a single unit of the item.&lt;br&gt;
When a purchase request is made, we use the &lt;code&gt;DECR&lt;/code&gt; command to deduct inventory atomically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; DECR item_on_sale:&amp;lt;item_id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The return value of the &lt;code&gt;DECR&lt;/code&gt; command is crucial.&lt;br&gt;
If it is greater than zero, it indicates that the product is still available, and the purchase can proceed.&lt;br&gt;
Conversely, if the return value is zero or less, it signifies that the product is sold out, and further purchases should be denied.&lt;br&gt;
This method is easy to implement and particularly effective for straightforward scenarios where immediate inventory updates are sufficient and more complex processes like order cancellations can be managed later.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Atomic Operations with Lua Scripts
&lt;/h3&gt;

&lt;p&gt;For more complex scenarios, Lua scripts can be used to implement atomic inventory verification and deduction.&lt;br&gt;
It is notable that Dragonfly allows non-atomic operations in Lua scripts with the &lt;code&gt;disable-atomicity&lt;/code&gt; script flag, as explained in this &lt;a href="https://www.dragonflydb.io/blog/leveraging-power-of-lua-scripting" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;.&lt;br&gt;
Thus, we need to make sure that the script flag is not used for our e-commerce inventory deduction scenario.&lt;br&gt;
Let's assume that we store the inventory of an item using the &lt;code&gt;Hash&lt;/code&gt; data type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; HSET item_on_sale:&amp;lt;item_id&amp;gt; inventory 100 purchased 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To verify and deduct inventory, we can use the following Lua script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- atomic_inventory_deduction.lua&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;num_to_purchase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;num_to_purchase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"HMGET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"inventory"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"purchased"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;purchased&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;purchased&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;num_to_purchase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;inventory&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
   &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"HINCRBY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"purchased"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num_to_purchase&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num_to_purchase&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the script above, we first parse the keys and arguments passed to the script.&lt;br&gt;
The script operates on a single key, which is the key of the item on sale.&lt;br&gt;
Similarly, the script expects a single argument, which is the number of units to purchase.&lt;br&gt;
Then, we retrieve the current inventory and the number of units purchased for the item using the &lt;code&gt;HMGET&lt;/code&gt; command.&lt;br&gt;
If the total number of units purchased plus the number of units to purchase is less than or equal to the inventory, we increment the number of units purchased and return the number of units purchased.&lt;br&gt;
Otherwise, we return &lt;code&gt;nil&lt;/code&gt; to indicate that the purchase cannot proceed.&lt;br&gt;
The script above can be executed using the &lt;code&gt;EVAL&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# General syntax of the 'EVAL' command:&lt;/span&gt;
&lt;span class="c"&gt;#   EVAL script num_of_keys [key [key ...]] [arg [arg ...]]&lt;/span&gt;

&lt;span class="c"&gt;# Try to purchase 5 units of the item:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; EVAL &lt;span class="s2"&gt;"&amp;lt;script&amp;gt;"&lt;/span&gt; 1 item_on_sale:&amp;lt;item_id&amp;gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, we can load the script and use the &lt;code&gt;EVALSHA&lt;/code&gt; command, which is more efficient as it stores the script in Dragonfly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Load the script into Dragonfly and get the SHA1 digest of the script.&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; SCRIPT LOAD &lt;span class="s2"&gt;"&amp;lt;script&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Try to purchase 5 units of the item using the SHA1 digest of the script:&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; EVALSHA &lt;span class="s2"&gt;"&amp;lt;script_sha&amp;gt;"&lt;/span&gt; 1 item_on_sale:&amp;lt;item_id&amp;gt; 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In comparison to the &lt;code&gt;DECR&lt;/code&gt; command, the Lua script option allows for more complex inventory verification and deduction logic and covers more edge cases.&lt;br&gt;
For instance, in the script above, we have a sanity check to ensure that the number of units to purchase is greater than zero.&lt;br&gt;
In the meantime, we allow purchases of more than one unit of the item, and it handles the edge case where the inventory is not sufficient to fulfill the requested quantity nicely.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Distributed Locks with Conditional &lt;code&gt;SET&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Another powerful feature of Dragonfly is its ability to act as distributed locks, playing a critical role in handling surges of traffic.&lt;br&gt;
During high traffic periods, such as flash sales, each incoming request attempts to acquire a lock from Dragonfly.&lt;br&gt;
Only the request that successfully secures a lock gains the exclusive right to proceed with further operations for that particular on-sale item.&lt;br&gt;
These operations might include database operations, payment processes, or any other actions that require direct interaction with the primary database or third-party services within the e-commerce platform.&lt;/p&gt;

&lt;p&gt;Requests that fail to acquire a lock are denied further processing and can be redirected to a waiting page or a retry page with a countdown timer, depending on the implementation.&lt;br&gt;
This ensures that &lt;strong&gt;only one request is allowed to proceed at a time per on-sale item&lt;/strong&gt;, preventing the primary database from being overwhelmed by excessive simultaneous requests.&lt;/p&gt;

&lt;p&gt;A common logic for using distributed locks could be something similar to the following pseudocode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// purchase_item_pseudocode.js&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;itemId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getItemId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getUserId&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expiration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getLockExpirationTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`item_lock:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockVal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lockAcquired&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;acquireLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lockVal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;expiration&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;lockAcquired&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Another user got this item, please try again later.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;purchaseItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;purchaseError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;purchaseError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;releaseLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lockKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lockVal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;sendResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You have successfully purchased the item!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is notable that both &lt;code&gt;acquireLock&lt;/code&gt; and &lt;code&gt;releaseLock&lt;/code&gt; take the item ID and user ID into account.&lt;br&gt;
We want to ensure that an acquired lock cannot be accidentally released by another user under high concurrency situations, the &lt;code&gt;releaseLock&lt;/code&gt; implementation should conform to this requirement.&lt;br&gt;
Also, the choice of the lock expiration time is important.&lt;br&gt;
It should be long enough to allow the user to complete the purchase process, but not too long to prevent other users from acquiring the lock if the service process dies unexpectedly without releasing the lock.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;acquireLock&lt;/code&gt; function can be implemented in Dragonfly using the &lt;code&gt;SET&lt;/code&gt; command with the &lt;code&gt;NX&lt;/code&gt; and &lt;code&gt;EX&lt;/code&gt; options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using the 'SET' command with the 'NX' option to acquire a lock.&lt;/span&gt;
&lt;span class="c"&gt;# The 'NX' option ensures that the lock is only acquired if the key does not exist.&lt;/span&gt;
&lt;span class="c"&gt;# Also, we set the expiration time for the lock to prevent the lock from being held indefinitely.&lt;/span&gt;
dragonfly&lt;span class="nv"&gt;$&amp;gt;&lt;/span&gt; SET item_lock:&amp;lt;item_id&amp;gt; &amp;lt;user_id&amp;gt; NX EX &amp;lt;expiration&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the other hand, the &lt;code&gt;releaseLock&lt;/code&gt; function needs to be implemented in a Lua script to ensure that the lock is only released if the user ID matches the one that acquired the lock.&lt;br&gt;
This can be achieved in Dragonfly using the &lt;code&gt;EVAL&lt;/code&gt; command with the following Lua script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- release_lock.lua&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;lock_val&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;lock_val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
   &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"DEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Distributed Locks with RedLock
&lt;/h3&gt;

&lt;p&gt;Using conditional &lt;code&gt;SET&lt;/code&gt; commands and Lua scripts for distributed locking in Dragonfly is a straightforward yet effective way to manage highly concurrent operations.&lt;br&gt;
However, in environments where an even higher level of reliability and fault tolerance is required, particularly across distributed systems, the &lt;strong&gt;RedLock&lt;/strong&gt; distributed lock algorithm adds additional safety.&lt;/p&gt;

&lt;p&gt;RedLock is designed to extend the locking mechanism across multiple primary instances of Redis.&lt;br&gt;
Since Dragonfly is highly compatible with Redis, RedLock can be used with Dragonfly instances as well.&lt;br&gt;
RedLock ensures that a lock is acquired and released correctly and consistently across all these instances, enhancing the reliability and integrity of the distributed locking process.&lt;/p&gt;

&lt;p&gt;When using RedLock, the &lt;code&gt;acquireLock&lt;/code&gt; and &lt;code&gt;releaseLock&lt;/code&gt; functions are normally provided by the client library already.&lt;br&gt;
This typically involves attempting to acquire the lock on multiple instances simultaneously and ensuring that a majority of the instances grant the lock before proceeding.&lt;br&gt;
Similarly, the release process involves trying to release the lock across all instances to maintain consistency.&lt;/p&gt;

&lt;p&gt;For more information on RedLock, read the documentation &lt;a href="https://redis.io/docs/manual/patterns/distributed-locks/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this blog, we explored how Dragonfly can be used in various ways to elevate your e-commerce platform's performance and user experience.&lt;br&gt;
We discussed how Dragonfly can be used for caching, personalization, and high-traffic flash sales.&lt;/p&gt;

&lt;p&gt;Overall, Dragonfly is a versatile and powerful tool for building and maintaining an e-commerce platform, proficient in handling various aspects from everyday user interactions to the most demanding sales events.&lt;br&gt;
Although not directly shown in this blog, Dragonfly's performance is phenomenal, as discussed in detail in our previous &lt;a href="https://www.dragonflydb.io/blog" rel="noopener noreferrer"&gt;blog posts&lt;/a&gt;.&lt;br&gt;
We encourage you to &lt;a href="https://www.dragonflydb.io/docs/getting-started" rel="noopener noreferrer"&gt;try Dragonfly out for yourself&lt;/a&gt; and experience its capabilities firsthand.&lt;br&gt;
Also, consider subscribing to our newsletter below to stay in the loop with the latest Dragonfly news and updates!&lt;/p&gt;

</description>
      <category>redis</category>
      <category>database</category>
    </item>
  </channel>
</rss>
