Skip to content

Commit 17f06d4

Browse files
authored
feat: optimize savePolicy with BatchWriteItem and exponential backoff retry (#72)
1 parent 1246357 commit 17f06d4

File tree

2 files changed

+114
-8
lines changed

2 files changed

+114
-8
lines changed

src/main/java/org/casbin/adapter/DynamoDBAdapter.java

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,17 @@ private CasbinRule savePolicyLine(String ptype, List<String> rule) {
214214
}
215215

216216
private void putCasbinRuleItem(CasbinRule line) {
217+
Map<String, AttributeValue> item = buildItemFromCasbinRule(line);
218+
219+
PutItemRequest request = PutItemRequest.builder()
220+
.tableName(TABLE_NAME)
221+
.item(item)
222+
.build();
223+
224+
client.putItem(request);
225+
}
226+
227+
private Map<String, AttributeValue> buildItemFromCasbinRule(CasbinRule line) {
217228
Map<String, AttributeValue> item = new HashMap<>();
218229
item.put("ID", AttributeValue.builder().s(UUID.randomUUID().toString()).build());
219230
item.put("ptype", AttributeValue.builder().s(line.ptype != null ? line.ptype : "").build());
@@ -223,27 +234,94 @@ private void putCasbinRuleItem(CasbinRule line) {
223234
item.put("v3", AttributeValue.builder().s(line.v3 != null ? line.v3 : "").build());
224235
item.put("v4", AttributeValue.builder().s(line.v4 != null ? line.v4 : "").build());
225236
item.put("v5", AttributeValue.builder().s(line.v5 != null ? line.v5 : "").build());
237+
return item;
238+
}
239+
240+
private void writeBatchWithRetry(List<WriteRequest> writeRequests) {
241+
List<WriteRequest> unprocessedItems = new ArrayList<>(writeRequests);
242+
int retryCount = 0;
243+
int maxRetries = 5;
244+
long initialBackoffMs = 50;
226245

227-
PutItemRequest request = PutItemRequest.builder()
228-
.tableName(TABLE_NAME)
229-
.item(item)
230-
.build();
246+
while (!unprocessedItems.isEmpty() && retryCount < maxRetries) {
247+
Map<String, List<WriteRequest>> requestItems = new HashMap<>();
248+
requestItems.put(TABLE_NAME, unprocessedItems);
249+
250+
BatchWriteItemRequest batchRequest = BatchWriteItemRequest.builder()
251+
.requestItems(requestItems)
252+
.build();
253+
254+
BatchWriteItemResponse response = client.batchWriteItem(batchRequest);
255+
256+
// Get any unprocessed items for retry
257+
Map<String, List<WriteRequest>> unprocessed = response.unprocessedItems();
258+
if (unprocessed != null && unprocessed.containsKey(TABLE_NAME)) {
259+
unprocessedItems = new ArrayList<>(unprocessed.get(TABLE_NAME));
260+
retryCount++;
261+
262+
if (!unprocessedItems.isEmpty()) {
263+
// Exponential backoff
264+
long backoffMs = initialBackoffMs * (long) Math.pow(2, retryCount - 1);
265+
try {
266+
Thread.sleep(backoffMs);
267+
} catch (InterruptedException e) {
268+
Thread.currentThread().interrupt();
269+
throw new Error("Interrupted during backoff", e);
270+
}
271+
}
272+
} else {
273+
// All items processed successfully
274+
unprocessedItems.clear();
275+
}
276+
}
231277

232-
client.putItem(request);
278+
if (!unprocessedItems.isEmpty()) {
279+
throw new Error("Failed to write all items after " + maxRetries + " retries. " +
280+
unprocessedItems.size() + " items remaining.");
281+
}
282+
}
283+
284+
private void batchSaveRows(List<CasbinRule> rules) {
285+
if (rules.isEmpty()) {
286+
return;
287+
}
288+
289+
int batchSize = 25; // DynamoDB batch write limit
290+
List<WriteRequest> batch = new ArrayList<>();
291+
292+
for (CasbinRule rule : rules) {
293+
Map<String, AttributeValue> item = buildItemFromCasbinRule(rule);
294+
WriteRequest writeRequest = WriteRequest.builder()
295+
.putRequest(PutRequest.builder().item(item).build())
296+
.build();
297+
batch.add(writeRequest);
298+
299+
if (batch.size() >= batchSize) {
300+
writeBatchWithRetry(batch);
301+
batch.clear();
302+
}
303+
}
304+
305+
// Write any remaining items
306+
if (!batch.isEmpty()) {
307+
writeBatchWithRetry(batch);
308+
}
233309
}
234310

235311

236312
/**
237-
* svePolicy saves all policy rules to the storage.
313+
* savePolicy saves all policy rules to the storage.
238314
*/
239315
@Override
240316
public void savePolicy(Model model) {
317+
List<CasbinRule> allRules = new ArrayList<>();
318+
241319
for (Map.Entry<String, Assertion> entry : model.model.get("p").entrySet()) {
242320
String ptype = entry.getKey();
243321
Assertion ast = entry.getValue();
244322
for (List<String> rule : ast.policy) {
245323
CasbinRule line = savePolicyLine(ptype, rule);
246-
putCasbinRuleItem(line);
324+
allRules.add(line);
247325
}
248326
}
249327

@@ -252,9 +330,11 @@ public void savePolicy(Model model) {
252330
Assertion ast = entry.getValue();
253331
for (List<String> rule : ast.policy) {
254332
CasbinRule line = savePolicyLine(ptype, rule);
255-
putCasbinRuleItem(line);
333+
allRules.add(line);
256334
}
257335
}
336+
337+
batchSaveRows(allRules);
258338
}
259339

260340
/**

src/test/java/org/casbin/adapter/DynamoDBAdapterTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,30 @@ public void testAdapter() {
102102
asList("data2_admin", "data2", "write")));
103103
adapter.dropTable();
104104
}
105+
106+
@Test
107+
public void testPerformanceAndLimits() {
108+
// Test with more than 25 rules to verify batch processing works correctly
109+
Enforcer e = new Enforcer("examples/rbac_model.conf");
110+
111+
adapter.createTable();
112+
113+
// Add 30 policy rules to test batch writing (exceeds DynamoDB's 25-item limit per batch)
114+
List<List<String>> expectedPolicies = new ArrayList<>();
115+
for (int i = 0; i < 30; i++) {
116+
List<String> rule = asList("user" + i, "data" + i, "read");
117+
e.addPolicy(rule);
118+
expectedPolicies.add(rule);
119+
}
120+
121+
// Save all policies using batch write
122+
adapter.savePolicy(e.getModel());
123+
124+
// Clear and reload to verify all policies were saved correctly
125+
e.clearPolicy();
126+
adapter.loadPolicy(e.getModel());
127+
128+
testGetPolicy(e, expectedPolicies);
129+
adapter.dropTable();
130+
}
105131
}

0 commit comments

Comments
 (0)