A quick code demo on how to create a deferred response gateway within a q/KDB+ environment. In this demo, we will be sending multiple asynchronous messages to multiple q processes and then retrieving the result from all downstream processes. *Note: This does not use the internal operator -30!*

2021-12-07 ryanmcfarland

In some situations, when a user executes a query on a gateway, we want to be able to send multiple requests to multiple servers at the same time. If we were doing this via the usual synchronous method, we would have to send and retrieve a result one after another. This can be costly if the user request is querying multiple large DB's at the same time. We can get around this problem by using deferred response messaging.

KDB Deferred Async Diagram

Simple Deferred Response Diagram

The gateway (GW) can send the same command to both DBs in parallel. We can then force the GW to wait for all responses to return from all DBs. This allows DB1 and DB2 to perform work at the same time, in parallel to each other. Once both DB's have finished their request and all responses have been collected by the GW will the GW then proceed to do it's own work.

Deferred Response Demo

Our GW code below, no functions need to be loaded into our DBs.

.gw.handles:hopen 5001 5002;

// Function that gets sent from the GW to the DB, result gets returned via neg[.z.w]
.gw.async:{ neg[.z.w] @[value;x;{(0b;x)}]};

// Send q (input query) and .gw.async to connected handle 
.gw.send:{[h;q] neg[h] (.gw.async;q)};

// Wait to collect from whatever handles we sent our intial query too
.gw.receive:{[h](enlist h)!enlist @[h;::;{"error: ",x}]};

// GW API that the user will call
.gw.query:{[qry]
    .gw.send[;qry] each .gw.handles;
    r:raze .gw.receive each .gw.handles;
    :r;
    };

When we execute this example command, .gw.query["2+2"], the GW will send the query string asynchronously to each of our opened handles. Alongside the query string, we also send the gateway function .gw.async.

.gw.async is how we get our result returned. In this function, we will try to perform the query string using the command value and then attempt to resend the result back asynchronous using the internal dotz variable for the incoming handle (.z.w).

Back on the GW, once we have sent off both commands, we try and collect all results using .gw.receive and once we have collected the results from each DB, we return the result to the client.

Testing

I have three processes launched with the above code block initialized at start-up:

  1. GW | q gw.q
  2. DB1 | q -p 5001 -proc 5001
  3. DB2 | q -p 5002 -proc 5002
q).gw.query "2+2"
3| 4
4| 4
q).gw.query ".z.x"
3| "-proc" "5001"
4| "-proc" "5002"

You can also test the speed of one of the processes by loading this bit of code into one DB process just to observe how the GW process reacts to the delay.

.z.ps:{show x;system "sleep 5";value x;}

Conclusion

I hope this helps anyone and if you have any questions, don't hesitate to reach out via: Contact Me