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