Search code examples
gobenchmarkingredigo

Redigo: getting errors on apache load testing


I am connecting my go program to redis using library redigo. When i run one request I get correct results. But on load testing, using apache benchmark tool, It works when:

ab -n 1000 -k -c 10 -p post.txt -T application/x-www-form-urlencoded http://localhost:8084/abcd

However when request is:

ab -n 1000 -k -c 15 -p post.txt -T application/x-www-form-urlencoded http://localhost:8084/abcd

I am getting error:

panic: dial tcp :6379: too many open files

This is my code:

func newPool() *redis.Pool {
    return &redis.Pool{
        MaxIdle: 80, // max number of connections
        Dial: func() (redis.Conn, error) {
            c, err := redis.Dial("tcp", ":6379")
            if err != nil {
                panic(err.Error())
            }
            return c, err
        },
        // If Wait is true and the pool is at the MaxActive limit, then Get() waits
        // for a connection to be returned to the pool before returning
        Wait: true,
    }
}

var pool = newPool()

func checkError(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

func func1(pool *redis.Pool, id int) string {
    c := pool.Get()
    defer c.Close()
    m, err := redis.String(c.Do("HGET", "key", id))
    checkError(err)
    return m
}

func func2(pool *redis.Pool, string_ids []string) chan string {
    c := make(chan string)
    var ids []int
    var temp int
    for _, v := range string_ids {
        temp, _ = strconv.Atoi(v)
        ids = append(ids, temp)
    }
    go func() {
        var wg sync.WaitGroup
        wg.Add(len(ids))
        for _, v := range ids {
            go func(id int) {
                defer wg.Done()
                c <- func1(pool, id)
            }(v)
        }
        wg.Wait()
        close(c)
    }()
    return c
}
func getReq(w http.ResponseWriter, req *http.Request) {
    err := req.ParseForm()
    checkError(err)
    ids := req.Form["ids[]"]
    for v := range func2(pool, ids) {
        fmt.Println(v)
    }
}

func main() {
    http.HandleFunc("/abcd", getReq)

    log.Fatal(http.ListenAndServe(":8084", nil))
}

How to handle atleast 40 concurrent request using apache benchmark tool.

Note: I haven't changed anything in my redis conf file

I am getting following response when running apache benchmark tool. Only 15 request are completed.

$ ab -n 1000 -k -c 15 -p post.txt -T application/x-www-form-urlencoded http://localhost:8084/abcd
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
apr_socket_recv: Connection refused (111)
Total of 15 requests completed

Solution

  • To fix the issue immediately, set MaxActive on your redis.Pool, which you mention in the comments but don't set yourself.

    Fundamentally though, you should not be dispatching goroutines for every id lookup. Your maximum possible concurrency would then be (number of client connections) x (number of ids in the request), each of which can open a new redis connection. It would be far faster and more efficient to have a single redis connection read each of the ids serially. There's no need for any of the extra concurrency you have here, do it all serially from the handler, and don't convert the strings to ints when redis only operates on string keys to begin with.

    func getReq(w http.ResponseWriter, req *http.Request) {
        err := req.ParseForm()
        checkError(err)
    
        c := pool.Get()
        defer c.Close()
    
        for _, id := range req.Form["ids[]"] {
            m, err := redis.String(c.Do("HGET", "key", id))
            checkError(err)
            fmt.Println(id)
        }
    }
    

    If you want to optimize this further, you can use pipelines to reduce the round trips to the redis server.

    for _, id := range req.Form["ids[]"] {
        c.Send("HGET", "key", id))
    }
    c.Flush()
    values, err := redis.Strings(c.Receive())
    checkError(err)
    ...