-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic queue sharding #21
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
# Copyright 2025 SECO Mind Srl | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
defmodule Mississippi.Consumer.AMQPDataConsumer.Starter do | ||
@moduledoc false | ||
use Task, restart: :transient | ||
|
||
alias Horde.DynamicSupervisor | ||
alias Mississippi.Consumer.AMQPDataConsumer | ||
|
||
require Logger | ||
|
||
@restart_backoff :timer.seconds(2) | ||
|
||
def start_link(queues_config) do | ||
Task.start_link(__MODULE__, :start_consumers, [queues_config]) | ||
end | ||
|
||
def start_consumers(queues_config) do | ||
start_consumers(queues_config, 10) | ||
end | ||
|
||
defp start_consumers(_, 0) do | ||
_ = Logger.warning("Cannot start AMQPDataConsumers") | ||
{:error, :cannot_start_consumers} | ||
end | ||
|
||
defp start_consumers(queues_config, retry) do | ||
start_amqp_consumers(queues_config) | ||
|
||
queue_total = queues_config[:total_count] | ||
|
||
child_count = | ||
AMQPDataConsumer.Supervisor |> DynamicSupervisor.which_children() |> Enum.count() | ||
|
||
case child_count do | ||
^queue_total -> | ||
:ok | ||
|
||
_ -> | ||
# TODO: do we want something more refined, e.g. exponential backoff? | ||
backoff_delta = :rand.uniform(@restart_backoff) | ||
Process.sleep(@restart_backoff + backoff_delta) | ||
start_consumers(queues_config, retry - 1) | ||
end | ||
end | ||
|
||
def start_amqp_consumers(queues_config) do | ||
children = amqp_data_consumers_childspecs(queues_config) | ||
|
||
Enum.each(children, fn child -> | ||
DynamicSupervisor.start_child(AMQPDataConsumer.Supervisor, child) | ||
end) | ||
end | ||
|
||
defp amqp_data_consumers_childspecs(queues_config) do | ||
queue_prefix = queues_config[:prefix] | ||
queue_total = queues_config[:total_count] | ||
max_index = queue_total - 1 | ||
|
||
for queue_index <- 0..max_index do | ||
queue_name = "#{queue_prefix}#{queue_index}" | ||
|
||
init_args = [ | ||
queue_name: queue_name, | ||
queue_index: queue_index | ||
] | ||
|
||
{AMQPDataConsumer, init_args} | ||
end | ||
end | ||
end |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,9 +29,29 @@ defmodule Mississippi.Consumer.ConsumersSupervisor do | |
{Registry, [keys: :unique, name: DataUpdater.Registry, members: :auto]}, | ||
{Registry, [keys: :unique, name: MessageTracker.Registry, members: :auto]}, | ||
{Registry, [keys: :unique, name: AMQPDataConsumer.Registry, members: :auto]}, | ||
{DataUpdater.Supervisor, message_handler: message_handler}, | ||
{DynamicSupervisor, strategy: :one_for_one, name: MessageTracker.Supervisor, members: :auto}, | ||
{AMQPDataConsumer.Supervisor, queues_config: queues_config} | ||
{DynamicSupervisor, | ||
strategy: :one_for_one, | ||
name: DataUpdater.Supervisor, | ||
members: :auto, | ||
process_redistribution: :active, | ||
extra_arguments: [message_handler: message_handler], | ||
distribution_strategy: Horde.UniformQuorumDistribution}, | ||
{DynamicSupervisor, | ||
strategy: :one_for_one, | ||
name: MessageTracker.Supervisor, | ||
members: :auto, | ||
process_redistribution: :active, | ||
distribution_strategy: Horde.UniformQuorumDistribution}, | ||
{DynamicSupervisor, | ||
strategy: :one_for_one, | ||
name: AMQPDataConsumer.Supervisor, | ||
members: :auto, | ||
process_redistribution: :active, | ||
distribution_strategy: Horde.UniformQuorumDistribution}, | ||
# This will make queue listeners start after re-sharding in a multi-node cluster | ||
{NodeListener, queues_config}, | ||
# This will make queue listeners start in a single-node cluster | ||
{AMQPDataConsumer.Starter, queues_config} | ||
] | ||
|
||
opts = [strategy: :rest_for_one] | ||
|
@@ -66,16 +86,16 @@ defmodule Mississippi.Consumer.ConsumersSupervisor do | |
], | ||
range_start: [ | ||
type: :non_neg_integer, | ||
default: 0, | ||
doc: """ | ||
The start index of the range of queues that this Mississippi consumer instance will handle. | ||
This option is deprecated and will be ignored. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As these options have been deprecated, I think we should also remove them from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would leave them there until they are removed |
||
""" | ||
], | ||
range_end: [ | ||
type: :non_neg_integer, | ||
default: 127, | ||
doc: """ | ||
The end index of the range of queues that this Mississippi consumer instance will handle. | ||
This option is deprecated and will be ignored. | ||
""" | ||
], | ||
prefix: [ | ||
|
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
# Copyright 2025 SECO Mind Srl | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
||
defmodule NodeListener do | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After IRL discussion, we decided to keep it as is (copyright aside) |
||
@moduledoc false | ||
use GenServer | ||
|
||
alias Mississippi.Consumer.AMQPDataConsumer.Starter | ||
|
||
require Logger | ||
|
||
def start_link(args), do: GenServer.start_link(__MODULE__, args) | ||
|
||
def init(queues_config) do | ||
:net_kernel.monitor_nodes(true, node_type: :visible) | ||
{:ok, queues_config} | ||
end | ||
|
||
def handle_info({:nodeup, node, node_type}, queues_config) do | ||
_ = Logger.info("Node #{inspect(node)} of type #{inspect(node_type)} is up") | ||
_ = Starter.start_consumers(queues_config) | ||
{:noreply, queues_config} | ||
end | ||
|
||
def handle_info({:nodedown, node, node_type}, queues_config) do | ||
_ = Logger.info("Node #{inspect(node)} of type #{inspect(node_type)} is down") | ||
_ = Starter.start_consumers(queues_config) | ||
{:noreply, queues_config} | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly even with some randomness so that we avoid all nodes performing these checks in sync