58 lines
1.7 KiB
Elixir
58 lines
1.7 KiB
Elixir
defmodule Towerops.Workers.PollingOffset do
|
|
@moduledoc """
|
|
Calculates deterministic polling offsets to distribute load across time.
|
|
|
|
Uses erlang's phash2 to generate stable offsets from device IDs,
|
|
ensuring each device polls at a different time within the interval.
|
|
|
|
This prevents the "thundering herd" problem where all devices poll
|
|
at synchronized clock intervals (e.g., 1:00, 1:05, 1:10), creating
|
|
load spikes on the server.
|
|
|
|
## Examples
|
|
|
|
# Device with ID "abc-123" and 300-second interval
|
|
iex> calculate_offset("abc-123", 300)
|
|
94 # Device polls at :01:34, :06:34, :11:34, etc.
|
|
|
|
# Same device always gets same offset
|
|
iex> calculate_offset("abc-123", 300)
|
|
94
|
|
|
|
# Different device gets different offset
|
|
iex> calculate_offset("def-456", 300)
|
|
171 # Device polls at :02:51, :07:51, :12:51, etc.
|
|
"""
|
|
|
|
@doc """
|
|
Calculate polling offset for a device.
|
|
|
|
Returns an offset in seconds between 0 and interval_seconds-1.
|
|
Same device_id always returns same offset for a given interval.
|
|
|
|
## Parameters
|
|
|
|
* `device_id` - The device UUID (binary or string format)
|
|
* `interval_seconds` - The polling interval in seconds
|
|
|
|
## Returns
|
|
|
|
An integer offset in the range [0, interval_seconds)
|
|
|
|
## Examples
|
|
|
|
iex> calculate_offset("abc-123", 300)
|
|
94
|
|
|
|
iex> calculate_offset("abc-123", 300)
|
|
94 # Always returns same value for same device
|
|
|
|
iex> offset = calculate_offset("some-uuid", 60)
|
|
iex> offset >= 0 and offset < 60
|
|
true
|
|
"""
|
|
@spec calculate_offset(binary(), pos_integer()) :: non_neg_integer()
|
|
def calculate_offset(device_id, interval_seconds) do
|
|
device_id |> :erlang.phash2() |> rem(interval_seconds)
|
|
end
|
|
end
|