Skip to content

Commit 6e0f2a1

Browse files
authored
fixes continue scan in tablet mgmt iterator (apache#4457)
Many places in the accumulo code will read a batch of key/values and then use the last key in the batch to construct a range to get the next batch. The last key in the batch will be a non inclusive start key for the range. The tablet mgmt iterator was not handling this case and returning the key that should have been excluded.
1 parent 370a7de commit 6e0f2a1

File tree

2 files changed

+44
-13
lines changed

2 files changed

+44
-13
lines changed

server/base/src/main/java/org/apache/accumulo/server/manager/state/TabletManagementIterator.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,11 @@ protected void consume() throws IOException {
217217
// can pull this K,V pair from the results by looking at the colf.
218218
TabletManagement.addActions(decodedRow, actions);
219219
}
220-
topKey = decodedRow.firstKey();
220+
221+
// This key is being created exactly the same way as the whole row iterator creates keys.
222+
// This is important for ensuring that seek works as expected in the continue case. See
223+
// WholeRowIterator seek function for details, it looks for keys w/o columns.
224+
topKey = new Key(decodedRow.firstKey().getRow());
221225
topValue = WholeRowIterator.encodeRow(new ArrayList<>(decodedRow.keySet()),
222226
new ArrayList<>(decodedRow.values()));
223227
LOG.trace("Returning extent {} with reasons: {}", tm.getExtent(), actions);

test/src/main/java/org/apache/accumulo/test/functional/TabletManagementIteratorIT.java

+39-12
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,13 @@
1818
*/
1919
package org.apache.accumulo.test.functional;
2020

21+
import static org.apache.accumulo.core.manager.state.TabletManagement.ManagementAction.BAD_STATE;
2122
import static org.apache.accumulo.core.manager.state.TabletManagement.ManagementAction.NEEDS_LOCATION_UPDATE;
23+
import static org.apache.accumulo.core.manager.state.TabletManagement.ManagementAction.NEEDS_RECOVERY;
24+
import static org.apache.accumulo.core.manager.state.TabletManagement.ManagementAction.NEEDS_SPLITTING;
25+
import static org.apache.accumulo.core.manager.state.TabletManagement.ManagementAction.NEEDS_VOLUME_REPLACEMENT;
2226
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertTrue;
2328

2429
import java.io.IOException;
2530
import java.time.Duration;
@@ -187,6 +192,10 @@ public void test() throws AccumuloException, AccumuloSecurityException, TableExi
187192
assertEquals(expected, findTabletsNeedingAttention(client, metaCopy1, tabletMgmtParams),
188193
"Should have four tablets with hosting availability changes");
189194

195+
// test continue scan functionality, this test needs a table and tablet mgmt params that will
196+
// return more than one tablet
197+
testContinueScan(client, metaCopy1, tabletMgmtParams);
198+
190199
// test the assigned case (no location)
191200
removeLocation(client, metaCopy1, t3);
192201
expected =
@@ -210,18 +219,15 @@ public void test() throws AccumuloException, AccumuloSecurityException, TableExi
210219
// Test the recovery cases
211220
createLogEntry(client, metaCopy5, t1);
212221
setTabletAvailability(client, metaCopy5, t1, TabletAvailability.UNHOSTED.name());
213-
expected = Map.of(endR1,
214-
Set.of(NEEDS_LOCATION_UPDATE, TabletManagement.ManagementAction.NEEDS_RECOVERY));
222+
expected = Map.of(endR1, Set.of(NEEDS_LOCATION_UPDATE, NEEDS_RECOVERY));
215223
assertEquals(expected, findTabletsNeedingAttention(client, metaCopy5, tabletMgmtParams),
216224
"Only 1 of 2 tablets in table t1 should be returned");
217225
setTabletAvailability(client, metaCopy5, t1, TabletAvailability.ONDEMAND.name());
218-
expected = Map.of(endR1,
219-
Set.of(NEEDS_LOCATION_UPDATE, TabletManagement.ManagementAction.NEEDS_RECOVERY));
226+
expected = Map.of(endR1, Set.of(NEEDS_LOCATION_UPDATE, NEEDS_RECOVERY));
220227
assertEquals(expected, findTabletsNeedingAttention(client, metaCopy5, tabletMgmtParams),
221228
"Only 1 of 2 tablets in table t1 should be returned");
222229
setTabletAvailability(client, metaCopy5, t1, TabletAvailability.HOSTED.name());
223-
expected = Map.of(endR1,
224-
Set.of(NEEDS_LOCATION_UPDATE, TabletManagement.ManagementAction.NEEDS_RECOVERY), prevR1,
230+
expected = Map.of(endR1, Set.of(NEEDS_LOCATION_UPDATE, NEEDS_RECOVERY), prevR1,
225231
Set.of(NEEDS_LOCATION_UPDATE));
226232
assertEquals(expected, findTabletsNeedingAttention(client, metaCopy5, tabletMgmtParams),
227233
"2 tablets in table t1 should be returned");
@@ -248,8 +254,7 @@ public void test() throws AccumuloException, AccumuloSecurityException, TableExi
248254
// for both MERGING and SPLITTING
249255
setOperationId(client, metaCopy4, t4, null, TabletOperationType.MERGING);
250256
createLogEntry(client, metaCopy4, t4);
251-
expected = Map.of(endR4,
252-
Set.of(NEEDS_LOCATION_UPDATE, TabletManagement.ManagementAction.NEEDS_RECOVERY));
257+
expected = Map.of(endR4, Set.of(NEEDS_LOCATION_UPDATE, NEEDS_RECOVERY));
253258
assertEquals(expected, findTabletsNeedingAttention(client, metaCopy4, tabletMgmtParams),
254259
"Should have a tablet needing attention because of wals");
255260
// Switch op to SPLITTING which should also need attention like MERGING
@@ -266,8 +271,7 @@ public void test() throws AccumuloException, AccumuloSecurityException, TableExi
266271
// test the bad tablet location state case (inconsistent metadata)
267272
tabletMgmtParams = createParameters(client);
268273
addDuplicateLocation(client, metaCopy3, t3);
269-
expected = Map.of(prevR3,
270-
Set.of(NEEDS_LOCATION_UPDATE, TabletManagement.ManagementAction.BAD_STATE));
274+
expected = Map.of(prevR3, Set.of(NEEDS_LOCATION_UPDATE, BAD_STATE));
271275
assertEquals(expected, findTabletsNeedingAttention(client, metaCopy3, tabletMgmtParams),
272276
"Should have 1 tablet that needs a metadata repair");
273277

@@ -278,7 +282,7 @@ public void test() throws AccumuloException, AccumuloSecurityException, TableExi
278282
Map<Path,Path> replacements =
279283
Map.of(new Path("file:/vol1/accumulo/inst_id"), new Path("file:/vol2/accumulo/inst_id"));
280284
tabletMgmtParams = createParameters(client, replacements);
281-
expected = Map.of(prevR4, Set.of(TabletManagement.ManagementAction.NEEDS_VOLUME_REPLACEMENT));
285+
expected = Map.of(prevR4, Set.of(NEEDS_VOLUME_REPLACEMENT));
282286
assertEquals(expected, findTabletsNeedingAttention(client, metaCopy4, tabletMgmtParams),
283287
"Should have one tablet that needs a volume replacement");
284288

@@ -291,7 +295,7 @@ public void test() throws AccumuloException, AccumuloSecurityException, TableExi
291295
// Lower the split threshold for the table, should cause the files added to need attention.
292296
client.tableOperations().setProperty(tables[3], Property.TABLE_SPLIT_THRESHOLD.getKey(),
293297
"1K");
294-
expected = Map.of(prevR4, Set.of(TabletManagement.ManagementAction.NEEDS_SPLITTING));
298+
expected = Map.of(prevR4, Set.of(NEEDS_SPLITTING));
295299
assertEquals(expected, findTabletsNeedingAttention(client, metaCopy6, tabletMgmtParams),
296300
"Should have one tablet that needs splitting");
297301

@@ -444,6 +448,29 @@ private Map<KeyExtent,Set<TabletManagement.ManagementAction>> findTabletsNeeding
444448
return results;
445449
}
446450

451+
// Multiple places in the accumulo code will read a batch of keys and then take the last key and
452+
// make it non inclusive to get the next batch. This test code simulates that and ensures the
453+
// tablet mgmt iterator works with that.
454+
private void testContinueScan(AccumuloClient client, String table,
455+
TabletManagementParameters tabletMgmtParams) throws TableNotFoundException {
456+
try (Scanner scanner = client.createScanner(table, Authorizations.EMPTY)) {
457+
TabletManagementIterator.configureScanner(scanner, tabletMgmtParams);
458+
List<Entry<Key,Value>> entries1 = new ArrayList<>();
459+
scanner.forEach(e -> entries1.add(e));
460+
assertTrue(entries1.size() > 1);
461+
462+
// Create a range that does not include the first key from the last scan.
463+
var range = new Range(entries1.get(0).getKey(), false, null, true);
464+
scanner.setRange(range);
465+
466+
// Ensure the first key excluded from the scan
467+
List<Entry<Key,Value>> entries2 = new ArrayList<>();
468+
scanner.forEach(e -> entries2.add(e));
469+
assertEquals(entries1.size() - 1, entries2.size());
470+
assertEquals(entries1.get(1).getKey(), entries2.get(0).getKey());
471+
}
472+
}
473+
447474
private void createTable(AccumuloClient client, String t, boolean online)
448475
throws AccumuloSecurityException, AccumuloException, TableNotFoundException,
449476
TableExistsException {

0 commit comments

Comments
 (0)