0

I'm working on a backend migration on some scala code. The code current returns com.twitter.util.Future[A] and I am taking the response from my new backend and transforming it into Future[A] as well

To monitor the migration, I want to do a comparison so I can tell how often the response from the 2 backends differ, so I have something like

val b1Resp: Future[A] = getResponseFromBackend1()
val b2Resp: Future[A] = getResponseFromBackend2() map { resp => convert(resp)}

return b1Resp

I want to verify that the results of the futures are either the same or that they both fail. On discrepancies, I will be publishing a metric.

Moreover, I don't want to delay the return. I don't really care when the metric is published (within reason of course), so it's okay to allow execution to continue

How do I do the comparison?

DFL
  • 53
  • 8
  • 1
    I want to make sure that I understood correctly, so you want to make sure that the results are the same, and you want to also benchmark the two methods and compare them? – AminMal Jul 29 '22 at 22:09
  • I don't need to benchmark. I just want to make sure the results are the same, including if they fail. I don't want to delay the return (eg, I don't want to wait for the futures to complete in this method) as it will impact the overall performance – DFL Jul 29 '22 at 22:38
  • 1
    I'm not familiar with the Twitter `Future`s but looking at the documentation you can: use `map` and `handle` methods to wrap the result in a `Try` (effectively getting `Future[Try[A]]` for each response), then use a for comprehension to combine the results into a tuple (`Future[(Try[A], Try[A])]`) and finally use `addEventListener` to execute a callback recording the metric when the result `Future` completes. – Ava Jul 29 '22 at 22:59
  • Ah, thanks! I think that will work. It looks like Twitter's Future even has a built in liftToTry method to help with this. Thanks again! – DFL Jul 30 '22 at 00:01

1 Answers1

1

The broad outline of what you want to do is

{
  val b1Resp: Future[A] = getResponseFromBackend1()
  val b2Resp: Future[A] = getResponseFromBackend2() map { resp => convert(resp)}

  Future.join(b1Resp, b2Resp)  // combine the two futures into a future of a pair of their values
    .foreach {                 // when both are complete...
      case (b1, b2) =>
        // compare and tickle a metric or whatever
        ()  // explicit Unit value, in case the compiler warns about discarding a value
    }

  b1Resp  // return should generally be avoided in Scala
}

Note that Future.join.foreach just arranges for the comparison etc. to happen in the future; it should only add a few microseconds to the block.

For standard library futures, the equivalent to Future.join.foreach is (assuming an implicit ExecutionContext is in scope):

b1Resp.zip(b2Resp)
  .foreach {
    // as above
  }
Levi Ramsey
  • 18,884
  • 1
  • 16
  • 30
  • Worth noting that in both of these, the comparison will only happen if both futures succeed. One option for comparing failures would be to have a nested `rescue` (Twitter) or `recoverWith` (standard library) between the `join`/`zip` and the `foreach`, though this would mean that the `foreach` would now see an `(Any, Any)`. – Levi Ramsey Jul 30 '22 at 20:27