EBOOK – REDIS IN ACTION

This book covers the use of Redis, an in-memory database/data structure server.

open all | close all

4.4.2 Listing items in the marketplace

In the process of listing, we’ll use a Redis operation called WATCH, which we combine
with MULTI and EXEC, and sometimes UNWATCH or DISCARD. When we’ve watched keys
with WATCH, if at any time some other client replaces, updates, or deletes any keys that
we’ve WATCHed before we have performed the EXEC operation, our operations against
Redis will fail with an error message when we try to EXEC (at which point we can retry
or abort the operation). By using WATCH, MULTI/EXEC, and UNWATCH/DISCARD, we can
ensure that the data that we’re working with doesn’t change while we’re doing something
important, which protects us from data corruption.

WHAT IS DISCARD?In the same way that UNWATCH will let us reset our connection
if sent after WATCH but before MULTI, DISCARD will also reset the connection
if sent after MULTI but before EXEC. That is to say, if we’d WATCHed a key or
keys, fetched some data, and then started a transaction with MULTI followed
by a group of commands, we could cancel the WATCH and clear out any
queued commands with DISCARD. We don’t use DISCARD here, primarily
because we know whether we want to perform a MULTI/EXEC or UNWATCH, so a
DISCARD is unnecessary for our purposes.

Let’s go about listing an item in the marketplace. To do so, we add the item to the
market ZSET, while WATCHing the seller’s inventory to make sure that the item is still
available to be sold. The function to list an item is shown here.

Listing 4.5The list_item() function
def list_item(conn, itemid, sellerid, price):
   inventory = "inventory:%s"%sellerid
   item = "%s.%s"%(itemid, sellerid)
   end = time.time() + 5
   pipe = conn.pipeline()

   while time.time() < end:
      try:
         pipe.watch(inventory)

Watch for changes to the user’s inventory.

         if not pipe.sismember(inventory, itemid):

Verify that the user still has the item to be listed.

            pipe.unwatch()

If the item isn’t in the user’s inventory, stop watching the inventory key and return.

            return None

         pipe.multi()
         pipe.zadd("market:", item, price)
         pipe.srem(inventory, itemid)

Actually list the item.

         pipe.execute()

If execute returns without a WatchError being raised, then the transaction is complete and the inventory key is no longer watched.

         return True

      except redis.exceptions.WatchError:
         pass

The user’s inventory was changed; retry.

   return False

After some initial setup, we’ll do what we described earlier. We’ll tell Redis that we
want to watch the seller’s inventory, verify that the seller can still sell the item, and if
so, add the item to the market and remove the item from their invsentory. If there’s an update or change to the inventory while we’re looking at it, we’ll receive an error and
retry, as is shown by the while loop outside of our actual operation.

Let’s look at the sequence of operations that are performed when Frank (user 17) wants to sell ItemM for 97 e-dollars in figure 4.4.

Figure 4.4list_item(conn, "ItemM", 17, 97)

Generally, listing an item should occur without any significant issue, since only the
user should be selling their own items (which is enforced farther up the application
stack). But as I mentioned before, if a user’s inventory were to change between the
WATCH and EXEC, our attempt to list the item would fail, and we’d retry.

Now that you know how to list an item, it’s time to purchase an item.