Jump to content
  • Advertisement

Project: Unsettled World

In-Memory Key-Value Databases: Redis (Initial Evaluation)

Septopus

1323 views

This is the first of at least two posts regarding my evaluation of the addition of an In-Memory(RAM) Key-Value Database system to my server architecture.  If you're unfamiliar, check out https://en.wikipedia.org/wiki/Key-value_database, for some broad spectrum info. 

I'm beginning with Redis, it runs on Linux which is my scale-up server platform of choice, and this database will be the key to the scalability of my server architecture.  It's also open-source and has been around for a while now.

Download: https://redis.io/download

Documentation: https://redis.io/documentation

Installation was pretty straight forward, I created a Centos7 VM using Oracle's VirtualBox software (https://www.virtualbox.org/ one of the easiest ways to use vms locally I've used on Windows),

It has been a couple years since I worked on a  Linux machine, but I still managed to figure it out:

redis_getmakeinstall.png.ca0a106d9d57b35e821e5961e4bb7fd0.png

redis_working_install.png.d082d4a447c013bf003a49bdf33ec0f3.png

So, that's a working installation.  Easy as 1,2,3..

Okay, a simple benchmark,VM is using a single core and 4G of ram, and I'm using the StackExchange.Redis client from NuGet:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;

namespace TestRedis
{
    class Program
    {
        static Dictionary<string, string> TestDataSet = new Dictionary<string, string>();
        static ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.56.101");

        static void Main(string[] args)
        {
            //Build Test Dataset
            Console.Write("Building Test Data Set.");
            int cnt = 0;
            for (int i = 0; i < 1000000; i++)
            {
                cnt++;
                TestDataSet.Add(Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
                if (cnt > 999)
                {
                    cnt = 0;
                    Console.Write(i.ToString() + "_");
                }
            }
            Console.WriteLine("Done");

            IDatabase db = redis.GetDatabase();

            Stopwatch sw = new Stopwatch();
            sw.Start();
            //Console.WriteLine("Starting 'write' Benchmark");
            //foreach (KeyValuePair<string, string> kv in TestDataSet)
            //{
            //    db.StringSet(kv.Key, kv.Value);
            //}
            Console.WriteLine("Starting Parallel 'write' Benchmark.");
            Parallel.ForEach(TestDataSet, td =>
            {
                db.StringSet(td.Key, td.Value);
            });
            Console.WriteLine("TIME: " + (sw.ElapsedMilliseconds / 1000).ToString());
            sw.Restart();
            Console.WriteLine("Testing Read Verify.");
            //foreach (KeyValuePair<string, string> kv in TestDataSet)
            //{
            //    if (db.StringGet(kv.Key) != kv.Value)
            //    {
            //        Console.WriteLine("Error Getting Value for Key: " + kv.Key);
            //    }
            //}
            Console.WriteLine("Testing Parallel Read Verify");
            Parallel.ForEach(TestDataSet, td =>
            {
                if (db.StringGet(td.Key) != td.Value)
                {
                    Console.WriteLine("Error Getting Value for Key: " + td.Key);
                }
            });
            Console.WriteLine("TIME: " + (sw.ElapsedMilliseconds / 1000).ToString());
            sw.Stop();
            Console.WriteLine("Press any key..");
            Console.ReadKey();
        }
    }
}

Basically I'm creating a 1million record Dictionary filled with Guids(keys & values). Then testing both a standard foreach and a parallel.foreach loop to write to Redis and then read from Redis/compare to dictionary value.

Result times are all in seconds.

Standard foreach loop(iterating through dictionary to insert into redis):

Redis_1000000_standardforeach.png.75b7b64679f16b89ada68115ac442d5b.png

Almost 5minutes to write and another almost 5 minutes to verify..

Using the Parallel.Foreach method:

Redis_1000000_parallelforeach.png.2ea0e6d400ab85a85a02c2982c8806d4.png

and then again with 1M records already present in DB:

Redis_1M-2M_parallelforeach.png.39e75586711984ce6570619762edbf99.png

And then with 2M records present in DB:

Redis_2M-3M_parallelforeach.png.4e225e1782bdde3854bd4498b250c0c3.png

And then with 3M:

Redis_3M-4M_parallelforeach.png.c2bc6dca4584500f71641fa0393712f5.png

and 4M:

Redis_4M-5M_parallelforeach.png.74c9f8b064716c150bea41adb10ed4fb.png

Well, you get the picture.. ;)

So far, I think I really like Redis. 

And now with the async "fire and forget" set method (db write without response), on empty db:

Console.WriteLine("Starting Parallel 'write' Benchmark.");
Parallel.ForEach(TestDataSet, td =>
{
	//db.StringSet(td.Key, td.Value);
	db.StringSetAsync(td.Key, td.Value, flags: CommandFlags.FireAndForget);
});

Easy enough code change.

Redis_1M_fireandforgetSet.png.f9b4b3e8a77c9935331c8e45509889e5.png

Well, that's pretty fancy.  1 Million records added in under 10 seconds, and not a single error.. :D

So, even though I think this is my winning candidate, I should at least perform the same benchmarks on NCache to see if it somehow blows Redis out of the water. 

So, keep posted, as NCache will be my next victim.  It may be somewhat unfair though since I'll be installing NCache on my Windows machine directly and Redis was sequestered into a tiny vm with very limited resources, so if I need to(if NCache is ridiculously faster ootb) I'll be re-running these benchmarks again on a bigger VM. ;)

 

A few more benchmark results(11/1/18):

I've repeated the final "fire and forget" with a couple bigger data sets.  Same VM, so 1 single core, and 4G ram.

2 Million Records:

redis_2m_faf_midreadverifytop.png.af0af493bd74c47132662e2d7449ba74.png

This shows the top output snapped somewhere in the middle of the "write" test.

 

redis_2m_faf.thumb.png.165173d3dd3687bc9525460e2e9c2f02.png

This shows the benchmark output, the Redis cli console, and top for the Redis server.  Post benchmark.

 

Now 10Million Records:

redis_10m_faf_midinsertop.png.22eec6eb80a4160070ddcd80f8847cf1.png

Redis Server Top output somewhere in the middle of the "write" test.  We're hitting that single core ceiling here, but it's trucking along anyhow.

 

redis_10m_faf_midreadverify.thumb.png.28104d98fdaf8e9254b66c2dc7d9b523.png

Here's a snap from somewhere in the middle of the read/verify test.  Working hard, but not taxing that single core.  Up to 2G of ram now, 10m records.. not too bad.

 

redis_10m_faf.thumb.png.746e5bb8acdb187c675c5a8ff2d40f6d.png

And the completed test.  Frankly, I'm impressed.  It pegged the single core for a fraction of that 83 seconds, but it didn't lose a single record in the process.

 

redis_1m_10m_faf.thumb.png.c756ef1f88bc7b531e3c16ad0efb9aa8.png

And the final test result: a 1million record write, with 10million records already in the DB. :D  Boom, still no errors.

 

So, let us really break it good.

I rewrote the benchmark script to Parallel.Foreach through 10 individual 1million record benchmark tests.  Roughly simulating separate concurrent clients to the Redis server.

And here is where we start finding limits in (this) installation/configuration.

Redis_Excepted.png.b27abbe3c9128ccf1ba7b0ae62e1adaf.png

Redis_Excepted_Multiple_threads.thumb.png.2c1cfdba1560c5f5b860ae4ed4712c5a.png

So, it got to about 3Million records inserted from 6 different connections before the load on the server caused the additional connections to time out(more or less).

Okay, lets try it with 6 connections.  hmm, I have 6 servers.. hmm. ;)

redis_1mX6_midwrite.thumb.png.9834a4e49fc4fb37d7af85c8689adf25.png

Mid write, good to go.

 

redis_1mX6_midreadverify.thumb.png.b81abdd2aa36ed092d0c8824bf12868a.png

Mid read/verify, good to go.

 

redis_1mX6_noworries.thumb.png.bd05f32ec4906da4cb8e7a26f0fac6fc.png

And would you look at that, no exceptions, no read errors. :D  



1 Comment


Recommended Comments

A general observation of your use of Parallel.Foreach and StringSetAsync; the test you're performing there is actually one of how fast you are putting work onto the task scheduler - you're not awaiting the completion of the task (which itself could be problematic in the event of an exception).

Share this comment


Link to comment
5 hours ago, evolutional said:

A general observation of your use of Parallel.Foreach and StringSetAsync; the test you're performing there is actually one of how fast you are putting work onto the task scheduler - you're not awaiting the completion of the task (which itself could be problematic in the event of an exception).

Very true indeed.  Which was why it wasn't the only test.  Regardless, out of the box Redis didn't stumble.  I wasn't really going for a complete benchmark test here either.  More establishing a behavioral baseline which I could use to compare to other products.  I'll be finding its actual hard limits later, I'm sure.  That being said though, if I ever have 1000000 records that need to go into the database in under 10 seconds, I'll probably already have clusters of servers to split the load, so it will likely never be actually used like that in production anyhow.

Share this comment


Link to comment

Also important to note, I wasn't attempting to benchmark the abilities of Redis to handle errors.  I was benchmarking its ability to handle data WITHOUT errors. ;)  I should have reiterated that test with multiple millions of records until it started to fail on retrieval.  That would give me the upper limit if Redis' capabilities.  And now that I think about it, I want to know that information. 

Thanks!

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Advertisement
  • Advertisement
  • Blog Entries

  • Similar Content

    • By ThisIsFix
      YouTube Video Tutorial: https://www.youtube.com/watch?v=et6BAdlxECw&t=7s
      using System.Collections; using System.Collections.Generic; using UnityEngine; public class BillboardFX : MonoBehaviour { public Transform camTransform; Quaternion originalRotation; void Start() { originalRotation = transform.rotation; } void Update() { transform.rotation = camTransform.rotation * originalRotation; } } Script Link: https://github.com/ThisIsFix/Unity-Billboard
    • By IGStudios
      We are currently working in a new indie Survival Crafting game, the project "Putrid" is looking for focused resource farming and modifiable environment.
      The project have an advanced state of development with many items and assets.
      We want a programmer with experience in c# to handle the main mechanics of the game.


      here's our Discord: https://discord.gg/hZQYapv
      and here's my mail for any doubts: tyxefield@gmail.com
    • By RamblingBaba
      Hello,
      So I have been programming with C++ for 2.5 years now. I have been interested in checking out C# for fun. My question is pretty simple. Does C# use a form typically for their games? The simple game examples I have watched on YouTube reminds me A LOT of Visual Basic. 
      Was this just an easy way to show you C#? Or using the form is a big part of C#? Or is it nothing more than using something like MFC and completely irrelevant to making games?
      Thanks
    • By Bromaster
      Hi i´m currently working on  a SciFi-Shooter and im looking for people to help me. 
      If you´re interessted dm me here.
    • By felix.d
      Hello,
      i am Felix from Tübingen. I want to develop animated movies and games. If anyone has the same plans and no partners, please whatsapp me +4917656922229.
      I need people to exchange ideas with
  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!