Skip to content

improve io flow for midi-redis #2

@romange

Description

@romange

Current design is not optimal. Below are the inefficiencies (both helio and this repo)

  1. Socket interface requires the already allocated buffer (Recv(MutableBytes dest)).
    So for 1K sockets we need to allocate 1K buffers in advance even if most of them are idle.
    Ideally, the design should support buffers for in-flight requests, like what epoll provides.
    Both ioruing and epoll can be designed like this if we invert the control, i.e. instead of
    having Socket::Recv(...) we can implement Socket::OnRecv(Notification not) struct where for
    iouring we may need to rearm it for non-multishot configurations. For iouring/provided buffers
    Notification may provide the already read buffer owned by kernel.
  2. Ambiguous string ownership during parsing. Sometimes arguments are string_views of the original query buffer,
    but sometimes (for pipelines) they are allocated and copied. In general the design favors synchronous execution,
    so that argument lifecycle must persist until the command execution finishes. This prevents implementing the async design for
    the underlying commands.
    Goal: either fully parse the command or fully deplete the socket input buffer - with arguments always allocated on heap.
    This will allow us to parse K commands inside Socket::OnRecv and create small batches of parsed commands that
    could be executed asynchronously. Separate parsing of small batches from the execution.
  3. the connection loop currently blocks on recv and send. This creates multiple problems - we need to juggle two fibers to support
    pubsub and pipelining. and we can not implement asynchronous commands - if the fiber is blocked on recv, it can send replies if the
    synchronous recv is stuck. Instead, we can make the loop to block only on logical conditions like:
     while (true) {
        int opcode = wait_for_recv_or_commandreply_notifications();
        execute(opcode);
      }
    
    with such design we will be able to notify the io-fiber about ready replies or ready input (see (1) ).
  4. Once we have 1-3, we can design an async command execution, where single hop commands send replies to the io fiber without it waiting for them to finish.
  5. With streaming replies we can introduce a more sophisticated reply builder, that takes a queue of already ready replies and sends the serialized data to the socket.
  6. (Needs deeper checks) Introduce key batches as a first grade primitive during the flow of the command execution. So, say we have, if we process a batch of commands, GET x, GET x2 .. GET xn, and x1,x4, x6 go to shard 0; x2, x3, x5 go to shard 1, we are aware of these keys on batch level in the shard threads and can possibly prefetch memory before the execution of each shard callbacks.
  7. With huge pipelines/responses, it is possible that the connection (or even server) execution will be blocked due to a stalled socket: in the worst case, a write is stalled, command execution is stalled , and it's part of the larger (MULT) transaction, then the whole transaction queue is stalled. with async sends we could offload stalled replies to disk allowing the system to progresss or possibly drop connection if it reaches unreasonable sizes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions