hakarabakara

hakarabakara

Help required - avoid sending duplicate messages

I have a strange situation for which I need your thoughts.

My application sends messages to users on request from a different server. For some reason which we’re yet to identify, the server would bombard my application to send same message to users instead of a single request.

I wanted to reduce the problem of annoying customers in case this unfortunate scenario happens since the other team hasn’t figured out why that happens yet.

This is what I came up with:

# Let's check whether message with the same fingerprint exits within the last 5 minutes.
    # If so, send incident reminder to team and log
    msg = :base64.encode("#{message}:#{to}:#{provider}:#{app}") |> MyApp.Bucket.find_message

     cond do
      #If we don't have the message fingerprint or similar message send within last 5 minutes
      msg == nil ->
        #Add new message in the bucket
        # New conversation, save particulars in the database
        MyApp.Bucket.create_message(%MyApp.Bucket{
          id: :base64.encode("#{message}:#{to}:#{provider}:#{app}"),
          provider: provider,
          app: @utilities.get_app_value(app, "name"),
          recipients: to,
          attempts: 1,
          message: message
        })
       
        #Send message
        process_message_sending(message, to, provider, app)

      true ->
        case to_string(msg.message) == message and DateTime.diff(DateTime.from_naive!( NaiveDateTime.utc_now(),"Etc/UTC"), DateTime.from_naive!(msg.updated_at,"Etc/UTC"), :second) < 300 do
          true ->
              "Multiple request to send received for message id #{msg.id}  with #{msg.attempts+1} attempts so far"  |> IO.inspect
          _ ->
            process_message_sending(message, to, provider, app)
        end
        msg
          |> Map.replace!(:attempts, msg.attempts+1)
          |> MyApp.Bucket.update_message()
    end

The idea is to save every outgoing message in a bucket and cross-check with next request to send out message to the same recipient. If similar message has been sent within the last five minutes, it is meant to ignore.

The challenge with this approach is that, if I perform a load test, I’m only around 70% successful at stopping the messages. It looks like there’s a delay in persistence into the Mnesia Bucket wrapped by memento is a tad slow.

Any idea how this can be addressed?

I’ve also thought about rate limiting through nginx but I’ve never done it before and not sure how to successfully use it in conjunction with my code

Most Liked

dominicletz

dominicletz

Creator of Elixir Desktop

@hakarabakara you could use a debouncer for this as well. E.g. if you want this event only to sent max. once per five minutes:

    # Creating the message key to debounce
    key = {message, to, provider, app}

    #Send message
    Debouncer.immediate2(key, fn -> 
      process_message_sending(message, to, provider, app)
    end, 5*60_000)

This does not help you logging an error message, but it will mute any duplicated message within 5 minutes. There are different debouncer behaviours. immediate2() will not trigger a second message within the time limit. So if there is a message after 4 minutes it will not be executed at all, but another new message after 6 minutes would be delivered again.

Memory usage. Thought to highlight this. The key is the full message term {message, to, provider, app} this will work fine since the debouncer is using an ETS table internally but if your message is a very big value it might consume a lot of memory, you could limit this with using a hash of the message itself:

key = {:erlang.phash2(message), to, provider, app} or
key = {:crypto.hash(:sha256, message), to, provider, app} if message is a binary

To start using the debouncer just add it to your deps and your extra_applications like this:

  def deps do
    [
      {:debouncer, "~> 0.1"}
    ]
  end

  # and the app
  def application do
    [
      mod: {Your.Application, []},
      extra_applications: [:debouncer]
    ]
  end
hakarabakara

hakarabakara

Thank you @mindok and @dominicletz for your input. I ended up using Cachex because of the ability to slide the expiry which Debouncer didn’t seem to have even though it looked like a nifty solution nonetheless.

I also took your input on hashing the key @dominicletz.

hakarabakara

hakarabakara

My application is a bot so use mnesia to track Id’s of conversation especially if the app is restarted after an update. We used in-memory ETS storage for this but we had to look for something that persists to disk.

It also runs on a single node at the moment.

We’re talking about 100s messages depending how much was available on the account. Let me read about cachex

Where Next?

Popular in Questions Top

chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID&lt;0.412.0&gt; terminating ** (Postgrex.Error) FATAL...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New

Other popular topics Top

albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement