Running Phoenix on a Unix socket

phoenix
#1

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
#2

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
#3

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
#4

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
#5

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)

7 Likes
#6

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
#7

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

#8

@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
#9

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

2 Likes
#10

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

#11

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
#12

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?

1 Like
#13

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

2 Likes
#14

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

5 Likes
#15

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.

1 Like
#16

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

2 Likes
#17

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.

5 Likes
#18

Great, looking forward to test it.

2 Likes