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 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. Starting polling loop."
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 @pending[:reads] and @pending[:writes]
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 poll tables, &block
7
- if tables.nil?
8
- tables = AWS::DynamoDB.new.tables.to_a.map(&:name)
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 |table|
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(table, Metrics.all_metrics(table, {
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 DynamoAutoscale.actioners[table].can_run?
37
- logger.debug "[dispatcher] Checking rules..."
38
- DynamoAutoscale.rules.test(table)
39
- @last_check[table.name] = time
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, table is not ready " +
42
- "to have its throughputs modified."
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
- dynamo_scale(aws_throughput_key => value)
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
- dynamo_scale(read_capacity_units: reads, write_capacity_units: writes)
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
- times = data.inject([]) do |memo, (_, timeseries)|
25
- memo += timeseries.keys
26
- end.sort!.uniq!
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
- times.each do |time|
29
- datum = {
30
- provisioned_writes: data[:provisioned_writes][time],
31
- provisioned_reads: data[:provisioned_reads][time],
32
- consumed_writes: data[:consumed_writes][time],
33
- consumed_reads: data[:consumed_reads][time],
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
- filters.each { |filter| filter.call(table, time, datum) }
40
+ filters.each { |filter| filter.call(table, time, datum) }
37
41
 
38
- DynamoAutoscale.dispatcher.dispatch(table, time, datum, &block)
39
- end
42
+ DynamoAutoscale.dispatcher.dispatch(table, time, datum, &block)
40
43
  end
41
44
  end
42
45
  end
@@ -1,3 +1,3 @@
1
1
  module DynamoAutoscale
2
- VERSION = '0.2.9'
2
+ VERSION = '0.3'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dynamo-autoscale
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
4
+ version: '0.3'
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors: