dynamo-autoscale 0.2.9 → 0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|