etaty / rediscala

Non-blocking, Reactive Redis driver for Scala (with Sentinel support)
Apache License 2.0
790 stars 142 forks source link

How to check if watch operation fails? #125

Closed tpraizler closed 8 years ago

tpraizler commented 8 years ago

I am not sure if this is the place to ask, but I hope it is.

I am trying to figure out how to use your implementation for the watch operation. I want to add keys to redis with timeout, and make sure those keys are added atomically, I am trying to achieve this using the watch operation, but I don't understand I can I know if a transaction failed. It make sens to me to map the exec future to a result, and query that result, while the examples show how to wait until the operations are done.

So this is the code that works as expected:

val redisTransaction = redisClient.transaction()
redisTransaction.watch("key")
redisClient.set("key", "abcValue123").flatMap(x => redisTransaction.set("key", "abcValue"))
val execFuture = redisTransaction.exec()

val getResult = Await.result(redisClient.get[String]("key"), 30 seconds)
println(s"Value: $getResult!") //print - Some(abcValue123)
val exec = Await.result(execFuture, 30 seconds)
println(s"Exec result: $exec!") //print - MultiBulk(Some(Vector()))

So after the creation of the transaction I tried to edit the key, and indeed the transaction did not completed. and the final value is "abcValue123"

The following code is the problematic one, I am trying to set a value as part of teh transaction and then flatmap that future and set this key to another value, I expect this to fail also, but it doesn't:

val redisTransaction = redisClient.transaction()
redisTransaction.watch("key")
redisTransaction.set("key", "abcValue").flatMap(x => redisClient.set("key", "abcValue123"))
val execFuture = redisTransaction.exec()

val getResult = Await.result(redisClient.get[String]("key"), 30 seconds)
println(s"Value: $getResult!") //print - Some(abcValue)
val exec = Await.result(execFuture, 30 seconds)
println(s"Exec result: $exec!") //print - MultiBulk(Some(Vector(OK)))

What am I missing here?

etaty commented 8 years ago

yes it's the right place to ask.

redisTransaction.set("key", "abcValue") return a future completed once the transaction is completed. So In the second code block, if for example you do

//watch
...
val r = redisTransaction.set("key", "abcValue").flatMap(x => redisClient.set("key", "abcValue123"))
await { r }
...
//exec()

Then you will wait indefinitely as you don't have executed the transaction yet.

About transaction, you need to know that the watch and other command of the transaction are not sent to the redis server until you send exec. (Use the scala console and redis-cli monitor to see the how it behave)

Now I advice you to use Lua scripts instead of transactions, it's more powerful and the idea of atomicity is already in.

etaty commented 8 years ago

Also you can read other issue related to transaction (they might help you understand it) https://github.com/etaty/rediscala/search?q=transaction&type=Issues&utf8=%E2%9C%93

tpraizler commented 8 years ago

OK! this was super helpful! thanks! I am definitely going to pay a visit with Lua scripts (Redis is new to me).

But was wandering, in this specific case, my only requirement is to add keys atomically with timeout, transaction looks like a good enough solution?

So something like this?

val redisTransaction = redisClient.transaction()
redisTransaction.watch("key")
val get = redisTransaction.get("key")
val set = redisTransaction.set("key", "", Some(20))
redisTransaction.exec()

val result = for{
  g <- get
  s <- set
}yield {
  g.isDefined
}

val exec = Await.result(result, 30 seconds)
println(s"Exec result: $exec!") //should print false the first time and true the second. 
etaty commented 8 years ago

yeah it should work here for example, you can't reuse the returned value of get into the set, you have to use lua script for that. For example, you can't do an increment

tpraizler commented 8 years ago

Awesome! thanks!!