redis / rueidis

A fast Golang Redis client that supports Client Side Caching, Auto Pipelining, Generics OM, RedisJSON, RedisBloom, RediSearch, etc.
Apache License 2.0
2.43k stars 153 forks source link

What is the difference between Auto Pipelining and Manually Pipelining? #182

Closed totorofly closed 1 year ago

totorofly commented 1 year ago

Hello, I'm here to seek your help again.For each API request from the user, the server program needs to execute 10 FT.SEARCH commands, each with a different query. Currently, my program runs these commands one by one and retrieves the results for each one sequentially. However, this approach does not save round-trip time (RTT), and it also does not save on context switching between Redis server kernel space and user space.

Therefore, I plan to use pipeline to bundle the 10 FT.SEARCH commands required for each API request together and execute them via pipeline to reduce RTT and context switching.

I have written functions for both Auto Pipelining(testReidsPipelineAuto) and Manually Pipelining(testReidsPipelineManual) and performed comparative tests. I expect both functions to return the results of the 10 FT.SEARCH commands required for each request in the order they were initiated, with each result corresponding to the respective command in a one-to-one manner.

The execution speed of testReidsPipelineAuto is faster than testReidsPipelineManual, but while testReidsPipelineAuto is expected to return results for 10 FT.SEARCH commands, it only returns the result of the last FT.SEARCH command. On the other hand, testReidsPipelineManual returns results for each of the 10 FT.SEARCH commands in order.

Here is my code example. What is causing the difference between the two, and is there an error in the implementation of the testReidsPipelineAuto function?

func testReidsPipelineAuto() {
    //记录函数执行时间
    defer func(t time.Time) {
        fmt.Println("执行时间testReidsPipelineAuto:", time.Since(t))
    }(time.Now())
    ctx := context.Background()
    var sqlArr = []string{
        "'@status:1 @preOrderTime:[-inf (1678288103] @providerCode:xxxxxxxx @hitMassRuleId:(last4_mid2|last4|last3_mid2|last3|last4_mmdd|last4_yyyy|last4_idCardLast4|mid4_mmdd|mid4_yyyy|last2_yy|anyAABB|anyAAA|anyAAAA|anyABCD|lastAAB|lastABC|lastAABB|lastABBA|lastABAB|lastAAAB|anyABCABC|anyABCDEF|lastAABBB|anyABCDABCD|anyAAABBB|random_include4|random_any|random_rule_mass|any5|any4|any3|lastABCABC|lastABCD|anyAABBB|lastAAAA|lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear|lastAAABBB|lastAABBCC|lastABABAB|anyAABBCC|anyABABAB|lastAAAAA|lastAAAAB|lastABBCBB|lastABBCDD|anyAAAAA|anyAAAAB|anyABBCBB|anyABBCDD|lastABACAD|anyABBA|anyABAB|anyABACAD|lastABBB|lastABAC|lastABB|anyABB|any88|any66|lastX8_X9|anyX80|anyX90|any360|any365|any3658|any158|any168|any178|any198|any189|tail5|tail4|tail3|continuous5|continuous4|continuous3|fiveNum|anyABC|any1314|any520|any521|any530|any230|any731|any920|any813|any258|lastAXAX|lastXAXA|head1888|head1889|head1XAA|head1XA8|head1XA9|head13X8|head13X9|anyAXAX|anyXAXA|anyXAA|nohit|head1520) @last4:(6886)'",
        "'@status:1 @preOrderTime:[-inf (1678288162] @providerCode:xxxxxxxx @hitMassRuleId:anyABACAD|any3658|any189|lastAAB|anyAAA|lastABC|any258|lastABB|any198|any530|anyX80|any66|any1314|any230|any365 @fiveNum:无 -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288185] @providerCode:xxxxxxxx @hitMassRuleId:any158|anyX80|any520|lastAXAX|anyABB|any88|anyX90|any258|any530|any521|any168 -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288200] @providerCode:xxxxxxxx @hitMassRuleId:any521|any189|any530|any813|any365|any88|any520|any198|anyXAA|anyABC|any1314 -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288213] @providerCode:xxxxxxxx @hitMassRuleId:anyABAB|anyABCD|anyAABB|anyABACAD|lastAAB|anyAAA -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288309] @providerCode:xxxxxxxx @hitMassRuleId:lastABACAD|anyAABB|lastAAAB|lastABAC|lastABBB|lastABBA|anyAAAA|anyABBA -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288320] @providerCode:xxxxxxxx @hitMassRuleId:lastAAAA|lastAABBCC|anyAAABBB|lastABAC|lastABCD|lastAAABBB|anyAABBB|lastAAAAB|anyAABBCC|anyABCDABCD|anyAAAA|lastAAAAA|lastABCABC -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288320] @providerCode:xxxxxxxx @hitMassRuleId:lastAAAA|lastAABBCC|anyAAABBB|lastABAC|lastABCD|lastAAABBB|anyAABBB|lastAAAAB|anyAABBCC|anyABCDABCD|anyAAAA|lastAAAAA|lastABCABC -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288320] @providerCode:xxxxxxxx @hitMassRuleId:lastAAAA|lastAABBCC|anyAAABBB|lastABAC|lastABCD|lastAAABBB|anyAABBB|lastAAAAB|anyAABBCC|anyABCDABCD|anyAAAA|lastAAAAA|lastABCABC -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288320] @providerCode:xxxxxxxx @hitMassRuleId:lastAAAA|lastAABBCC|anyAAABBB|lastABAC|lastABCD|lastAAABBB|anyAABBB|lastAAAAB|anyAABBCC|anyABCDABCD|anyAAAA|lastAAAAA|lastABCABC -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
    }
    var sqlAndIdArr []sqlAndId
    for i, sql := range sqlArr {
        sqlAndIdArr = append(sqlAndIdArr, sqlAndId{
            sql: sql,
            id:  i,
        })
    }
    eg, _ := errgroup.WithContext(ctx)
    for i, sql_and_id := range sqlAndIdArr {
        eg.Go(func() (err error) {
            resp, err := global.ASIR_RedisDB.Do(ctx, global.ASIR_RedisDB.B().FtSearch().Index("phoneIndex").Query(sql_and_id.sql).Sortby("diffNumCount").Asc().Limit().OffsetNum(0, int64(1)).Build()).ToArray()
            if err != nil {
                panic(err.Error()) // proper error handling instead of panic in your app
            }
            for k := 1; k < len(resp); k += 2 {
                key, _ := resp[k+0].ToString()
                val, _ := resp[k+1].ToArray()
                var record good_num.PhoneRedisObj
                if len(val) == 0 {
                    continue
                } else if len(val) == 2 {
                    if err := val[1].DecodeJSON(&record); err != nil {
                        panic(err)
                    }
                } else if len(val) == 4 {
                    if err := val[3].DecodeJSON(&record); err != nil {
                        panic(err)
                    }
                }

                sqlAndIdArr[i].phoneData = record
            }
            return err
        })
    }
    eg.Wait()
    fmt.Println("sqlAndIdArr[0].phoneData.Phone:", sqlAndIdArr[0].phoneData.Phone)
}

func testReidsPipelineManual() {
    //记录函数执行时间
    defer func(t time.Time) {
        fmt.Println("执行时间testReidsPipelineManual:", time.Since(t))
    }(time.Now())
    ctx := context.Background()
    var sqlArr = []string{
        "'@status:1 @preOrderTime:[-inf (1678288103] @providerCode:xxxxxxxx @hitMassRuleId:(last4_mid2|last4|last3_mid2|last3|last4_mmdd|last4_yyyy|last4_idCardLast4|mid4_mmdd|mid4_yyyy|last2_yy|anyAABB|anyAAA|anyAAAA|anyABCD|lastAAB|lastABC|lastAABB|lastABBA|lastABAB|lastAAAB|anyABCABC|anyABCDEF|lastAABBB|anyABCDABCD|anyAAABBB|random_include4|random_any|random_rule_mass|any5|any4|any3|lastABCABC|lastABCD|anyAABBB|lastAAAA|lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear|lastAAABBB|lastAABBCC|lastABABAB|anyAABBCC|anyABABAB|lastAAAAA|lastAAAAB|lastABBCBB|lastABBCDD|anyAAAAA|anyAAAAB|anyABBCBB|anyABBCDD|lastABACAD|anyABBA|anyABAB|anyABACAD|lastABBB|lastABAC|lastABB|anyABB|any88|any66|lastX8_X9|anyX80|anyX90|any360|any365|any3658|any158|any168|any178|any198|any189|tail5|tail4|tail3|continuous5|continuous4|continuous3|fiveNum|anyABC|any1314|any520|any521|any530|any230|any731|any920|any813|any258|lastAXAX|lastXAXA|head1888|head1889|head1XAA|head1XA8|head1XA9|head13X8|head13X9|anyAXAX|anyXAXA|anyXAA|nohit|head1520) @last4:(6886)'",
        "'@status:1 @preOrderTime:[-inf (1678288162] @providerCode:xxxxxxxx @hitMassRuleId:anyABACAD|any3658|any189|lastAAB|anyAAA|lastABC|any258|lastABB|any198|any530|anyX80|any66|any1314|any230|any365 @fiveNum:无 -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288185] @providerCode:xxxxxxxx @hitMassRuleId:any158|anyX80|any520|lastAXAX|anyABB|any88|anyX90|any258|any530|any521|any168 -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288200] @providerCode:xxxxxxxx @hitMassRuleId:any521|any189|any530|any813|any365|any88|any520|any198|anyXAA|anyABC|any1314 -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288213] @providerCode:xxxxxxxx @hitMassRuleId:anyABAB|anyABCD|anyAABB|anyABACAD|lastAAB|anyAAA -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288309] @providerCode:xxxxxxxx @hitMassRuleId:lastABACAD|anyAABB|lastAAAB|lastABAC|lastABBB|lastABBA|anyAAAA|anyABBA -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288320] @providerCode:xxxxxxxx @hitMassRuleId:lastAAAA|lastAABBCC|anyAAABBB|lastABAC|lastABCD|lastAAABBB|anyAABBB|lastAAAAB|anyAABBCC|anyABCDABCD|anyAAAA|lastAAAAA|lastABCABC -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288320] @providerCode:xxxxxxxx @hitMassRuleId:lastAAAA|lastAABBCC|anyAAABBB|lastABAC|lastABCD|lastAAABBB|anyAABBB|lastAAAAB|anyAABBCC|anyABCDABCD|anyAAAA|lastAAAAA|lastABCABC -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288320] @providerCode:xxxxxxxx @hitMassRuleId:lastAAAA|lastAABBCC|anyAAABBB|lastABAC|lastABCD|lastAAABBB|anyAABBB|lastAAAAB|anyAABBCC|anyABCDABCD|anyAAAA|lastAAAAA|lastABCABC -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
        "'@status:1 @preOrderTime:[-inf (1678288320] @providerCode:xxxxxxxx @hitMassRuleId:lastAAAA|lastAABBCC|anyAAABBB|lastABAC|lastABCD|lastAAABBB|anyAABBB|lastAAAAB|anyAABBCC|anyABCDABCD|anyAAAA|lastAAAAA|lastABCABC -@hitRuleMassId:(lastBrithDay|lastBrithYear|anyBrithDay|anyBrithYear)'",
    }
    var phoneDataArr []good_num.PhoneRedisObj
    cmds := make(rueidis.Commands, 0, 10)
    for _, sql := range sqlArr {
        cmds = append(cmds, global.ASIR_RedisDB.B().FtSearch().Index("phoneIndex").Query(sql).Sortby("diffNumCount").Asc().Limit().OffsetNum(0, int64(1)).Build())
    }
    for _, resp := range global.ASIR_RedisDB.DoMulti(ctx, cmds...) {
        if err := resp.Error(); err != nil {
            panic(err)
        }
        temp, _ := resp.ToArray()
        var record good_num.PhoneRedisObj
        val, _ := temp[2].ToArray()

        //当val的长度为0时,说明是空的,刚好时效到了,只获取到了key而没有拿到value,不需要解析,直接跳过
        if len(val) == 0 {
            continue
        } else if len(val) == 2 {
            if err := val[1].DecodeJSON(&record); err != nil {
                panic(err)
            }
        } else if len(val) == 4 {
            if err := val[3].DecodeJSON(&record); err != nil {
                panic(err)
            }
        }
        fmt.Println("record:", record.Phone)
        phoneDataArr = append(phoneDataArr, record)
    }
    fmt.Println("phoneDataArr", phoneDataArr[0].Phone)
}
rueian commented 1 year ago

Try add this line

for i, sql_and_id := range sqlAndIdArr {
---->       i, sql_and_id := i, sql_and_id
        eg.Go(func() (err error) {
totorofly commented 1 year ago

Try add this line

for i, sql_and_id := range sqlAndIdArr {
---->     i, sql_and_id := i, sql_and_id
      eg.Go(func() (err error) {

Thank you, it's resolved now. Both testReidsPipelineAuto and testReidsPipelineManual are working properly on standalone Redis through public network connection.

image

However, when I tried to connect to the Redis cluster through internal network connection, testReidsPipelineAuto worked fine, but testReidsPipelineManual threw an error: "panic: Blocking module command called from transaction". The screenshot is shown below:

image image

Does DoMulti not support Redis Cluster?

rueian commented 1 year ago

Yes, DoMulti supports clusters. However, it currently wraps commands with MULTI EXEC block to handle retries if their slot got migrated. This behavior might be changed in future releases.

But now, you can use the auto pipelining way.

Another thing is that RediSearch by default does not support Redis Cluster. Did you get expected results with testReidsPipelineAuto on the cluster?

totorofly commented 1 year ago

Yes, DoMulti supports clusters. However, it currently wraps commands with MULTI EXEC block to handle retries if their slot got migrated. This behavior might be changed in future releases.

But now, you can use the auto pipelining way.

Another thing is that RediSearch by default does not support Redis Cluster. Did you get expected results with testReidsPipelineAuto on the cluster?

testReidsPipelineAuto seems to be able to achieve the results I expected. The open-source version of Redis Cluster actually supports RediSearch, and building the corresponding .so module for the OSS version can enable support for RediSearch.

Install OSS version 2.4.15 to keep it consistent with the latest enterprise version

git clone -b v2.4.15 --recursive https://github.com/RediSearch/RediSearch.git

make setup

make build COORD=oss

cd /root/RediSearch/bin/linux-x64-release/coord-oss

After compilation, copy module-oss.so to the bin directory

redisearch.so # For master-slave module-oss.so # For cluster

rueian commented 1 year ago

Hi @dwzkit, the behavior of wrapping commands with MULTI EXEC has been removed in v0.0.95. You can now use testReidsPipelineManual with a redis cluster.