Skip to content

Commit 9ef094c

Browse files
author
Tag Spilman
committed
IC-8392: Improve performance of Paginator by switching from DEFAULT_PAGE_SIZE to MAX_PAGE_SIZE
1 parent c8e2dd1 commit 9ef094c

File tree

3 files changed

+101
-64
lines changed

3 files changed

+101
-64
lines changed

src/main/java/com/iland/coda/footprint/RetryCodaClient.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,16 @@ public CodaClient login() throws ApiException {
108108
public Set<RegistrationLight> listRegistrations(final String category)
109109
throws ApiException {
110110
if (delegatee instanceof SimpleCodaClient simpleCodaClient) {
111-
112-
return new Paginator<>(pageNo -> retryIfNecessary(
113-
() -> simpleCodaClient.adminApi.adminRegistrationsLightRetrieve(
114-
category, pageNo, MAX_PAGE_SIZE)),
111+
try (final var paginator = new Paginator<>(
112+
pageNo -> retryIfNecessary(
113+
() -> simpleCodaClient.adminApi.adminRegistrationsLightRetrieve(
114+
category, pageNo, MAX_PAGE_SIZE)),
115115
PaginatedRegistrationLightList::getPage,
116116
PaginatedRegistrationLightList::getTotalPages,
117117
PaginatedRegistrationLightList::getTotalCount,
118-
PaginatedRegistrationLightList::getItems).fetchAllAsync();
118+
PaginatedRegistrationLightList::getItems)) {
119+
return paginator.fetchAllAsync();
120+
}
119121
}
120122

121123
return retryIfNecessary(() -> delegatee.listRegistrations(category));
@@ -125,13 +127,16 @@ public Set<RegistrationLight> listRegistrations(final String category)
125127
public Set<Account> listAccounts(final Integer accountId)
126128
throws ApiException {
127129
if (delegatee instanceof SimpleCodaClient simpleCodaClient) {
128-
129-
return new Paginator<>(pageNo -> retryIfNecessary(
130-
() -> simpleCodaClient.commonApi.getAccounts(null, pageNo,
131-
MAX_PAGE_SIZE, accountId)), PaginatedAccountList::getPage,
130+
try (final var paginator = new Paginator<>(
131+
pageNo -> retryIfNecessary(
132+
() -> simpleCodaClient.commonApi.getAccounts(null, pageNo,
133+
MAX_PAGE_SIZE, accountId)),
134+
PaginatedAccountList::getPage,
132135
PaginatedAccountList::getTotalPages,
133136
PaginatedAccountList::getTotalCount,
134-
PaginatedAccountList::getItems).fetchAllAsync();
137+
PaginatedAccountList::getItems)) {
138+
return paginator.fetchAllAsync();
139+
}
135140
}
136141

137142
return retryIfNecessary(() -> delegatee.listAccounts(accountId));

src/main/java/com/iland/coda/footprint/SimpleCodaClient.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,13 @@ public Set<RegistrationLight> listRegistrations(final String category)
8989
throws ApiException {
9090
logger.debug("Retrieving registrations...");
9191
final Stopwatch stopwatch = Stopwatch.createStarted();
92-
try {
93-
return new Paginator<>(
94-
pageNo -> adminApi.adminRegistrationsLightRetrieve(category,
95-
pageNo, DEFAULT_PAGE_SIZE),
96-
PaginatedRegistrationLightList::getPage,
97-
PaginatedRegistrationLightList::getTotalPages,
98-
PaginatedRegistrationLightList::getTotalCount,
99-
PaginatedRegistrationLightList::getItems).fetchAllAsync();
92+
try (final var paginator = new Paginator<>(
93+
pageNo -> adminApi.adminRegistrationsLightRetrieve(category, pageNo,
94+
MAX_PAGE_SIZE), PaginatedRegistrationLightList::getPage,
95+
PaginatedRegistrationLightList::getTotalPages,
96+
PaginatedRegistrationLightList::getTotalCount,
97+
PaginatedRegistrationLightList::getItems)) {
98+
return paginator.fetchAllAsync();
10099
} finally {
101100
logger.debug("...registrations retrieved in {}", stopwatch);
102101
}
@@ -107,13 +106,13 @@ public Set<Account> listAccounts(final Integer accountId)
107106
throws ApiException {
108107
logger.debug("Retrieving accounts...");
109108
final Stopwatch stopwatch = Stopwatch.createStarted();
110-
try {
111-
return new Paginator<>(
112-
pageNo -> commonApi.getAccounts(null, pageNo, DEFAULT_PAGE_SIZE,
113-
accountId), PaginatedAccountList::getPage,
114-
PaginatedAccountList::getTotalPages,
115-
PaginatedAccountList::getTotalCount,
116-
PaginatedAccountList::getItems).fetchAllAsync();
109+
try (final var paginator = new Paginator<>(
110+
pageNo -> commonApi.getAccounts(null, pageNo, MAX_PAGE_SIZE,
111+
accountId), PaginatedAccountList::getPage,
112+
PaginatedAccountList::getTotalPages,
113+
PaginatedAccountList::getTotalCount,
114+
PaginatedAccountList::getItems)) {
115+
return paginator.fetchAllAsync();
117116
} finally {
118117
logger.debug("...registrations retrieved in {}", stopwatch);
119118
}
@@ -223,12 +222,14 @@ public Scan getScanStatus(final String scanId, final Integer accountId)
223222
public List<ScanSurfaceEntry> getScanSurface(final Integer scannerId,
224223
final String textFilter, final Integer accountId) throws ApiException {
225224
logger.debug("Retrieving scan surface...");
226-
return new Paginator<>(
225+
try (final var paginator = new Paginator<>(
227226
pageNo -> consoleApi.consoleScanSurfaceRetrieve(pageNo, scannerId,
228227
textFilter, accountId), PaginatedScanSurfaceEntryList::getPage,
229228
PaginatedScanSurfaceEntryList::getTotalPages,
230229
PaginatedScanSurfaceEntryList::getTotalCount,
231-
PaginatedScanSurfaceEntryList::getItems).fetchAll();
230+
PaginatedScanSurfaceEntryList::getItems)) {
231+
return paginator.fetchAll();
232+
}
232233
}
233234

234235
@Override

src/main/java/com/iland/coda/footprint/pagination/Paginator.java

Lines changed: 68 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,21 @@
2222
import java.util.HashSet;
2323
import java.util.List;
2424
import java.util.Set;
25+
import java.util.concurrent.CompletableFuture;
26+
import java.util.concurrent.ExecutorService;
27+
import java.util.concurrent.Executors;
28+
import java.util.concurrent.Future;
2529
import java.util.concurrent.atomic.AtomicInteger;
2630
import java.util.function.Function;
2731
import java.util.function.Supplier;
32+
import java.util.function.ToIntFunction;
2833
import java.util.stream.Collectors;
2934
import java.util.stream.IntStream;
35+
import java.util.stream.Stream;
3036

3137
import com.google.common.base.Stopwatch;
3238
import com.google.common.base.Throwables;
39+
import com.google.common.util.concurrent.Futures;
3340
import net.codacloud.ApiException;
3441
import org.slf4j.Logger;
3542
import org.slf4j.LoggerFactory;
@@ -39,25 +46,27 @@
3946
*
4047
* @param <I> the paginated SDK type
4148
* @param <V> the item value type
42-
* @author <a href="mailto:[email protected]">Tag Spilman</a>
4349
*/
44-
public final class Paginator<I, V> {
50+
public final class Paginator<I, V> implements AutoCloseable {
4551

4652
private static final Logger logger =
4753
LoggerFactory.getLogger(Paginator.class);
4854

55+
private final ExecutorService service = Executors.newFixedThreadPool(
56+
Runtime.getRuntime().availableProcessors());
57+
4958
private final PageFetcher<I> fetcher;
5059
private final Function<I, Page<V>> pageMapper;
5160

5261
public Paginator(final PageFetcher<I> fetcher,
53-
final Function<I, Integer> pageNoMapper,
54-
final Function<I, Integer> totalPageMapper,
55-
final Function<I, Integer> totalCountMapper,
62+
final ToIntFunction<I> pageNoMapper,
63+
final ToIntFunction<I> totalPageMapper,
64+
final ToIntFunction<I> totalCountMapper,
5665
final Function<I, List<V>> itemsMapper) {
5766
this.fetcher = fetcher;
58-
this.pageMapper =
59-
i -> new Page<>(pageNoMapper.apply(i), totalPageMapper.apply(i),
60-
totalCountMapper.apply(i), itemsMapper.apply(i));
67+
this.pageMapper = i -> new Page<>(pageNoMapper.applyAsInt(i),
68+
totalPageMapper.applyAsInt(i), totalCountMapper.applyAsInt(i),
69+
itemsMapper.apply(i));
6170
}
6271

6372
/**
@@ -67,7 +76,7 @@ public Paginator(final PageFetcher<I> fetcher,
6776
* @throws ApiException ...
6877
*/
6978
public List<V> fetchAll() throws ApiException {
70-
return fetchAll(Function.identity(), ArrayList::new);
79+
return fetchAll(false, ArrayList::new);
7180
}
7281

7382
/**
@@ -77,55 +86,77 @@ public List<V> fetchAll() throws ApiException {
7786
* @throws ApiException ...
7887
*/
7988
public Set<V> fetchAllAsync() throws ApiException {
80-
return fetchAll(IntStream::parallel, HashSet::new);
89+
return fetchAll(true, HashSet::new);
8190
}
8291

83-
private <C extends Collection<V>> C fetchAll(
84-
final Function<IntStream, IntStream> streamMapper,
92+
private <C extends Collection<V>> C fetchAll(final boolean parallel,
8593
final Supplier<C> supplier) throws ApiException {
8694
final I pageOfItems = fetcher.fetch(1);
8795
final Page<V> firstPage = pageMapper.apply(pageOfItems);
8896
final AtomicInteger count = new AtomicInteger(0);
8997
try {
90-
final C items = streamMapper.apply(
91-
IntStream.range(2, firstPage.getTotalPages() + 1))
92-
.mapToObj(pageNo -> fetch(pageNo, count)).map(pageMapper)
93-
.map(Page::getItems).flatMap(List::stream)
98+
return Stream.concat(
99+
Stream.of(pageOfItems).map(CompletableFuture::completedFuture),
100+
IntStream.range(2, firstPage.getTotalPages() + 1)
101+
.mapToObj(pageNo -> submit(parallel, pageNo, count)))
102+
// collect here to act as a latch
103+
.toList()
104+
.stream()
105+
.map(Futures::getUnchecked)
106+
.map(pageMapper)
107+
.map(Page::getItems)
108+
.flatMap(List::stream)
94109
.collect(Collectors.toCollection(supplier));
95-
items.addAll(firstPage.getItems());
96-
return items;
97-
} catch (RuntimeException e) {
110+
} catch (final RuntimeException e) {
98111
Throwables.throwIfInstanceOf(e.getCause(), ApiException.class);
99112
throw e;
100113
}
101114
}
102115

103-
private I fetch(final Integer pageNo, final AtomicInteger count) {
104-
final Stopwatch stopwatch = Stopwatch.createStarted();
116+
private Future<I> submit(final boolean parallel, final int pageNo,
117+
final AtomicInteger count) {
118+
if (parallel) {
119+
return service.submit(() -> fetch(pageNo, count));
120+
}
105121

106122
try {
107-
final I fetch = fetcher.fetch(pageNo);
108-
109-
if (logger.isDebugEnabled()) {
110-
final Page<V> page = pageMapper.apply(fetch);
111-
final Integer totalPages = page.getTotalPages();
112-
final String percent =
113-
calculatePercentage(count.incrementAndGet(), totalPages);
114-
logger.debug("Page {}/{} ({} items) retrieved in {} ({}%)",
115-
pageNo, totalPages, page.getItems().size(), stopwatch,
116-
percent);
117-
}
118-
119-
return fetch;
120-
} catch (ApiException e) {
121-
throw new RuntimeException(e);
123+
final I result = fetch(pageNo, count);
124+
125+
return CompletableFuture.completedFuture(result);
126+
} catch (final ApiException e) {
127+
return CompletableFuture.failedFuture(e);
122128
}
123129
}
124130

131+
private I fetch(final Integer pageNo, final AtomicInteger count)
132+
throws ApiException {
133+
final Stopwatch stopwatch = Stopwatch.createStarted();
134+
135+
final I fetch = fetcher.fetch(pageNo);
136+
137+
if (logger.isDebugEnabled()) {
138+
final Page<V> page = pageMapper.apply(fetch);
139+
final Integer totalPages = page.getTotalPages();
140+
final String percent =
141+
calculatePercentage(count.incrementAndGet(), totalPages);
142+
logger.debug("Page {}/{} ({} items) retrieved in {} ({}%)", pageNo,
143+
totalPages, page.getItems().size(), stopwatch, percent);
144+
}
145+
146+
return fetch;
147+
}
148+
125149
private static String calculatePercentage(final int a, final int b) {
126150
return new BigDecimal(a).divide(BigDecimal.valueOf(b), 3,
127-
RoundingMode.FLOOR).multiply(BigDecimal.valueOf(100)).setScale(1)
151+
RoundingMode.FLOOR)
152+
.multiply(BigDecimal.valueOf(100))
153+
.setScale(1, RoundingMode.UNNECESSARY)
128154
.toString();
129155
}
130156

157+
@Override
158+
public void close() {
159+
service.shutdown();
160+
}
161+
131162
}

0 commit comments

Comments
 (0)