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
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)
...