Skip to content

Commit

Permalink
Use optional backoff in import method
Browse files Browse the repository at this point in the history
  • Loading branch information
andrykonchin committed Mar 23, 2018
1 parent dd55e18 commit a3d9d52
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 5 deletions.
22 changes: 17 additions & 5 deletions lib/dynamoid/persistence.rb
Expand Up @@ -213,13 +213,25 @@ def dynamo_type(type)
end

def import(objects)
documents = objects.map { |attrs|
self.build(attrs).tap { |item|
documents = objects.map do |attrs|
self.build(attrs).tap do |item|
item.hash_key = SecureRandom.uuid if item.hash_key.blank?
}
}
end
end

Dynamoid.adapter.batch_write_item(self.table_name, documents.map(&:dump))
unless Dynamoid.config.backoff
Dynamoid.adapter.batch_write_item(self.table_name, documents.map(&:dump))
else
backoff = nil
Dynamoid.adapter.batch_write_item(self.table_name, documents.map(&:dump)) do |has_unprocessed_items|
if has_unprocessed_items
backoff ||= Dynamoid.config.build_backoff
backoff.call
else
backoff = nil
end
end
end

documents.each { |d| d.new_record = false }
documents
Expand Down
76 changes: 76 additions & 0 deletions spec/dynamoid/persistence_spec.rb
Expand Up @@ -1028,5 +1028,81 @@ def self.name; 'Address'; end
user = User.find(users[0].id)
expect(user.todo_list).to eq nil
end

context 'backoff is specified' do
let(:backoff_strategy) do
->(_) { -> { @counter += 1 } }
end

before do
@old_backoff = Dynamoid.config.backoff
@old_backoff_strategies = Dynamoid.config.backoff_strategies.dup

@counter = 0
Dynamoid.config.backoff_strategies[:simple] = backoff_strategy
Dynamoid.config.backoff = { simple: nil }
end

after do
Dynamoid.config.backoff = @old_backoff
Dynamoid.config.backoff_strategies = @old_backoff_strategies
end

it 'creates multiple documents' do
expect {
Address.import([{city: 'Chicago'}, {city: 'New York'}])
}.to change { Address.count }.by(2)
end

it 'uses specified backoff when some items are not processed' do
# dynamodb-local ignores provisioned throughput settings
# so we cannot emulate unprocessed items - let's stub

klass = new_class
table_name = klass.table_name
items = (1 .. 3).map(&:to_s).map { |id| { id: id } }

responses = [
double('response 1', unprocessed_items: { table_name => [
double(put_request: double(item: { id: '3' }))
]}),
double('response 2', unprocessed_items: { table_name => [
double(put_request: double(item: { id: '3' }))
]}),
double('response 3', unprocessed_items: nil)
]
allow(Dynamoid.adapter.client).to receive(:batch_write_item).and_return(*responses)

klass.import(items)
expect(@counter).to eq 2
end

it 'uses new backoff after successful call without unprocessed items' do
# dynamodb-local ignores provisioned throughput settings
# so we cannot emulate unprocessed items - let's stub

klass = new_class
table_name = klass.table_name
# batch_write_item processes up to 15 items at once
# so we emulate 4 calls with items
items = (1 .. 50).map(&:to_s).map { |id| { id: id } }

responses = [
double('response 1', unprocessed_items: { table_name => [
double(put_request: double(item: { id: '25' }))
]}),
double('response 3', unprocessed_items: nil),
double('response 2', unprocessed_items: { table_name => [
double(put_request: double(item: { id: '25' }))
]}),
double('response 3', unprocessed_items: nil)
]
allow(Dynamoid.adapter.client).to receive(:batch_write_item).and_return(*responses)

expect(backoff_strategy).to receive(:call).exactly(2).times.and_call_original
klass.import(items)
expect(@counter).to eq 2
end
end
end
end

0 comments on commit a3d9d52

Please sign in to comment.