The problem/use-case that the feature addresses

When a consumer for a group comes online, it must ensure that the stream and group both exist. The XGROUP CREATE command supports the MKSTREAM option to ensure the stream exists. But if the group already exists, XGROUP CREATE returns an error, which client libraries convert into an exception.

One way to do this in JavaScript is

async function upsertGroup(client, groupName, startID, options) {
  const groupInfo = await client.XINFO_GROUPS(topic)
  if (groupInfo.some(({ name }) => name === groupName)) return

  try {
    await client.XGROUP_CREATE(topic, groupName, startID, options)
  } catch (e) {
    // another client may have created the group since our `XINFO_GROUPS` command
    console.warn(`Error creating group: ${e}`)
  }
}

See https://stackoverflow.com/questions/64370409/how-to-check-consumer-group-already-exists-in-redis

Description of the feature

A way to create a group only if it doesn't exist, all in one command. One option would be an extra argument to XGROUP CREATE:

XGROUP CREATE newstream mygroup $ MKSTREAM UPSERT

or a new subcommand:

XGROUP UPSERT newstream mygroup $ MKSTREAM

Alternatives you've considered

Another way of implementing this would be a Lua script, but it's a little annoying to have to build that for every streams project.

Comment From: itamarhaber

Hello @jamesarosen - thanks for sharing your feedback and making the proposal.

But if the group already exists, XGROUP CREATE returns an error, which client libraries convert into an exception.

True. One should note, however, that the error returned is quite specific: "BUSYGROUP Consumer Group name already exists". It makes detecting and handling this scenario as simple as the JS snippet you've shared above. Also, you don't really need to call INFO, just try creating the group every time a new consumer comes online and wait for success/error.

IIUC your suggestion, the new UPSERT mode will disable the error, possibly returning a value of 0 or 1 to indicate failure or success. While I think that the change is simple enough, I'm not seeing how it provides a better experience/improved functionality, but maybe I'm missing something.

Nitpick: usually the meaning of an UPSERT is to update the data if it already exists, or insert it. In this case, that would mean that the new command/mode would overwrite the group's last delivered ID if the group exists. Of course, this isn't the intended behavior (I should hope), so perhaps a better name for this feature, per the Redis nomenclature, would be NX.

Comment From: jamesarosen

Using exceptions as a way to control non-exceptional behavior is a practice as old as programming, but most folks recommend against it. The C2 Wiki has one of the more famous articles on this.

I also haven’t encountered any other parts of the Redis API that use exceptions for control flow, which makes this stand out as odd.

perhaps a better name for this feature, per the Redis nomenclature, would be NX.

I love that idea! It fits very nicely.

Comment From: renatodex

I will second @jamesarosen , the idea of using exceptions in flow control comes from the compilation/interpreter perspective, since some operations could be more expensive to run by making checks in each loop.

Now If you think in the "usage UX" perspective, many modern tools and frameworks have benefit themselves from the use of upsert-like operations.

While we want to be aware to not create a lot of unnecessary abstraction, this kind of thinking has the potential to increases the adoption of Redis as a modern streaming framework.

Comment From: itamarhaber

If you think in the "usage UX" perspective

@renatodex That's my favorite perspective :)

@jamesarosen you guessed it, I am quite ripe. But even an old dog can learn new tricks, so I'll be doing a 180 and jumping on board with your revised suggestion.

There's still the question of the reply. Personally, I dislike commands that return different reply types, so it may be ok to always return 'OK' when 'NX' is specified.