Golang Unix Socket Setup

Anna Elde / October 19 2017

Golang Unix Socket Setup

If you're using a reverse proxy server with Go, such as Nginx, you need to decide how you'll connect your Go program with your server—either through TCP or UDS (Unix Domain Sockets). This post will walk you through using UDS for your Go program.

Use Case

A reverse proxy isn't appropriate in all use cases. Benchmarks indicate that there is quite a bit of overhead to serving a Go program through a reverse proxy. However, you might require features like virtual hosts, so reducing that overhead through proper configuration is the next logical step.

Code Snippets

The code in this section is from my husband's Funetik Ingglish project, written in Go. Originally, he had it configured to use a TCP connection in his development environment. Here's the original server code and Nginx server block:

Go using TCP

// Start Server
if err := http.ListenAndServe(":8080", nil); err != nil {
    log.Fatal("ListenAndServe:", err)
}

Nginx using TCP

server {
    server_name     my.domain.com;
    listen          443 ssl;

    location / {
        include     proxy_params;
        proxy_pass  http://127.0.0.1:8080;
    }
}

Changing the code to use a Unix socket requires little modification:

Go using Unix Sockets

const SOCK = "/filepath/to/socket/go.sock"

// Start Server
os.Remove(SOCK)
unixListener, err := net.Listen("unix", SOCK)
if err != nil {
    log.Fatal("Listen (UNIX socket): ", err)
}
defer unixListener.Close()
http.Serve(unixListener, nil)

Nginx using Unix Sockets

server {
    server_name     my.domain.com;
    listen          443 ssl;

    location / {
        include     proxy_params;
        proxy_pass  http://unix:/filepath/to/socket/go.sock;
    }
}

Benchmarks

Benchmarks were generated using the two configurations above. Some specifications:

  • The server is a DigitalOcean 512MB VPS
  • wrk was used to benchmark
  • The test was run with 6 threads and 90 connections over the course of two minutes
  • GOMAXPROCS = 1

Go and Nginx using TCP

:~$ wrk -t6 -c90 -d120s https://nils.elde.codes/funing/
Running 2m test @ https://nils.elde.codes/funing/
  6 threads and 90 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    99.84ms   16.67ms 286.55ms   89.45%
    Req/Sec   150.67     20.16   220.00     78.66%
  107798 requests in 2.00m, 1.65GB read
Requests/sec:    897.59
Transfer/sec:     14.09MB

Go and Nginx using Unix Sockets

:~$ wrk -t6 -c90 -d120s https://nils.elde.codes/funing/
Running 2m test @ https://nils.elde.codes/funing/
  6 threads and 90 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    88.09ms   15.79ms 296.23ms   89.66%
    Req/Sec   170.77     23.74   260.00     78.10%
  122203 requests in 2.00m, 1.87GB read
Requests/sec:   1017.53
Transfer/sec:     15.97MB

These results indicate that, compared to TCP, using Unix sockets to connect your Go program to a reverse proxy results in a faster connection able to handle more requests per second.

Discussion

Here's a couple of facts that may help you save some time if you're troubleshooting this sort of configuration:

  1. The folder that will contain the socket must have write permissions enabled for the user that executes the Go program, so that the socket can be created/destroyed.
  2. Don't put your socket in /tmp. This directory might be namespaced, meaning that the /tmp folder is a different folder for each running program.
  3. If you're testing this using Docker, do not set the socket location within a volume—it will be hidden (shadowed) by your own file system.
  4. Remember to remove your old socket before creating a new one. You can use os.Remove() to do this.

Credits: The Go Gopher in the post banner is originally by Renee French and was cast in marble and gold leaf by Nils Elde for this post.