@@ -44,9 +44,13 @@ defmodule Xandra.Cluster do
44
44
45
45
## Load balancing strategies
46
46
47
- For now, the only load balancing "strategy" implemented is random choice of
48
- nodes: when you execute a query against a `Xandra.Cluster` connection, it will
49
- choose one of connected nodes at random and execute the query on that node.
47
+ For now, there are two load balancing "strategies" implemented:
48
+
49
+ * `:random` - it will choose one of the connected nodes at random and
50
+ execute the query on that node.
51
+
52
+ * `:priority` - it will choose a node to execute the query according
53
+ to the order nodes appear in `:nodes`.
50
54
51
55
## Disconnections and reconnections
52
56
@@ -67,6 +71,8 @@ defmodule Xandra.Cluster do
67
71
* `:underlying_pool` - (module) the `DBConnection.Pool` pool used to pool
68
72
connections to each of the specified nodes.
69
73
74
+ * `:load_balancing` - (atom) load balancing "strategy".
75
+
70
76
To pass options to the underlying pool, you can just pass them alongside other
71
77
options to `Xandra.start_link/1`.
72
78
"""
@@ -76,13 +82,21 @@ defmodule Xandra.Cluster do
76
82
@ behaviour DBConnection.Pool
77
83
78
84
@ default_pool_module DBConnection.Connection
85
+ @ default_load_balancing :random
79
86
80
87
alias __MODULE__ . { ControlConnection , StatusChange }
81
88
alias Xandra.ConnectionError
82
89
83
90
require Logger
84
91
85
- defstruct [ :options , :pool_supervisor , :pool_module , pools: % { } ]
92
+ defstruct [
93
+ :options ,
94
+ :node_refs ,
95
+ :load_balancing ,
96
+ :pool_supervisor ,
97
+ :pool_module ,
98
+ pools: % { } ,
99
+ ]
86
100
87
101
def ensure_all_started ( options , type ) do
88
102
{ pool_module , options } = Keyword . pop ( options , :underlying_pool , @ default_pool_module )
@@ -95,17 +109,22 @@ defmodule Xandra.Cluster do
95
109
96
110
def start_link ( Xandra.Connection , options ) do
97
111
{ pool_module , options } = Keyword . pop ( options , :underlying_pool , @ default_pool_module )
112
+ { load_balancing , options } = Keyword . pop ( options , :load_balancing , @ default_load_balancing )
98
113
{ nodes , options } = Keyword . pop ( options , :nodes )
99
114
{ name , options } = Keyword . pop ( options , :name )
100
115
101
- state = % __MODULE__ { options: Keyword . delete ( options , :pool ) , pool_module: pool_module }
116
+ state = % __MODULE__ {
117
+ options: Keyword . delete ( options , :pool ) ,
118
+ load_balancing: load_balancing ,
119
+ pool_module: pool_module ,
120
+ }
102
121
GenServer . start_link ( __MODULE__ , { state , nodes } , name: name )
103
122
end
104
123
105
124
def init ( { % __MODULE__ { options: options } = state , nodes } ) do
106
125
{ :ok , pool_supervisor } = Supervisor . start_link ( [ ] , strategy: :one_for_one , max_restarts: 0 )
107
- start_control_connections ( nodes , options )
108
- { :ok , % { state | pool_supervisor: pool_supervisor } }
126
+ node_refs = start_control_connections ( nodes , options )
127
+ { :ok , % { state | node_refs: node_refs , pool_supervisor: pool_supervisor } }
109
128
end
110
129
111
130
def checkout ( cluster , options ) do
@@ -132,25 +151,30 @@ defmodule Xandra.Cluster do
132
151
pool_module . stop ( pool_ref , error , state , options )
133
152
end
134
153
135
- def activate ( cluster , address , port ) do
136
- GenServer . cast ( cluster , { :activate , address , port } )
154
+ def activate ( cluster , node_ref , address , port ) do
155
+ GenServer . cast ( cluster , { :activate , node_ref , address , port } )
137
156
end
138
157
139
158
def update ( cluster , status_change ) do
140
159
GenServer . cast ( cluster , { :update , status_change } )
141
160
end
142
161
143
- def handle_call ( :checkout , _from , % __MODULE__ { pools: pools } = state ) do
162
+ def handle_call ( :checkout , _from , % __MODULE__ { } = state ) do
163
+ % { node_refs: node_refs ,
164
+ load_balancing: load_balancing ,
165
+ pool_module: pool_module ,
166
+ pools: pools } = state
167
+
144
168
if Enum . empty? ( pools ) do
145
169
{ :reply , { :error , :empty } , state }
146
170
else
147
- { _address , pool } = Enum . random ( pools )
148
- { :reply , { :ok , state . pool_module , pool } , state }
171
+ pool = select_pool ( load_balancing , pools , node_refs )
172
+ { :reply , { :ok , pool_module , pool } , state }
149
173
end
150
174
end
151
175
152
- def handle_cast ( { :activate , address , port } , % __MODULE__ { } = state ) do
153
- { :noreply , start_pool ( state , address , port ) }
176
+ def handle_cast ( { :activate , node_ref , address , port } , % __MODULE__ { } = state ) do
177
+ { :noreply , start_pool ( state , node_ref , address , port ) }
154
178
end
155
179
156
180
def handle_cast ( { :update , % StatusChange { } = status_change } , % __MODULE__ { } = state ) do
@@ -159,19 +183,26 @@ defmodule Xandra.Cluster do
159
183
160
184
defp start_control_connections ( nodes , options ) do
161
185
cluster = self ( )
162
- Enum . each ( nodes , fn ( { address , port } ) ->
163
- ControlConnection . start_link ( cluster , address , port , options )
186
+ Enum . map ( nodes , fn { address , port } ->
187
+ node_ref = make_ref ( )
188
+ ControlConnection . start_link ( cluster , node_ref , address , port , options )
189
+ { node_ref , nil }
164
190
end )
165
191
end
166
192
167
- defp start_pool ( state , address , port ) do
168
- % { options: options , pool_module: pool_module ,
169
- pools: pools , pool_supervisor: pool_supervisor } = state
193
+ defp start_pool ( state , node_ref , address , port ) do
194
+ % { options: options ,
195
+ node_refs: node_refs ,
196
+ pool_module: pool_module ,
197
+ pool_supervisor: pool_supervisor ,
198
+ pools: pools } = state
199
+
170
200
options = [ address: address , port: port ] ++ options
171
201
child_spec = pool_module . child_spec ( Xandra.Connection , options , id: address )
172
202
case Supervisor . start_child ( pool_supervisor , child_spec ) do
173
203
{ :ok , pool } ->
174
- % { state | pools: Map . put ( pools , address , pool ) }
204
+ node_refs = List . keystore ( node_refs , node_ref , 0 , { node_ref , address } )
205
+ % { state | node_refs: node_refs , pools: Map . put ( pools , address , pool ) }
175
206
{ :error , { :already_started , _pool } } ->
176
207
Logger . warn ( fn ->
177
208
"Xandra cluster #{ inspect ( name ( ) ) } " <>
@@ -190,7 +221,8 @@ defmodule Xandra.Cluster do
190
221
end
191
222
192
223
defp toggle_pool ( state , % { effect: "UP" , address: address } ) do
193
- % { pools: pools , pool_supervisor: pool_supervisor } = state
224
+ % { pool_supervisor: pool_supervisor , pools: pools } = state
225
+
194
226
case Supervisor . restart_child ( pool_supervisor , address ) do
195
227
{ :error , reason } when reason in [ :not_found , :running , :restarting ] ->
196
228
state
@@ -200,8 +232,20 @@ defmodule Xandra.Cluster do
200
232
end
201
233
202
234
defp toggle_pool ( state , % { effect: "DOWN" , address: address } ) do
203
- % { pools: pools , pool_supervisor: pool_supervisor } = state
235
+ % { pool_supervisor: pool_supervisor , pools: pools } = state
236
+
204
237
Supervisor . terminate_child ( pool_supervisor , address )
205
238
% { state | pools: Map . delete ( pools , address ) }
206
239
end
240
+
241
+ defp select_pool ( :random , pools , _node_refs ) do
242
+ { _address , pool } = Enum . random ( pools )
243
+ pool
244
+ end
245
+
246
+ defp select_pool ( :priority , pools , node_refs ) do
247
+ Enum . find_value ( node_refs , fn { _node_ref , address } ->
248
+ Map . get ( pools , address )
249
+ end )
250
+ end
207
251
end
0 commit comments