-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
452 additions
and
317 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 0 additions & 82 deletions
82
application/src/main/java/run/halo/app/infra/ReactiveExtensionPaginatedIterator.java
This file was deleted.
Oops, something went wrong.
37 changes: 37 additions & 0 deletions
37
application/src/main/java/run/halo/app/infra/ReactiveExtensionPaginatedOperator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package run.halo.app.infra; | ||
|
||
import reactor.core.publisher.Flux; | ||
import reactor.core.publisher.Mono; | ||
import run.halo.app.extension.Extension; | ||
import run.halo.app.extension.ListOptions; | ||
|
||
/** | ||
* Reactive extension paginated operator to handle extensions by pagination. | ||
* | ||
* @author guqing | ||
* @since 2.15.0 | ||
*/ | ||
public interface ReactiveExtensionPaginatedOperator { | ||
|
||
/** | ||
* <p>Deletes all data, including any new entries added during the execution of this method.</p> | ||
* <p>This method continuously monitors and removes data that appears throughout its runtime, | ||
* ensuring that even data created during the deletion process is also removed.</p> | ||
*/ | ||
<E extends Extension> Mono<Void> deleteContinuously(Class<E> type, | ||
ListOptions listOptions); | ||
|
||
/** | ||
* <p>Deletes only the data that existed at the start of the operation.</p> | ||
* <p>This method takes a snapshot of the data at the beginning and deletes only that dataset; | ||
* any data added after the method starts will not be affected or removed.</p> | ||
*/ | ||
<E extends Extension> Flux<E> deleteInitialBatch(Class<E> type, | ||
ListOptions listOptions); | ||
|
||
/** | ||
* <p>Note that: This method can not be used for <code>deletion</code> operation, because | ||
* deletion operation will change the total records.</p> | ||
*/ | ||
<E extends Extension> Flux<E> list(Class<E> type, ListOptions listOptions); | ||
} |
115 changes: 115 additions & 0 deletions
115
application/src/main/java/run/halo/app/infra/ReactiveExtensionPaginatedOperatorImpl.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package run.halo.app.infra; | ||
|
||
import static run.halo.app.extension.index.query.QueryFactory.isNull; | ||
|
||
import java.util.concurrent.atomic.AtomicLong; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.data.domain.Sort; | ||
import org.springframework.stereotype.Component; | ||
import reactor.core.publisher.Flux; | ||
import reactor.core.publisher.Mono; | ||
import run.halo.app.extension.Extension; | ||
import run.halo.app.extension.ListOptions; | ||
import run.halo.app.extension.ListResult; | ||
import run.halo.app.extension.PageRequest; | ||
import run.halo.app.extension.PageRequestImpl; | ||
import run.halo.app.extension.ReactiveExtensionClient; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class ReactiveExtensionPaginatedOperatorImpl implements ReactiveExtensionPaginatedOperator { | ||
private static final int DEFAULT_PAGE_SIZE = 200; | ||
private final ReactiveExtensionClient client; | ||
|
||
@Override | ||
public <E extends Extension> Mono<Void> deleteContinuously(Class<E> type, | ||
ListOptions listOptions) { | ||
var pageRequest = createPageRequest(); | ||
return cleanupContinuously(type, listOptions, pageRequest); | ||
} | ||
|
||
private <E extends Extension> Mono<Void> cleanupContinuously(Class<E> type, | ||
ListOptions listOptions, | ||
PageRequest pageRequest) { | ||
// forever loop first page until no more to delete | ||
return pageBy(type, listOptions, pageRequest) | ||
.flatMap(page -> Flux.fromIterable(page.getItems()) | ||
.flatMap(client::delete) | ||
.then(page.hasNext() ? cleanupContinuously(type, listOptions, pageRequest) | ||
: Mono.empty()) | ||
); | ||
} | ||
|
||
@Override | ||
public <E extends Extension> Flux<E> deleteInitialBatch(Class<E> type, | ||
ListOptions listOptions) { | ||
var pageRequest = createPageRequest(); | ||
var newFieldQuery = listOptions.getFieldSelector() | ||
.andQuery(isNull("metadata.deletionTimestamp")); | ||
listOptions.setFieldSelector(newFieldQuery); | ||
|
||
final AtomicLong totalRecords = new AtomicLong(0); | ||
final AtomicLong consumedRecords = new AtomicLong(0); | ||
|
||
return pageBy(type, listOptions, pageRequest) | ||
.doOnNext(page -> totalRecords.compareAndSet(0, page.getTotal())) | ||
// forever loop first page until no more to delete | ||
.expandDeep(page -> hasMorePages(page, consumedRecords.get(), totalRecords.get()) | ||
? pageBy(type, listOptions, pageRequest) : Mono.empty()) | ||
.flatMap(page -> Flux.fromIterable(page.getItems())) | ||
.takeWhile(item -> consumedRecords.incrementAndGet() <= totalRecords.get()) | ||
.flatMap(client::delete); | ||
} | ||
|
||
private <E extends Extension> boolean hasMorePages(ListResult<E> result, long consumedRecords, | ||
long totalRecords) { | ||
return result.hasNext() && consumedRecords < totalRecords; | ||
} | ||
|
||
@Override | ||
public <E extends Extension> Flux<E> list(Class<E> type, ListOptions listOptions) { | ||
var pageRequest = createPageRequest(); | ||
return list(type, listOptions, pageRequest); | ||
} | ||
|
||
/** | ||
* Paginated list all items to avoid memory overflow. | ||
* <pre> | ||
* 1. Retrieve data multiple times until all data is consumed. | ||
* 2. Fetch next page if current page has more data and consumed records is less than total | ||
* records. | ||
* 3. Take while consumed records is less than total records. | ||
* 4. totalRecords from first page to ensure new inserted data will not be counted in during | ||
* querying to avoid infinite loop. | ||
* </pre> | ||
*/ | ||
private <E extends Extension> Flux<E> list(Class<E> type, ListOptions listOptions, | ||
PageRequest pageRequest) { | ||
final AtomicLong totalRecords = new AtomicLong(0); | ||
final AtomicLong consumedRecords = new AtomicLong(0); | ||
return pageBy(type, listOptions, pageRequest) | ||
// set total records in first page | ||
.doOnNext(page -> totalRecords.compareAndSet(0, page.getTotal())) | ||
.expandDeep(page -> { | ||
if (hasMorePages(page, consumedRecords.get(), totalRecords.get())) { | ||
// fetch next page | ||
PageRequest nextPageRequest = pageRequest.next(); | ||
return pageBy(type, listOptions, nextPageRequest); | ||
} else { | ||
return Mono.empty(); | ||
} | ||
}) | ||
.flatMap(page -> Flux.fromIterable(page.getItems())) | ||
.takeWhile(item -> consumedRecords.incrementAndGet() <= totalRecords.get()); | ||
} | ||
|
||
private PageRequest createPageRequest() { | ||
return PageRequestImpl.of(1, DEFAULT_PAGE_SIZE, | ||
Sort.by("metadata.creationTimestamp", "metadata.name")); | ||
} | ||
|
||
private <E extends Extension> Mono<ListResult<E>> pageBy(Class<E> type, ListOptions listOptions, | ||
PageRequest pageRequest) { | ||
return client.listBy(type, listOptions, pageRequest); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.