@@ -3,15 +3,22 @@ package dynamodbcopy
3
3
import (
4
4
"errors"
5
5
"fmt"
6
+ "time"
6
7
7
8
"github.com/aws/aws-sdk-go/aws"
9
+ "github.com/aws/aws-sdk-go/aws/awserr"
8
10
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
9
11
"github.com/aws/aws-sdk-go/aws/session"
10
12
"github.com/aws/aws-sdk-go/service/dynamodb"
11
13
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbiface"
12
14
)
13
15
14
- const maxBatchWriteSize = 25
16
+ const (
17
+ maxBatchWriteSize = 25
18
+ maxRetryTime = int (time .Minute ) * 3
19
+
20
+ errCodeThrottlingException = "ThrottlingException"
21
+ )
15
22
16
23
// DynamoDBAPI just a wrapper over aws-sdk dynamodbiface.DynamoDBAPI interface for mocking purposes
17
24
type DynamoDBAPI interface {
@@ -32,6 +39,7 @@ type dynamoDBSerivce struct {
32
39
tableName string
33
40
api DynamoDBAPI
34
41
sleep Sleeper
42
+ logger Logger
35
43
}
36
44
37
45
func NewDynamoDBAPI (roleArn string ) DynamoDBAPI {
@@ -49,8 +57,8 @@ func NewDynamoDBAPI(roleArn string) DynamoDBAPI {
49
57
return dynamodb .New (currentSession )
50
58
}
51
59
52
- func NewDynamoDBService (tableName string , api DynamoDBAPI , sleepFn Sleeper ) DynamoDBService {
53
- return dynamoDBSerivce {tableName , api , sleepFn }
60
+ func NewDynamoDBService (tableName string , api DynamoDBAPI , sleepFn Sleeper , logger Logger ) DynamoDBService {
61
+ return dynamoDBSerivce {tableName , api , sleepFn , logger }
54
62
}
55
63
56
64
func (db dynamoDBSerivce ) DescribeTable () (* dynamodb.TableDescription , error ) {
@@ -86,6 +94,7 @@ func (db dynamoDBSerivce) UpdateCapacity(capacity Capacity) error {
86
94
},
87
95
}
88
96
97
+ db .logger .Printf ("updating %s with read: %d, write: %d" , db .tableName , read , write )
89
98
_ , err := db .api .UpdateTable (input )
90
99
if err != nil {
91
100
return fmt .Errorf ("unable to update table %s: %s" , db .tableName , err )
@@ -95,6 +104,7 @@ func (db dynamoDBSerivce) UpdateCapacity(capacity Capacity) error {
95
104
}
96
105
97
106
func (db dynamoDBSerivce ) BatchWrite (items []DynamoDBItem ) error {
107
+ db .logger .Printf ("writing batch of %d to %s" , len (items ), db .tableName )
98
108
if len (items ) == 0 {
99
109
return nil
100
110
}
@@ -125,40 +135,76 @@ func (db dynamoDBSerivce) batchWriteItem(requests []*dynamodb.WriteRequest) erro
125
135
126
136
writeRequests := requests
127
137
for len (writeRequests ) != 0 {
128
- batchInput := & dynamodb.BatchWriteItemInput {
129
- RequestItems : map [string ][]* dynamodb.WriteRequest {
130
- tableName : writeRequests ,
131
- },
132
- }
138
+ retryHandler := func (attempt , elapsed int ) (bool , error ) {
139
+ batchInput := & dynamodb.BatchWriteItemInput {
140
+ RequestItems : map [string ][]* dynamodb.WriteRequest {
141
+ tableName : writeRequests ,
142
+ },
143
+ }
133
144
134
- output , err := db .api .BatchWriteItem (batchInput )
135
- if err != nil {
136
- return fmt .Errorf ("unable to batch write to table %s: %s" , db .tableName , err )
145
+ output , err := db .api .BatchWriteItem (batchInput )
146
+ if err == nil {
147
+ writeRequests = output .UnprocessedItems [tableName ]
148
+
149
+ return true , nil
150
+ }
151
+
152
+ if awsErr , ok := err .(awserr.Error ); ok {
153
+ switch awsErr .Code () {
154
+ case dynamodb .ErrCodeProvisionedThroughputExceededException :
155
+ db .logger .Printf ("batch write provisioning error: waited %d ms (attempt %d)" , elapsed , attempt )
156
+ return false , nil
157
+ case errCodeThrottlingException :
158
+ db .logger .Printf ("batch write throttling error: waited %d ms (attempt %d)" , elapsed , attempt )
159
+ return false , nil
160
+ default :
161
+ return false , fmt .Errorf (
162
+ "aws %s error in batch write to table %s: %s" ,
163
+ awsErr .Code (),
164
+ db .tableName ,
165
+ awsErr .Error (),
166
+ )
167
+ }
168
+ }
169
+
170
+ return false , fmt .Errorf ("unable to batch write to table %s: %s" , db .tableName , err )
137
171
}
138
172
139
- writeRequests = output .UnprocessedItems [tableName ]
173
+ if err := db .retry (retryHandler ); err != nil {
174
+ return err
175
+ }
140
176
}
141
177
142
178
return nil
143
179
}
144
180
145
181
func (db dynamoDBSerivce ) WaitForReadyTable () error {
146
- elapsed := 0
147
-
148
- for attempt := 0 ; ; attempt ++ {
182
+ return db .retry (func (attempt , elapsed int ) (bool , error ) {
149
183
description , err := db .DescribeTable ()
184
+ if err != nil {
185
+ return false , err
186
+ }
187
+
188
+ return * description .TableStatus == dynamodb .TableStatusActive , nil
189
+ })
190
+ }
191
+
192
+ func (db dynamoDBSerivce ) retry (handler func (attempt , elapsed int ) (bool , error )) error {
193
+ elapsed := 0
194
+ for attempt := 0 ; elapsed < maxRetryTime ; attempt ++ {
195
+ handled , err := handler (attempt , elapsed )
150
196
if err != nil {
151
197
return err
152
198
}
153
199
154
- if * description . TableStatus == dynamodb . TableStatusActive {
155
- break
200
+ if handled {
201
+ return nil
156
202
}
157
203
158
204
elapsed += db .sleep (elapsed * attempt )
159
205
}
160
206
161
- return nil
207
+ return fmt . Errorf ( "waited for too long (%d ms) to perform operation on %s table" , elapsed , db . tableName )
162
208
}
163
209
164
210
func (db dynamoDBSerivce ) Scan (totalSegments , segment int , itemsChan chan <- []DynamoDBItem ) error {
@@ -175,11 +221,14 @@ func (db dynamoDBSerivce) Scan(totalSegments, segment int, itemsChan chan<- []Dy
175
221
input .SetTotalSegments (int64 (totalSegments ))
176
222
}
177
223
224
+ totalScanned := 0
178
225
pagerFn := func (output * dynamodb.ScanOutput , b bool ) bool {
179
226
var items []DynamoDBItem
180
227
for _ , item := range output .Items {
181
228
items = append (items , item )
229
+ totalScanned ++
182
230
}
231
+ db .logger .Printf ("%s table scanned page with %d items (reader %d)" , db .tableName , len (items ), segment )
183
232
184
233
itemsChan <- items
185
234
@@ -190,5 +239,7 @@ func (db dynamoDBSerivce) Scan(totalSegments, segment int, itemsChan chan<- []Dy
190
239
return fmt .Errorf ("unable to scan table %s: %s" , db .tableName , err )
191
240
}
192
241
242
+ db .logger .Printf ("%s table scanned a total of %d items (reader %d)" , db .tableName , totalScanned , segment )
243
+
193
244
return nil
194
245
}
0 commit comments