Sebastien Lorber
Sebastien Lorber

Reputation: 92210

Throttle or debounce method calls

Let's say I have a method that permits to update some date in DB:

def updateLastConsultationDate(userId: String): Unit = ???

How can I throttle/debounce that method easily so that it won't be run more than once an hour per user.

I'd like the simplest possible solution, not based on any event-bus, actor lib or persistence layer. I'd like an in-memory solution (and I am aware of the risks).

I've seen solutions for throttling in Scala, based on Akka Throttler, but this really looks to me overkill to start using actors just for throttling method calls. Isn't there a very simple way to do that?

Edit: as it seems not clear enough, here's a visual representation of what I want, implemented in JS. As you can see, throttling may not only be about filtering subsequent calls, but also postponing calls (also called trailing events in js/lodash/underscore). The solution I'm looking for can't be based on pure-synchronous code only.

Upvotes: 4

Views: 1122

Answers (3)

Haspemulator
Haspemulator

Reputation: 11318

This sounds like a great job for a ReactiveX-based solution. On Scala, Monix is my favorite one. Here's the Ammonite REPL session illustrating it:

import $ivy.`io.monix::monix:2.1.0` // I'm using Ammonite's magic imports, it's equivalent to adding "io.monix" %% "monix" % "2.1.0" into your libraryImports in SBT

import scala.concurrent.duration.DurationInt
import monix.reactive.subjects.ConcurrentSubject
import monix.reactive.Consumer
import monix.execution.Scheduler.Implicits.global
import monix.eval.Task

class DbUpdater {
  val publish = ConcurrentSubject.publish[String]
  val throttled = publish.throttleFirst(1 hour)
  val cancelHandle = throttled.consumeWith(
    Consumer.foreach(userId =>
      println(s"update your database with $userId here")))
    .runAsync

  def updateLastConsultationDate(userId: String): Unit = {
    publish.onNext(userId)
  }

  def stop(): Unit = cancelHandle.cancel()
}

Yes, and with Scala.js this code will work in the browser, too, if it's important for you.

Upvotes: 5

radumanolescu
radumanolescu

Reputation: 4161

Since you ask for the simplest possible solution, you can store a val lastUpdateByUser: Map[String, Long], which you would consult before allowing an update

if (lastUpdateByUser.getOrElse(userName, 0)+60*60*1000 < System.currentTimeMillis) updateLastConsultationDate(...)

and update when a user actually performs an update

lastUpdateByUser(userName) = System.currentTimeMillis

Upvotes: 1

David Weiser
David Weiser

Reputation: 5205

One way to throttle, would be to maintain a count in a redis instance. Doing so would ensure that the DB wouldn't be updated, no matter how many scala processes you were running, because the state is stored outside of the process.

Upvotes: 0

Related Questions