I am pleased to announce the first alpha release of Simple_httpd, available on github and opam. It is a library to produce web server and sites.
Documentation: index (simple_httpd.index)
Github : GitHub - craff/simple_httpd: A library to write web server and site
WARNING: currently we need the latest master of ocaml-ssl. It requires Linux and OCaml 5.0, if you have this, you can install with:
opam pin add https://github.com/savonet/ocaml-ssl#master -k git
opam pin add https://github.com/craff/simple_httpd -k git
And test the template site (very simple, as it is an empty shell to start from) with
dune exec -- ./server.exe --log-folder ./log
The template is also documented at template (simple_httpd.template)
It aims at
Being simple to use and rather complete (support ssl, chaml: an equivalent of php, but in OCaml and compiled, status and statistics, authentication, cookies, …).
Being fast: our latencies and number of requests per seconds are very good, thanks to using linux epoll, eventfd, OCaml’s effects and domains, … The first page of the documentation shows some graphics, but here is a small
comparison of latencies for a small 1kb file:
min mean 50% 90% 95% 99% max
Simple_httpd 79.478µs 242.006µs 237.576µs 294.802µs 305.68µs 329.352µs 3.049ms
Nginx 170.551µs 328.904µs 309.577µs 384.313µs 400.51µs 482.987µs 42.003ms
Apaches 196.321µs 466.439µs 452.265µs 545.121µs 590.05µs 913.527µs 6.372ms
And a small chaml (our equivalent of php) against php-fpm from apache and nginx:
Simple_httpd 146.944µs 285.044µs 280.552µs 341.175µs 356.497µs 507.305µs 8.069ms
Nginx 411.151µs 793.437µs 653.131µs 796.300µs 882.268µs 2.9ms 44.504ms
Apache 688.765µs 2.342ms 950.647µs 1.201ms 1.321ms 5.844ms 1.171s
These were obtained with vegeta at 1000 requests/s. Simple_httpd offers much more stable latencies under charge than nginx or apache.
If you want your own measurments, you need to setup nginx/php on ports 7080 and 7443, an apache/php on port 80 and 443. Then, you can run [./bench.sh] from the [tests] folder of the source tree. I would be happy to have measurments for a big server with more than 20 cores.
Currently only linux is supported.
Help, comments, bug reports, … would be greatly appreciated, as this is alpha release, it is time for you to propose change in the design of the library.
My website https://raffalli.eu and therefore simple_httpd documentation are powered by simple_httpd (do we name this bootstrap ?
It’s be interesting to compare simple_httpd with tiny_httpd + moonpool
(see: GitHub - c-cube/tiny-httpd-moonpool-bench: experiment with tiny_httpd using moonpool as a scheduler) :-). I
wonder how much the two diverged, I wasn’t expecting simple_httpd to use
Very well indeed. Here is both latencies at 2500 req/s using vegeta:
- Tiny httpd+moonpool
Latencies [min, mean, 50, 90, 95, 99, max] 97.845µs, 197.1µs, 194.848µs, 251.333µs, 265.961µs, 470.347µs, 1.621ms
Latencies [min, mean, 50, 90, 95, 99, max] 98.27µs, 176.478µs, 169.674µs, 229.443µs, 243.888µs, 279.937µs, 2.008ms
For the number of requests per seconds with wrk, we are at 139675 for tiny+moonpool and 167819 for simple_httpd.
Tiny is a bit better for the worst case. Simple_httpd is better in average and for all quantile. I think I am missing a preemptive scheduler that I could get if there where some way to perform an effect periodically in OCaml.
Moonpool is a very good idea actually to get a preemptive scheduler with domains !
I should also say that simple_httpd routing is now looking at the Host field, address and port, not only the path and method, It has logging by type and level (not just on/off) and maybe a few other extra feature that do not come to my mind now but have an impact for an hello request.
On the side of memory, VSZ=1032908Ko RSS=27492Ko for simple_https and VSZ=999288Ko RSS=401804Ko for tiny All this is very reasonnable but threads use much more resident memory.
Yes simple_httpd use edge trigerred epoll and eventfd. This means we do not have to change the configuration of the socket in the epoll list (it is set up when we accept the connection) and mutex are dealt with using epoll too thanks to eventfd. So all scheduling is done by epoll_wait (inclusing our support for sleep, using the timeout).
That’s pretty cool, thank you. I think it’s good that both options exist
Tiny_httpd was never designed to be super high performance anyway, but
it’s nice that with a thread pool it’s at least decent. Memory usage is
not as good indeed (I also think OCaml 5.0 has some GC issues in
general, it’ll improve).
I think two things are missing from your results:
- what machine you used
- the CPU usage in both cases (I suspect it’s higher for tiny_httpd)
In fact as Tiny_hhtpd is simple, it is very efficient (even if mono thread, unless you use some extra machinery like moonpool). This is why I was using it and started working on it!
And now that simple_httpd is in production on my server I feel relived not to have php running on my server!
My machine for the test is a poor ACER Ryzen 5 3500U laptop with 6 cores, client and server are on the same machine so I don’t measure the network performance and CPU usage is at the maximum with wrk and mixup clients and servers, so I am not sure it is very significative.
We are both using OCaml 5.0, so the memory difference is not from there, but rather from the number of threads. You default configuration has 20, mine has 6 (1 to accept, 5 to serve) on a 6 core machines. I did play a bit with the thread parameter and it is not changing things significatively, I kept the default for both software as it seems to be good. I copied your t1.ml test to have the same number of routes in both case.
Hi Christophe, interesting that you mention this now. For various reasons OCaml’s effect implementation does not support this (yet?). The problem is more technical than conceptual though.
I tried various ways, in particular with signals, but never got something working, especially that I want a period around 1ms. This is the only thing missing to have lightweight preemptive threads from effect. If you know a way to do it, even using some C code (I already have some for epoll and eventfd), I would try it.
But I should say while I would probably get a better worst case for simple_httpd (but I am already much better for the worst case that nginx of apache) I would probably loose for the mean or the 50% quantile. But it would be worth trying has it could be a good option to resist DDOS attack exploiting the cooperative aspect of the scheduler.
What have you tried to do exactly? I assumed that you tried to perform an effect from a signal handler, in which case you had noticed that it simply does not work (exception
(The deep technical reason is that OCaml’s effect handlers cannot capture C stack frames, only OCaml stack frames.)
Yes, I try with signals! Probably doing the effect at minor GC or allocation would work ? Although there might not be any allocation ?
Being able to perform an effect from any kind of asynchronous callback would require a major effort in changing the way those are currently implemented in the OCaml runtime, so, no, not any time soon. But this would be possible modulo some caveats and applications of such asynchronous effects have been investigated here: [2307.13795] Higher-Order Asynchronous Effects. It is nice to know that there is concrete interest in this idea.
The timing aspect is a separate concern. Running callbacks predictably after a certain amount of allocations seems to work well (see my work on memprof-limits) and could be used to interrupt threads that run for too long in order to resist DOS attacks.