Skip to content

Commit

Permalink
refactor: optimize reply queries using index mechanisms (#5497)
Browse files Browse the repository at this point in the history
#### What type of PR is this?
/kind improvement
/area core
/milestone 2.14.x

#### What this PR does / why we need it:
使用索引机制优化回复功能的查询以提高查询速度

#### Does this PR introduce a user-facing change?
```release-note
使用索引机制优化回复功能的查询以提高查询速度
```
  • Loading branch information
guqing committed Mar 13, 2024
1 parent 956f4ef commit e704e09
Show file tree
Hide file tree
Showing 15 changed files with 410 additions and 292 deletions.
Expand Up @@ -56,13 +56,16 @@ public String getOwnerName() {
@ArraySchema(uniqueItems = true,
arraySchema = @Schema(name = "sort",
description = "Sort property and direction of the list result. Supported fields: "
+ "creationTimestamp,replyCount,lastReplyTime"),
+ "metadata.creationTimestamp,status.replyCount,status.lastReplyTime"),
schema = @Schema(description = "like field,asc or field,desc",
implementation = String.class,
example = "creationTimestamp,desc"))
public Sort getSort() {
var sort = SortResolver.defaultInstance.resolve(exchange);
return sort.and(Sort.by("spec.creationTime", "metadata.name").descending());
return sort.and(Sort.by("status.lastReplyTime",
"spec.creationTime",
"metadata.name"
).descending());
}

public PageRequest toPageRequest() {
Expand Down
@@ -1,26 +1,54 @@
package run.halo.app.content.comment;

import static run.halo.app.extension.index.query.QueryFactory.equal;
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToListOptions;

import io.swagger.v3.oas.annotations.media.Schema;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.MultiValueMap;
import org.springframework.data.domain.Sort;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.ServerWebInputException;
import run.halo.app.core.extension.content.Reply;
import run.halo.app.extension.router.IListRequest;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.PageRequest;
import run.halo.app.extension.PageRequestImpl;
import run.halo.app.extension.router.SortableRequest;

/**
* Query criteria for {@link Reply} list.
*
* @author guqing
* @since 2.0.0
*/
public class ReplyQuery extends IListRequest.QueryListRequest {
public class ReplyQuery extends SortableRequest {

public ReplyQuery(MultiValueMap<String, String> queryParams) {
super(queryParams);
public ReplyQuery(ServerWebExchange exchange) {
super(exchange);
}

@Schema(description = "Replies filtered by commentName.")
public String getCommentName() {
String commentName = queryParams.getFirst("commentName");
return StringUtils.isBlank(commentName) ? null : commentName;
if (StringUtils.isBlank(commentName)) {
throw new ServerWebInputException("The required parameter 'commentName' is missing.");
}
return commentName;
}

/**
* Build list options from query criteria.
*/
public ListOptions toListOptions() {
var listOptions =
labelAndFieldSelectorToListOptions(getLabelSelector(), getFieldSelector());
var newFieldSelector = listOptions.getFieldSelector()
.andQuery(equal("spec.commentName", getCommentName()));
listOptions.setFieldSelector(newFieldSelector);
return listOptions;
}

public PageRequest toPageRequest() {
var sort = getSort().and(Sort.by("spec.creationTime").ascending());
return PageRequestImpl.of(getPage(), getSize(), sort);
}
}
Expand Up @@ -79,9 +79,7 @@ public Mono<Reply> create(String commentName, Reply reply) {

@Override
public Mono<ListResult<ListedReply>> list(ReplyQuery query) {
return client.list(Reply.class, getReplyPredicate(query),
ReplyService.creationTimeAscComparator(),
query.getPage(), query.getSize())
return client.listBy(Reply.class, query.toListOptions(), query.toPageRequest())
.flatMap(list -> Flux.fromStream(list.get()
.map(this::toListedReply))
.concatMap(Function.identity())
Expand Down
Expand Up @@ -48,7 +48,7 @@ public RouterFunction<ServerResponse> endpoint() {
}

Mono<ServerResponse> listReplies(ServerRequest request) {
ReplyQuery replyQuery = new ReplyQuery(request.queryParams());
ReplyQuery replyQuery = new ReplyQuery(request.exchange());
return replyService.list(replyQuery)
.flatMap(listedReplies -> ServerResponse.ok().bodyValue(listedReplies));
}
Expand Down
@@ -1,5 +1,6 @@
package run.halo.app.core.extension.reconciler;

import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static run.halo.app.extension.ExtensionUtil.addFinalizers;

import java.util.Set;
Expand Down Expand Up @@ -45,6 +46,15 @@ public Result reconcile(Request request) {
eventPublisher.publishEvent(new ReplyCreatedEvent(this, reply));
}

if (reply.getSpec().getCreationTime() == null) {
reply.getSpec().setCreationTime(
defaultIfNull(reply.getSpec().getApprovedTime(),
reply.getMetadata().getCreationTimestamp()
)
);
}
client.update(reply);

replyNotificationSubscriptionHelper.subscribeNewReplyReasonForReply(reply);

eventPublisher.publishEvent(new ReplyChangedEvent(this, reply));
Expand Down
Expand Up @@ -223,7 +223,8 @@ public void onApplicationEvent(@NonNull ApplicationContextInitializedEvent event
indexSpecs.add(new IndexSpec()
.setName("spec.creationTime")
.setIndexFunc(simpleAttribute(Comment.class,
comment -> comment.getSpec().getCreationTime().toString())
comment -> defaultIfNull(comment.getSpec().getCreationTime(),
comment.getMetadata().getCreationTimestamp()).toString())
));
indexSpecs.add(new IndexSpec()
.setName("spec.approved")
Expand Down Expand Up @@ -282,7 +283,35 @@ public void onApplicationEvent(@NonNull ApplicationContextInitializedEvent event
return defaultIfNull(replyCount, 0).toString();
})));
});
schemeManager.register(Reply.class);
schemeManager.register(Reply.class, indexSpecs -> {
indexSpecs.add(new IndexSpec()
.setName("spec.creationTime")
.setIndexFunc(simpleAttribute(Reply.class,
reply -> defaultIfNull(reply.getSpec().getCreationTime(),
reply.getMetadata().getCreationTimestamp()).toString())
));
indexSpecs.add(new IndexSpec()
.setName("spec.commentName")
.setIndexFunc(simpleAttribute(Reply.class,
reply -> reply.getSpec().getCommentName())
));
indexSpecs.add(new IndexSpec()
.setName("spec.hidden")
.setIndexFunc(simpleAttribute(Reply.class,
reply -> toStringTrueFalse(isTrue(reply.getSpec().getHidden())))
));
indexSpecs.add(new IndexSpec()
.setName("spec.approved")
.setIndexFunc(simpleAttribute(Reply.class,
reply -> toStringTrueFalse(isTrue(reply.getSpec().getApproved())))
));
indexSpecs.add(new IndexSpec()
.setName("spec.owner")
.setIndexFunc(simpleAttribute(Reply.class, reply -> {
var owner = reply.getSpec().getOwner();
return Comment.CommentOwner.ownerIdentity(owner.getKind(), owner.getName());
})));
});
schemeManager.register(SinglePage.class);
// storage.halo.run
schemeManager.register(Group.class);
Expand Down
@@ -1,28 +1,35 @@
package run.halo.app.metrics;

import static org.apache.commons.lang3.BooleanUtils.isFalse;
import static org.apache.commons.lang3.BooleanUtils.isTrue;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static run.halo.app.extension.index.query.QueryFactory.and;
import static run.halo.app.extension.index.query.QueryFactory.equal;
import static run.halo.app.extension.index.query.QueryFactory.greaterThan;
import static run.halo.app.extension.index.query.QueryFactory.isNull;

import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.springframework.context.SmartLifecycle;
import org.springframework.context.event.EventListener;
import org.springframework.data.domain.Sort;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import run.halo.app.content.comment.ReplyService;
import run.halo.app.core.extension.content.Comment;
import run.halo.app.core.extension.content.Reply;
import run.halo.app.event.post.ReplyEvent;
import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.ListOptions;
import run.halo.app.extension.PageRequestImpl;
import run.halo.app.extension.controller.Controller;
import run.halo.app.extension.controller.ControllerBuilder;
import run.halo.app.extension.controller.DefaultController;
import run.halo.app.extension.controller.DefaultQueue;
import run.halo.app.extension.controller.Reconciler;
import run.halo.app.extension.controller.RequestQueue;
import run.halo.app.extension.index.query.Query;
import run.halo.app.extension.router.selector.FieldSelector;

/**
* Update the comment status after receiving the reply event.
Expand Down Expand Up @@ -54,42 +61,61 @@ public Result reconcile(ReplyEvent request) {
// if the comment has been deleted, then do nothing.
.filter(comment -> comment.getMetadata().getDeletionTimestamp() == null)
.ifPresent(comment -> {

// order by reply creation time desc to get first as last reply time
List<Reply> replies = client.list(Reply.class,
record -> commentName.equals(record.getSpec().getCommentName())
&& record.getMetadata().getDeletionTimestamp() == null,
ReplyService.creationTimeAscComparator().reversed());

Comment.CommentStatus status = comment.getStatusOrDefault();
var baseQuery = and(
equal("spec.commentName", commentName),
isNull("metadata.deletionTimestamp")
);
var pageRequest = PageRequestImpl.ofSize(1).withSort(
Sort.by("spec.creationTime", "metadata.name").descending()
);
final Comment.CommentStatus status = comment.getStatusOrDefault();

var replyPageResult =
client.listBy(Reply.class, listOptionsWithFieldQuery(baseQuery), pageRequest);
// total reply count
status.setReplyCount(replies.size());

long visibleReplyCount = replies.stream()
.filter(reply -> isTrue(reply.getSpec().getApproved())
&& isFalse(reply.getSpec().getHidden())
)
.count();
status.setVisibleReplyCount((int) visibleReplyCount);
status.setReplyCount((int) replyPageResult.getTotal());

// calculate last reply time
Instant lastReplyTime = replies.stream()
// calculate last reply time from total replies(top 1)
Instant lastReplyTime = replyPageResult.get()
.map(reply -> reply.getSpec().getCreationTime())
.findFirst()
.map(reply -> defaultIfNull(reply.getSpec().getCreationTime(),
reply.getMetadata().getCreationTimestamp())
)
.orElse(null);
status.setLastReplyTime(lastReplyTime);

Instant lastReadTime = comment.getSpec().getLastReadTime();
status.setUnreadReplyCount(Comment.getUnreadReplyCount(replies, lastReadTime));
// calculate visible reply count(only approved and not hidden)
var visibleReplyPageResult =
client.listBy(Reply.class, listOptionsWithFieldQuery(and(
baseQuery,
equal("spec.approved", BooleanUtils.TRUE),
equal("spec.hidden", BooleanUtils.FALSE)
)), pageRequest);
status.setVisibleReplyCount((int) visibleReplyPageResult.getTotal());

// calculate unread reply count(after last read time)
var unReadQuery = Optional.ofNullable(comment.getSpec().getLastReadTime())
.map(lastReadTime -> and(
baseQuery,
greaterThan("spec.creationTime", lastReadTime.toString())
))
.orElse(baseQuery);
var unReadPageResult =
client.listBy(Reply.class, listOptionsWithFieldQuery(unReadQuery), pageRequest);
status.setUnreadReplyCount((int) unReadPageResult.getTotal());

status.setHasNewReply(defaultIfNull(status.getUnreadReplyCount(), 0) > 0);

client.update(comment);
});
return new Result(false, null);
}

static ListOptions listOptionsWithFieldQuery(Query query) {
var listOptions = new ListOptions();
listOptions.setFieldSelector(FieldSelector.of(query));
return listOptions;
}

@Override
public Controller setupWith(ControllerBuilder builder) {
return new DefaultController<>(
Expand Down
@@ -1,9 +1,7 @@
package run.halo.app.theme.finders;

import java.util.Comparator;
import org.springframework.lang.Nullable;
import reactor.core.publisher.Mono;
import run.halo.app.core.extension.content.Reply;
import run.halo.app.extension.ListResult;
import run.halo.app.extension.PageRequest;
import run.halo.app.extension.Ref;
Expand All @@ -26,6 +24,5 @@ Mono<ListResult<CommentVo>> list(Ref ref, @Nullable Integer page,
Mono<ListResult<ReplyVo>> listReply(String commentName, @Nullable Integer page,
@Nullable Integer size);

Mono<ListResult<ReplyVo>> listReply(String commentName, @Nullable Integer page,
@Nullable Integer size, @Nullable Comparator<Reply> comparator);
Mono<ListResult<ReplyVo>> listReply(String commentName, PageRequest pageRequest);
}

0 comments on commit e704e09

Please sign in to comment.