|
| 1 | +# Discussion Doc: Event Loop |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +We use an event loop to handle various types of events in the network layer. This is a very common design pattern for |
| 6 | +handling asynchronous event. If you'd like to learn more about it some good keywords to search for are: "Event Loop", " |
| 7 | +Event Based Programming", "Event Driven Programming". |
| 8 | + |
| 9 | +## I/O Events |
| 10 | + |
| 11 | +- **Connection Requests**: The main thread will check for incoming connections requests and dispatch them a worker |
| 12 | + thread. |
| 13 | +- **Handling Connections**: The worker threads will listen for incoming queries over the connection, execute them, and |
| 14 | + respond back to the client. There is also a timeout event that will close these connections if there's been no |
| 15 | + activity for a certain amount of time. |
| 16 | + |
| 17 | +## Signal Events |
| 18 | + |
| 19 | +- **SIGHUP**: a SIGHUP signal will terminate the server process. |
| 20 | + |
| 21 | +## Timeout Events |
| 22 | + |
| 23 | +- **Handling Connections**: Idle connections will be terminated after a certain amount of time. This is tightly coupled |
| 24 | + with the I/O event related to handling connections. |
| 25 | + |
| 26 | +## Manual/Async Events |
| 27 | + |
| 28 | +These events are manually triggered by our code. |
| 29 | + |
| 30 | +- **Terminate Conection**: When connections are terminated a manual event is triggered to close the connection and clean |
| 31 | + up all associated resources. |
| 32 | + |
| 33 | +## Libevent vs Libev |
| 34 | + |
| 35 | +**Libevent**: https://libevent.org/ |
| 36 | + |
| 37 | +**Libev**: http://software.schmorp.de/pkg/libev.html |
| 38 | + |
| 39 | +Currently we use Libevent as our Event Loop implementation. We have also looked into using Libev and may transition to |
| 40 | +Libev in the future. Below is a comparison between the two libraries. |
| 41 | + |
| 42 | +### Language |
| 43 | + |
| 44 | +Libevent is implemented in C. Libev is implemented in C, but has wrapper C++ classes and APIs. |
| 45 | + |
| 46 | +### Usage |
| 47 | + |
| 48 | +Libevent is more "one size fits all" compared to libev. There is a single event type that can watch for any kind of |
| 49 | +event. There is also a single event loop type that you can start and stop on a per thread basis. Libev on the other hand |
| 50 | +has a different event type for every kind of event. So if you want a single event to watch for two things (like an I/O |
| 51 | +event and a timer event) at the same time, then you'll have to create your own custom composite event. Additionally |
| 52 | +Libev has two different event loop types. One "default" event loop, which there can only ever be one of, and another |
| 53 | +"non-default" event loop, which there can be any number of. In practice this isn't such a huge deal, for example in |
| 54 | +NoisePage the connection dispatcher thread would be the default event loop while all the connection handling threads |
| 55 | +would be non-default event loops. |
| 56 | + |
| 57 | +This makes libevent easier to use than libev, however it ends up using more memory and according the designer of libev |
| 58 | +it makes libevent |
| 59 | +slower. [This Stack Overflow post](https://stackoverflow.com/questions/9433864/whats-the-difference-between-libev-and-libevent) |
| 60 | +was answered by the creator of libev and describes the differences in design philosophy. |
| 61 | + |
| 62 | +### Performance |
| 63 | + |
| 64 | +Running the tpcc and noop oltpbenchmarks against the current master of NoisePage and an implementation that switches |
| 65 | +from libevent to libev for 60 seconds, we get the following results (Note: each benchmark was run twice): |
| 66 | + |
| 67 | +| Libev vs Libevent | Benchmark | Mean Latency (milliseconds) | Throughput (requests/second) |
| 68 | +| --- | --- | --- | --- | |
| 69 | +| Libev | NoOp | 50.1565409803912 | 3398.930926973485 | |
| 70 | +| Libev | NoOp | 50.15175110158765 | 3173.42297782733 | |
| 71 | +| Libevent | NoOp | 50.14538746950166 | 3231.0416656357247 | |
| 72 | +| Libevent | NoOp | 50.294910261202624 | 3254.812815029675 | |
| 73 | +| Libev | TPCC | 1.325 | 753.367 | |
| 74 | +| Libev | TPCC | 1.396 | 714.883 | |
| 75 | +| Libevent | TPCC | 1.295 | 770.867 | |
| 76 | +| Libevent | TPCC | 1.344 | 743.033 | |
| 77 | + |
| 78 | +These were run on dev8.db.pdl.local.cmu.edu. There doesn't seem to be a clear winner between the two from these results. |
| 79 | + |
| 80 | +Libev has it done it's own benchmarking comparing itself against libevent that can be found |
| 81 | +[here](http://libev.schmorp.de/bench.html). According to these result Libev only starts being noticeably faster when |
| 82 | +there are 10s of thousands of watched file descriptors, and the difference is measure in microseconds and nanoseconds. |
| 83 | +So it makes sense that we didn't notice a difference in the OLTP Benchmarks which is measured in milliseconds and don't |
| 84 | +have enough file descriptors. |
| 85 | + |
| 86 | +### NoisePage POC Implementation |
| 87 | +[Joe Koshakow's Libev Implementation](https://github.com/jkosh44/noisepage/tree/libev-666) |
0 commit comments