dynamo-autoscale 0.2.9 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -0
- data/bin/dynamo-autoscale +4 -1
- data/lib/dynamo-autoscale/actioner.rb +14 -1
- data/lib/dynamo-autoscale/cw_poller.rb +16 -5
- data/lib/dynamo-autoscale/dispatcher.rb +11 -6
- data/lib/dynamo-autoscale/dynamo_actioner.rb +42 -2
- data/lib/dynamo-autoscale/poller.rb +17 -14
- data/lib/dynamo-autoscale/version.rb +1 -1
- metadata +1 -1
data/CHANGELOG
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
v0.3
|
2
|
+
|
3
|
+
- Added backdating on startup. When dynamo-autoscale starts up, it now fetches 6
|
4
|
+
hours of data for each table then starts polling.
|
5
|
+
- Added a check before actioning that makes sure CloudWatch and DynamoDB agree
|
6
|
+
on what the current provisioned throughput is. Fixes a bug where downscales
|
7
|
+
were happening too frequently.
|
8
|
+
|
1
9
|
v0.2.9
|
2
10
|
|
3
11
|
- Fixed a problem where scale events weren't being showed in the scale report
|
data/bin/dynamo-autoscale
CHANGED
@@ -33,5 +33,8 @@ unless DynamoAutoscale.config[:dry_run]
|
|
33
33
|
DynamoAutoscale.actioner_class = DynamoAutoscale::DynamoActioner
|
34
34
|
end
|
35
35
|
|
36
|
-
DynamoAutoscale.logger.info "Finished setup.
|
36
|
+
DynamoAutoscale.logger.info "Finished setup. Backdating..."
|
37
|
+
DynamoAutoscale.poller.backdate
|
38
|
+
|
39
|
+
DynamoAutoscale.logger.info "Starting polling loop..."
|
37
40
|
DynamoAutoscale.poller.run
|
@@ -245,13 +245,26 @@ module DynamoAutoscale
|
|
245
245
|
return result
|
246
246
|
end
|
247
247
|
|
248
|
+
def pending_reads?
|
249
|
+
!!@pending[:reads]
|
250
|
+
end
|
251
|
+
|
252
|
+
def pending_writes?
|
253
|
+
!!@pending[:writes]
|
254
|
+
end
|
255
|
+
|
256
|
+
def clear_pending!
|
257
|
+
@pending[:writes] = nil
|
258
|
+
@pending[:reads] = nil
|
259
|
+
end
|
260
|
+
|
248
261
|
def should_flush?
|
249
262
|
if @opts[:group_downscales].nil?
|
250
263
|
logger.info "[flush] Downscales are not being grouped. Should flush."
|
251
264
|
return true
|
252
265
|
end
|
253
266
|
|
254
|
-
if
|
267
|
+
if pending_reads? and pending_writes?
|
255
268
|
logger.info "[flush] Both a read and a write are pending. Should flush."
|
256
269
|
return true
|
257
270
|
end
|
@@ -3,10 +3,21 @@ module DynamoAutoscale
|
|
3
3
|
include DynamoAutoscale::Logger
|
4
4
|
INTERVAL = 5.minutes
|
5
5
|
|
6
|
-
def
|
7
|
-
|
8
|
-
|
6
|
+
def backdate
|
7
|
+
now = Time.now.utc
|
8
|
+
|
9
|
+
@tables.each do |table_name|
|
10
|
+
table = DynamoAutoscale.tables[table_name]
|
11
|
+
dispatch(table, Metrics.all_metrics(table_name, {
|
12
|
+
period: 5.minutes,
|
13
|
+
start_time: now - 6.hours,
|
14
|
+
end_time: now,
|
15
|
+
}))
|
9
16
|
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def poll tables, &block
|
20
|
+
tables = AWS::DynamoDB.new.tables.to_a.map(&:name) if tables.nil?
|
10
21
|
|
11
22
|
loop do
|
12
23
|
# Sleep until the next interval occurrs. This calculation ensures that
|
@@ -24,7 +35,7 @@ module DynamoAutoscale
|
|
24
35
|
logger.debug "[cw_poller] Beginning CloudWatch poll..."
|
25
36
|
now = Time.now
|
26
37
|
|
27
|
-
tables.each do |
|
38
|
+
tables.each do |table_name|
|
28
39
|
# This code will dispatch a message to the listening table that looks
|
29
40
|
# like this:
|
30
41
|
#
|
@@ -39,7 +50,7 @@ module DynamoAutoscale
|
|
39
50
|
#
|
40
51
|
# There may also be :provisioned_reads and :provisioned_writes
|
41
52
|
# depending on how the CloudWatch API feels.
|
42
|
-
block.call(
|
53
|
+
block.call(table_name, Metrics.all_metrics(table_name, {
|
43
54
|
period: 5.minutes,
|
44
55
|
start_time: now - 20.minutes,
|
45
56
|
end_time: now,
|
@@ -33,13 +33,18 @@ module DynamoAutoscale
|
|
33
33
|
block.call(table, time, datum) if block
|
34
34
|
|
35
35
|
if @last_check[table.name].nil? or @last_check[table.name] < time
|
36
|
-
if
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
if time > 20.minutes.ago # Too young to vote!
|
37
|
+
if DynamoAutoscale.actioners[table].can_run?
|
38
|
+
logger.debug "[dispatcher] Checking rules..."
|
39
|
+
DynamoAutoscale.rules.test(table)
|
40
|
+
@last_check[table.name] = time
|
41
|
+
else
|
42
|
+
logger.debug "[dispatcher] Skipped rule check, table is not ready " +
|
43
|
+
"to have its throughputs modified."
|
44
|
+
end
|
40
45
|
else
|
41
|
-
logger.debug "[dispatcher] Skipped rule check,
|
42
|
-
"
|
46
|
+
logger.debug "[dispatcher] Skipped rule check, data point was from " +
|
47
|
+
"more than 20 minutes ago. Too young to vote!"
|
43
48
|
end
|
44
49
|
else
|
45
50
|
logger.debug "[dispatcher] Skipped rule check, already checked for " +
|
@@ -14,17 +14,57 @@ module DynamoAutoscale
|
|
14
14
|
:write_capacity_units
|
15
15
|
end
|
16
16
|
|
17
|
-
|
17
|
+
if throughput_synced?
|
18
|
+
dynamo_scale(aws_throughput_key => value)
|
19
|
+
else
|
20
|
+
# If the throughputs were not synced, the likelihood is we made the
|
21
|
+
# decision to scale based on false data. Clear it.
|
22
|
+
clear_pending!
|
23
|
+
false
|
24
|
+
end
|
18
25
|
end
|
19
26
|
|
20
27
|
def scale_both reads, writes
|
21
|
-
|
28
|
+
if throughput_synced?
|
29
|
+
dynamo_scale(read_capacity_units: reads, write_capacity_units: writes)
|
30
|
+
else
|
31
|
+
# If the throughputs were not synced, the likelihood is we made the
|
32
|
+
# decision to scale based on false data. Clear it.
|
33
|
+
clear_pending!
|
34
|
+
false
|
35
|
+
end
|
22
36
|
end
|
23
37
|
|
24
38
|
def can_run?
|
25
39
|
dynamo.status == :active
|
26
40
|
end
|
27
41
|
|
42
|
+
def throughput_synced?
|
43
|
+
time, datum = table.data.last
|
44
|
+
|
45
|
+
# If we've not gathered any data, we cannot know if our values are synced
|
46
|
+
# with Dynamo so we have to assume they are not until we get some data in.
|
47
|
+
return false if time.nil? or datum.nil?
|
48
|
+
|
49
|
+
if dynamo.read_capacity_units != datum[:provisioned_reads]
|
50
|
+
logger.error "[actioner] DynamoDB disagrees with CloudWatch on what " +
|
51
|
+
"the provisioned reads are. To be on the safe side, operations are " +
|
52
|
+
"not being applied."
|
53
|
+
|
54
|
+
return false
|
55
|
+
end
|
56
|
+
|
57
|
+
if dynamo.write_capacity_units != datum[:provisioned_writes]
|
58
|
+
logger.error "[actioner] DynamoDB disagrees with CloudWatch on what " +
|
59
|
+
"the provisioned writes are. To be on the safe side, operations are " +
|
60
|
+
"not being applied."
|
61
|
+
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
|
65
|
+
return true
|
66
|
+
end
|
67
|
+
|
28
68
|
private
|
29
69
|
|
30
70
|
def dynamo_scale opts
|
@@ -19,24 +19,27 @@ module DynamoAutoscale
|
|
19
19
|
def run &block
|
20
20
|
poll(tables) do |table_name, data|
|
21
21
|
logger.debug "[poller] Got data: #{data}"
|
22
|
-
table = DynamoAutoscale.tables[table_name]
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
dispatch(DynamoAutoscale.tables[table_name], data, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def dispatch table, data, &block
|
28
|
+
times = data.inject([]) do |memo, (_, timeseries)|
|
29
|
+
memo += timeseries.keys
|
30
|
+
end.sort!.uniq!
|
27
31
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
times.each do |time|
|
33
|
+
datum = {
|
34
|
+
provisioned_writes: data[:provisioned_writes][time],
|
35
|
+
provisioned_reads: data[:provisioned_reads][time],
|
36
|
+
consumed_writes: data[:consumed_writes][time],
|
37
|
+
consumed_reads: data[:consumed_reads][time],
|
38
|
+
}
|
35
39
|
|
36
|
-
|
40
|
+
filters.each { |filter| filter.call(table, time, datum) }
|
37
41
|
|
38
|
-
|
39
|
-
end
|
42
|
+
DynamoAutoscale.dispatcher.dispatch(table, time, datum, &block)
|
40
43
|
end
|
41
44
|
end
|
42
45
|
end
|