WebAssembly / wasi-messaging

messaging proposal for WASI
18 stars 9 forks source link

updated messaging.wit.md, and README.md #2

Closed danbugs closed 1 year ago

danbugs commented 1 year ago

Signed-off-by: danbugs danilochiarlone@gmail.com

danbugs commented 1 year ago

From the discussions in the WASI Meeting from Dec. 1st 2022, a couple of questions came up:

lukewagner commented 1 year ago

Hi, great work on this!

High-level question: are the worlds in the "World Examples" section of messaging.wit.md just example worlds that you could write, but aren't being standardized, or are these worlds being standardized alongside the interfaces in the "Interfaces" section? If the former, maybe replace the "wasi:messaging/*" in their names with an unofficial-sounding kebab-name like my-world so it's a bit more clear. If the latter, maybe remove "Examples" from the section title.

Another question, which may just be my lack of understanding of how these messaging systems work, so sorry in advance for that, but I'm having a hard time wrapping my head around the exported pull interface of the basic/sub-pull and stream/sub-pull worlds. Is the idea that the host calls receive and then kindof just waits for an event, and this gives the component the chance to run to produce events? If the timeout expires and receive returns, what causes the host to call receive again in the future (to let the component run again)? Does it happen continuously or periodically or does the call to receive reflect some external event? Overall, this feels sortof incompatible with the event-based/"serverless" model where the way this would work is that some independent event fires (HTTP request arrives, timer fires, message arrives via push, ...), and then in response to that, the component calls an imported push, pub or sub function. Thus, I wouldn't imagine there even being a pull interface. But maybe receive represents some external event, analogous to these other ones I listed?

Lastly, a few small ideas/nits:

Mossaka commented 1 year ago

Hey @lukewagner , good question! I will leave the first question and the last section of ideas/nits to @danbugs . I will try my bset to answer the second question regarding the pull interface.

Before answer the quesitons, I want to give some background on how message delivery in a messaging system works. From a high level point of view, there are two major message delivery mechanisms between a message broker and a subscriber:push and pull.

The push mechanism allows the host to continuously push messages to the subscriber component to handle. This will mostly match the serverless / function-as-a-service programming model, where the component only need to export a message handler function for the host to invoke.

on_message( event: Event ) -> Result<...>

The on_message will be implemented by the component, exported, and invoked by the host.

The second message delivery mechanism is the pull mechanism. The idea is that the subscriber service is a long running service that periodically pulls / polls the broker to get messages. This actually gives the subscriber service more flexibility and useful if the subscriber has limited resources to process messages.

receive(...) -> Result<Event>

The receive() function will be implemented by the host, imported and invoked by the guest.

You are right that the pull mechanism does not feel right to a serverless model, but it is a wildly used mechanism for subscribers to receive messages from the message broker. It is working much more like an outbound HTTP request, though the request is not necessarily a http reqeust.

lukewagner commented 1 year ago

Thanks a lot for the explanation @Mossaka, that makes sense. The remaining detail I don't understand is why the pull interface is exported by the sub-pull world instead of being imported: for long-running instances, I would expect the export to be something like a main function that didn't take any event-specific parameters.

Next, an observation and a question: I definitely agree that it's important to be able to run code that assumes a traditional long-running-instance model. However, it does seem like there is a way to support this code in the following manner that is also rather serverless- and autoscaling-friendly:

What's nice about this approach is that it avoids having two differently-shaped worlds (for serverless and long-running instances) and a corresponding bifurcation between hosts and components: there's just one world (for a particular set of functionality) and multiple ways to target and support it.

An obvious challenge with this approach is if the long-running guest code doesn't have an event loop but, rather, has non-concurrent, synchronous blocking pull calls. This code can be supported using either core wasm stack-switching or the component model's proposed blocking canon wait. Neither are available today or very soon, though, so that is a downside. But with either of these, such synchronous code can be suspended and resumed mid-stack.

Given all that, do you still see a motivation for defining separate interfaces for long-running instances? I can imagine hypothetical use cases where code might critically depend on having long-running instances (no autoscaling allowed) where a distinct blocking export interface would be necessary to reflect this requirement and, in this case, the component would want, e.g., to import a blocking pull (and probably other interfaces too). But if we don't have those use cases in the short-term, maybe we can punt on them for now and benefit from the increased portability resulting from fewer degrees of freedom?

devigned commented 1 year ago

I agree that the push model is the most natural way to go here, but in the same frame, I can see the desire for non-function kind of folks to depend on it.

I believe the idea of a host holding a queue of messages for the application is a solid one, but would require some elegant work to deal with back pressure and configuration of the message broker, which would ordinarily fall on the consuming application.

In the first edition, rather than making on omnipresent app host, I'd rather suggest cutting pull. I think this would satisfy much of the community and cause the pull folks to speak up, which would drive a better use case from folks who have stake in the outcome.

wdyt?

danbugs commented 1 year ago

Thanks for the feedback, @lukewagner, and @devigned!~ and thanks @Mossaka for addressing Luke's questions, your answers were spot on (:

To answer the remaining questions regarding worlds, and nits:

High-level question: are the worlds in the "World Examples" section of messaging.wit.md just example worlds that you could write, but aren't being standardized, or are these worlds being standardized alongside the interfaces in the "Interfaces" section? If the former, maybe replace the "wasi:messaging/*" in their names with an unofficial-sounding kebab-name like my-world so it's a bit more clear. If the latter, maybe remove "Examples" from the section title.

Thanks a lot for the explanation @Mossaka, that makes sense. The remaining detail I don't understand is why the pull interface is exported by the sub-pull world instead of being imported: for long-running instances, I would expect the export to be something like a main function that didn't take any event-specific parameters.

The first two worlds were there to illustrate that some interfaces (while separate) are intrinsically linked (i.e., sub/basic/pull and sub/stream/pull). Also, I should add that they are written separately in the first place because of the wide-range of use-cases for messaging – some providers only implement streaming pull, while others implement basic pull, and so forth. That said, in retrospect, I don't see much sense in doing an export of pull, so I'll fix that.

For the remaining worlds, I'll make it clearer that they are not being standardized and, instead, are just examples!

To allow efficient batch-processing of events via the push interface, instead of having a separate interface with a stream function, I'd recommend instead adding a second streaming function to wasi:messaging/push that has a stream param type.

I mentioned why I avoided doing this in my previous answer, but, yeah~ For completion, that's hard to do because not every provider that has an on-receive, has a stream-receive.

It might be nice to rename wasi:messaging/push/on-receive to wasi:messaging/handler/handle for symmetry with wasi:http/handler (they're both serving the same purpose, just different message types)?

One world I'd expect to see standardized but isn't quite is: world "wasi:messaging/service" { import pub: "wasi:messaging/pub" import sub: "wasi:messaging/sub" export message-handler: "wasi:messaging/handler" }

I like that, I'll update it!


Regarding the removal of pull-based messaging, I'm okay w/ it, but I think there is most definitely a need for it and it should land somewhere at some point. This might be a good topic to bring up in the upcoming WASI Subgroup Video Meeting for a more in-depth discussion...