Running Phoenix on a Unix socket

Any experiences and performance results compared to running the Phoenix server on a TCP port? Any minimum system requirements to use Unix socket?

1 Like

I donā€™t think you can run Cowboy (which is the only server Phoenix officially works with AFAIK) on unix sockets.

EDIT: someone says you can since OTP 19.

As far as performance goes, itā€™s not specific to Erlang and should be somewhat faster.

3 Likes

Whatā€™s the state of this?

There was a PR for OTP. Itā€™s marked as closed on Github, but the discussion seems to indicate that it got put into OTP-19.0-rc2.

I want to write a Docker plugin in Elixir, but it requires communicated with Docker over a Unix socket.

Thanks for the help.

2 Likes

With :gen_tcp you can use the tuple {:local, path} to connect to a unix socket. It is mentioned in the :inet module docs:
http://erlang.org/doc/man/inet.html#type-local_address

If you want to make HTTP requests to a unix socket you can use HTTPoison/Hackney with the ā€œhttp+unix://ā€ scheme, which is a bit of a hidden feature:

7 Likes

For posterity, this worksā€¦

{:ok, pid} = Plug.Adapters.Cowboy.http(MyPlug, [], ip: {:local, "my_plug.sock"}, port: 0)

The port: 0 is important; it wonā€™t work without it.

Also, you gotta rm my_plug.sock when youā€™re done otherwise it wonā€™t start up again: :eaddrinuse (address already in use)

9 Likes

Any suggestions on getting the Phoenix.Endpoint to accept these options?
Iā€™m getting an error in ranch_listener_sup:

config :hello, HelloWeb.Endpoint,
  http: [port: 0, ip: {:local, "hello.sock"}],
  debug_errors: true,
  code_reloader: false,
  check_origin: false,
  watchers: []
Error Details
** (Mix) Could not start application hello: Hello.Application.start(:normal, []) returned an error: shutdown: failed to start child: HelloWeb.Endpoint
    ** (EXIT) shutdown: failed to start child: Phoenix.Endpoint.Handler
        ** (EXIT) shutdown: failed to start child: {:ranch_listener_sup, HelloWeb.Endpoint.HTTP}
            ** (EXIT) an exception was raised:
                ** (Protocol.UndefinedError) protocol String.Chars not implemented for {:error, :einval}. This protocol is implemented for: Atom, BitString, Date, DateTime, Float, Integer, List, NaiveDateTime, Time, URI, Version, Version.Requirement
                    (elixir) /private/tmp/elixir-20180316-64850-zsrybb/elixir-1.6.4/lib/elixir/lib/string/chars.ex:3: String.Chars.impl_for!/1
                    (elixir) /private/tmp/elixir-20180316-64850-zsrybb/elixir-1.6.4/lib/elixir/lib/string/chars.ex:22: String.Chars.to_string/1
                    (phoenix) lib/phoenix/endpoint/cowboy_handler.ex:115: Phoenix.Endpoint.CowboyHandler.info/3
                    (phoenix) lib/phoenix/endpoint/cowboy_handler.ex:100: Phoenix.Endpoint.CowboyHandler.start_link/3
                    (stdlib) supervisor.erl:365: :supervisor.do_start_child/2
                    (stdlib) supervisor.erl:348: :supervisor.start_children/3
                    (stdlib) supervisor.erl:314: :supervisor.init_children/2
                    (stdlib) gen_server.erl:365: :gen_server.init_it/2
                    (stdlib) gen_server.erl:333: :gen_server.init_it/6
                    (stdlib) proc_lib.erl:247: :proc_lib.init_p_do_apply/3

What would be the reason to do this? Are plain Unix sockets faster than using a TCP port (when putting Phoenix+Cowboy behind a webserver like Nginx or Apache?)

@Qqwy There is a noticeable speed boost from using sockets over tcp. From what I recall it is 10-15%, in addition to avoiding the overhead of TLS.

Iā€™ve explored using a socket to connect to a legacy app running in a different languageā€”like terraformer, but on the same host and without the extra overhead.

2 Likes

AFAIK a Unix socket does not suffer from the TCP slow start. Especially for short lived connections this is a noticeable bottleneck.

2 Likes

So, is it possible to run Phoenix on a Unix socket?

In general it seems to be possible, as you can inject the socket after it has been established into cowboy, Iā€™m not sure though how this fits into phoenix startup procedure.

How it will work in Erlang using cowboy is explained briefly in this issue:

1 Like

Iā€™m primarily interested in unix sockets as a way to implement zero downtime deploys by updating a symlink to the current release and reloading nginx config. Never used Capistrano but I understand it worked similarly.

After trying to get this working Iā€™ve run into a couple more issues. The error above just requires a small change to the startup message phoenix outputs which assumes a port number.

The next issue I hit is the Plug.Static plug. Eventually it calls into the :file.sendfile kernel function, which fails with {:error, :badarg} when given a unix socket. There is a work-around in :ranch_transport.sendfile but Iā€™m not sure if the best place to fix this is in erlang, ranch, cowboy or plug?

2 Likes

Or maybe you could serve static files from nginx since you are already using it anyway?

2 Likes

Turns out the new file system in OTP 21 fixed the issue, so Phoenix should now run over a unix socket.

6 Likes

Much of this depends on the version of kernel. It is true that since the beginning of time, communication over the loopback subnet went through the entire TCP stack, twice, ā€œoutgoingā€ and ā€œincomingā€. But in some recentā€™ish Linux kernel short-circuiting was added so that those comms now skip that overhead and work much more like domain sockets.

2 Likes

Ooooo I hope this gets documented, should switch my server over. ^.^

2 Likes

If you follow the trail from the Phoenix endpoint docs, you land at: https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html

Which does mention how to set the ip and port appropriately for unix sockets.

6 Likes

Great, looking forward to test it.

2 Likes

Indeed, it is possible like this:
http: [ip: {:local, '/srv/phx/project/development.socket'}, port: 0],

7 Likes