Benchmarking Redis List Operations with FunTester: Multi‑Threaded Performance Insights
This article presents a detailed multi‑threaded performance benchmark of Redis list commands using the FunTester framework, describing three test scenarios, Java implementation details, raw JSON results, and observations on QPS and latency for insert, pop, and combined add‑remove operations.
In earlier posts the author introduced the FunTester framework and performed Redis key‑value performance tests. This article extends the study to Redis list data structures, covering three test scenarios: bulk insertion, concurrent removal, and combined add‑remove operations.
Test Scenarios
Insert a batch of list keys and concurrently push elements to each list.
Based on the data from scenario 1, concurrently pop elements from each list.
Simultaneously add and remove elements from the same list, including operations at both head and tail, and verify that added elements are correctly retrieved.
Implementation Approach
The author uses multi‑threading with a Redis connection pool. Each thread works with a unique key to avoid contention. An AtomicInteger provides unique list names, and the custom FixedThread implementation is extended with a listName field.
Overall flow: each thread repeatedly executes the operations defined in the selected scenario.
For scenario 3 the test case creates a list, pushes an element a to the head, pushes b to the tail, then pops from head ( c) and tail ( d) and asserts a==c and b==d.
Scenario 1 Code
class RedisList01 extends RedisBase {
static AtomicInteger num = new AtomicInteger(0);
static RedisBase drive;
public static void main(String[] args) {
String host = "FunTester";
int port = 6379;
drive = new RedisBase(host, port);
int times = 400;
int thread = 20;
Constant.RUNUP_TIME = 0;
def tester = new FunTester(times);
def task = new Concurrent(tester, thread, "redis test, list add test");
task.start();
drive.close();
}
private static class FunTester extends FixedThread {
String listName = DEFAULT_STRING + num.getAndIncrement();
FunTester(int limit) { super(null, limit, true); }
@Override
protected void doing() throws Exception {
drive.lpush(listName,
StringUtil.getString(10), StringUtil.getString(10),
StringUtil.getString(10), StringUtil.getString(10),
StringUtil.getString(10));
}
@Override
ThreadBase clone() { return new FunTester(this.limit); }
}
}Scenario 1 Results
{
"rt":165,
"failRate":0.0,
"threads":20,
"deviation":"0.44%",
"errorRate":0.0,
"executeTotal":7899,
"qps2":120.67464136761538,
"total":7899,
"qps":121.21212121212122,
"startTime":"2021-09-16 16:05:22",
"endTime":"2021-09-16 16:06:28",
"mark":"redis list add test",
"table":"eJztkzsKwkAQhnshd5gDRIjBKscQLxBwwQU3SlZBS18oWptSPIFWYuFtAorHcESND8SNRt0gs/wwIcX830c2RgaUx2clLrfL8W4x2cynu9XSrHBZ367Wm9Hs+BpsC+pln7kl9TYjYzzvLDBZq3qSQZEL5kAzK5nP3Qp4DWFCKyuQxvVUHWoOwT047nJyeQuENIXbdGzLxscPWCQ9YTDExDJJd0sYdDDfbQqDNgZHH3Ou+lRjtLyLwdHDXKoGmMu4Ln44XqSJ1a0sfXmoKCOs06f9Edab5BHtLeaDu5KmcTa5vwIpx45l9ux//YdxMEwBBhmSIRnqxyBDMiRD/RhkSIZkqB+DDMmQDPVjkCEZkqF+DDJMZLgHx9E+BA=="
}Scenario 2 Code
class RedisList02 extends RedisBase {
static AtomicInteger num = new AtomicInteger(0);
static RedisBase drive;
public static void main(String[] args) {
String host = "FunTester";
int port = 6379;
drive = new RedisBase(host, port);
int times = 400;
int thread = 20;
Constant.RUNUP_TIME = 0;
def tester = new FunTester(times);
def task = new Concurrent(tester, thread, "redis test, list pop from head test");
task.start();
drive.close();
}
private static class FunTester extends FixedThread {
String listName = DEFAULT_STRING + num.getAndIncrement();
FunTester(int limit) { super(null, limit, true); }
@Override
protected void doing() throws Exception { drive.lpop(listName); }
@Override
ThreadBase clone() { return new FunTester(this.limit); }
}
}Scenario 2 Results
{
"rt":172,
"failRate":0.0,
"threads":20,
"deviation":"0.25%",
"errorRate":0.0,
"executeTotal":7912,
"qps2":115.98962074677847,
"total":7912,
"qps":116.27906976744185,
"startTime":"2021-09-16 16:08:43",
"endTime":"2021-09-16 16:09:51",
"mark":"redis test, list pop from head test",
"table":"eJzj5VLAD4pSUzKLn23tfrF+6tN1815s36qTk1lc8mR339MlW170bX/aP+3pzm1POxa8nLkEokrByEChJKMoNTGFgMkKvFy8+G0PSi0uyM8rTlUIycxNtVKo0C1OLcpMzFHIK83VUajUzQU6LTGPkB2EXKGgkJuZpwAxy8rQxFght1gnN7HCysjMAsgkrJugLygFj6Z1ABFRPhm1ZdSWkW3Lo2ntQERzmx5NawYimDVUt+3RtEYgAlJNQISgWoAIzYcUuQDdGqifoNa0AhGCglqK227iKbyuhLsJn2Mosp56rh/0biXRV0SksKFJ8XINCmeM+nDUh6M+HHhnjPpw1IejPhx4Z4z6cNSHoz4ceGeM+nDUh6M+HHhn0NSHAKoNkl0="
}Scenario 3 Code
class RedisList03 extends RedisBase {
static AtomicInteger num = new AtomicInteger(0);
static RedisBase drive;
public static void main(String[] args) {
String host = "FunTester";
int port = 6379;
drive = new RedisBase(host, port);
int times = 100;
int thread = 20;
Constant.RUNUP_TIME = 0;
def tester = new FunTester(times);
def task = new Concurrent(tester, thread, "redis test, list pop from tail test");
task.start();
drive.close();
}
private static class FunTester extends FixedThread {
String listName = DEFAULT_STRING + num.getAndIncrement();
FunTester(int limit) { super(null, limit, true); }
@Override
protected void doing() throws Exception {
def a = Time.getTimeStamp() + StringUtil.getString(10);
def b = Time.getTimeStamp() + StringUtil.getString(10);
drive.lpush(listName, a);
drive.rpush(listName, b);
def c = drive.lpop(listName);
def d = drive.rpop(listName);
if (a != c || b != d) {
com.funtester.base.exception.FailException.fail(this.threadName + " verification failed!");
}
}
@Override
ThreadBase clone() { return new FunTester(this.limit); }
}
}Scenario 3 Results
{
"rt":673,
"failRate":0.0,
"threads":20,
"deviation":"0.55%",
"errorRate":0.0,
"executeTotal":1981,
"qps2":29.55481291400609,
"total":1981,
"qps":29.71768202080238,
"startTime":"2021-09-16 16:23:31",
"endTime":"2021-09-16 16:24:38",
"mark":"redis test, list pop from tail test",
"table":"eJzj5VLAD4pSUzKLn23tfrF+6tN1815s36qTk1lc8mR339MN+170bX/aP+3pzm1POxa8nLkEokrByEChJKMoNTGFgMkKvFy8+G0PSi0uyM8rTlUIycxNtVKo0C1OLcpMzFHIK83VUajUzQU6LTGPkB2EXKGgkJuZpwAxy8rU3Ewht1gnN7HCysLUGMgkrJugLygFj6Z1ABFRPhm1ZYjb8mhaIxABqSYgQlDNQASkWoAIZi0ltqPbAjW3FYgQVDsQoVlGMkXIdXCHoHqQ+g6h0OX43TkgDiTPR3CPoPpgEDiPYt9hzR7DiAL5cBA4Y9SHoz4c9eHAO2PUh6M+HPXhwDtj1IejPhz14cA7Y9SHoz4c9eHAO2PUh6M+HPXhwDuDpj4EAOQLudM="
}Observations
The QPS for scenario 3 (combined add‑remove) is roughly one‑fourth of the single‑operation scenarios, which matches the expectation that a full add‑remove cycle incurs more overhead.
Performance Curves
Conclusion
The series demonstrates how to use FunTester to benchmark Redis list operations under realistic multi‑threaded workloads. While Redis is generally fast, extreme cases such as uneven key distribution or specific data‑structure designs can become performance bottlenecks, and the presented metrics help evaluate alternative storage solutions.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
