Greyboi
The Infinite Machine
6 min readAug 4, 2017

--

Note: You should now use the python package https://pypi.org/project/im-debouncedtask/ to use @debouncedtask. The repo is here: https://github.com/emlynoregan/im_debouncedtask

Rage Alert and @debouncedtask

A Trump Rage Tweet Early Warning System using @debouncedtask

So your boss calls you into the office on Monday morning. He says “hey minion, I’ve got a job for you that you’re gonna love.”

oh no

“So,” he continues, “as you know, our Washington DC toilet paper business is highly volatile at the moment. Sales spike whenever Trump rage tweets.”

Brown Pants are Back!

“We want you to build a system to monitor Trump’s tweets. If he tweets, and the tweets seem angry, I need it to send an email to our CEO.”

“Ok. How big is his inbox? Because that could be a serious amount of emails.”

Your boss startles in alarm. “Oh no, no no. The last person that sent the CEO too many emails was fired immediately. I wouldn’t send him more than one every couple of hours, with a summary of the raging tweets.”

What are all these memos in the Outlook, computer man?

Resisting the temptation to roll your eyes, you leave wondering about how you’re going to do this.

“Oh, and it’s got to be running by this afternoon.”

After a quick spot of emergency product testing, you’re back at your desk. You grab a sheet of the company’s finest and sketch out a quick architecture:

What do you think I am, made of napkins?

Ok. If you can get an App Engine app running quickly, it can receive tweets on a webhook, send them to a sentiment analysis back end, and then if they seem angry, alert the CEO.

You start with a Flask web handler like this:

def GetTweetText(rawtweet):
... extract the text from the rawtweet and return ...
def IsAngry(tweettext):
... send to sentiment analysis and return boolean ...
def SendSummaryToCEO():
... summarize Trump's recent tweets and ...
... send to *gulp* CEO ...
... you sure you want to do that? ...
@app.route('/tweetstorm')
def report():
#1: Get the tweet from the querystring
tweet = request.args.get('tweet')
if IsAngry(tweet):
#if it's time to send the email (???)

SendSummaryToCEO(summary)

Ok, you can bash out everything here. But how do you handle the timing requirement?

What you need is a way to debounce the SendSummaryToCEO() function.

You can do that with the newfangled @debouncedtask for App Engine. It’ll look like this:

from taskutils.debouncedtask import debouncedtask@debouncedtask(initsec=60, repeatsec=7200)
def SendSummaryToCEO():
... summarize Trump's recent tweets and ...
... send to CEO ...
... safe to do because we're debounced ...

So how’s that work??

@debouncedtask

This is a decorator method available in the appenginetaskutils library presented in this article:

It uses @task to run a function in a separate task (ie: another process, maybe on another machine).

It takes all the same arguments as @task, but with these extra arguments available:

  • initsec: A minimum initial period to wait in seconds before calling the function. Defaults to zero. In the example above, it’s set to 60 seconds; we always wait at least 60 seconds after processing a tweet before sending a summary so there’s time to accumulate a few more tweets in our summary.
  • repeatedsec: The period to wait between subsequent invocations of the method. This defaults to 10 seconds, but in the example it’s set to 7200 = 2 * 60 * 60, or 2 hours. Don’t upset that CEO!
  • debouncename: This is a string, and all debounced functions sharing the same debouncename will share the same debouncing state (next/last time run). You can probably ignore this; a debouncename is generated from a function object by serialising it (including arguments and closure) then generating a cryptographic hash from the serialised form.

Something to know about debouncedtask; it’ll always try to run at least once after any call, between initsec and repeatedsec seconds from when it is called. You can think of it as a smart delay.

Note that it’s not a synchronisation primitive or a critical section; your code could still be re-entrant (ie: two calls could overlap), for instance if your code runs for longer than repeatedsec, or if its scheduling data is evicted from memcache (see below).

I wont go into depth on how this works in this article. If you want to check out the code, have a look here on github:

Unfortunately you could still be fired, because memcache!

The CEO can still receive too many emails!

Something to know about @debouncedtask is that it uses memcache to track the schedule of when to run. While this is transactionally valid (it uses compare-and-set), memcache can throw out values (or be flushed!) at any time. In the case of @debouncedtask this will have the effect of causing the next call to the function to run right now, regardless of when it was last run (because that information was thrown out).

You shouldn’t think of the debouncing as a guarantee, but as an almost guarantee. Like this:

@debounce* your function!

*-almost definitely

I’ve used memcache for debouncing because it costs nothing to use; this wont add any overhead to your application’s billing. I could have used datastore objects & transactions to manage debouncing, but that could be bad.

How bad could the cost be, from just transactionally accessing one datastore object? Consider the following:

How many tweets can an angry orange man send in any unit of time? It probably depends on how angry he is. I propose is it O(r), where r is the raw rage being experienced by the president.

O(r) is fake news! Sad.

From all available evidence, r is unbounded. So, tweets per second is also unbounded. Imagine a near future scenario where the grand jury is investigating the man’s business dealings, hauling in everyone over 30 who ever lived in New York City to testify about The Donald. He, in turn, enlists a (russian?) bot farm to begin spewing out rage tweets at DDOS levels.

Maybe Zapier will protect you. But maybe not. When that tweetpocalypse hits, you don’t want to be paying for datastore reads on every tweet you have to process. Instead, you want them discarded in bulk as quickly & cheaply as possible.

@debouncedtask should cope with pretty much any load you throw at it, which is most of the point of debouncing after all. The world can get noisy and ragey, but you can stay cool under pressure.

Got more questions about @debouncedtask? Yell out, I’m happy to chat. But not too much, or I will debounce you.

--

--

Greyboi
The Infinite Machine

I make things out of bits. Great and terrible things, tiny bits.