speed up tree building by looking up unread counts in batches

This commit is contained in:
Athou
2026-10-30 23:15:22 +01:00
parent 4b091fb90b
commit edb4d5e178
2 changed files with 35 additions and 14 deletions

View File

@@ -29,6 +29,7 @@ import org.apache.commons.collections4.CollectionUtils;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -238,19 +239,43 @@ public class FeedEntryStatusDAO extends GenericDAO<FeedEntryStatus> {
} }
} }
public UnreadCount getUnreadCount(FeedSubscription sub) { public Map<Long, UnreadCount> getUnreadCounts(List<FeedSubscription> subs) {
if (subs.isEmpty()) {
return Map.of();
}
Map<Long, List<FeedSubscription>> subsByFeedId =
subs.stream().collect(Collectors.groupingBy(s -> s.getFeed().getId()));
JPAQuery<Tuple> query = JPAQuery<Tuple> query =
query().select(ENTRY.count(), ENTRY.published.max()) query().select(ENTRY.feed.id, ENTRY.count(), ENTRY.published.max())
.from(ENTRY) .from(ENTRY)
.leftJoin(ENTRY.statuses, STATUS) .leftJoin(ENTRY.statuses, STATUS)
.on(STATUS.subscription.eq(sub)) .on(STATUS.subscription.in(subs))
.where(ENTRY.feed.eq(sub.getFeed())) .where(ENTRY.feed.id.in(subsByFeedId.keySet()))
.where(buildUnreadPredicate()); .where(buildUnreadPredicate())
.groupBy(ENTRY.feed.id);
Tuple tuple = query.fetchOne(); setTimeout(query, config.database().queryTimeout());
Long count = tuple.get(ENTRY.count());
Instant published = tuple.get(ENTRY.published.max()); Map<Long, UnreadCount> result = new HashMap<>();
return new UnreadCount(sub.getId(), count == null ? 0 : count, published); for (Tuple tuple : query.fetch()) {
Long feedId = tuple.get(ENTRY.feed.id);
Long count = tuple.get(ENTRY.count());
Instant published = tuple.get(ENTRY.published.max());
for (FeedSubscription sub : subsByFeedId.get(feedId)) {
result.put(
sub.getId(),
new UnreadCount(sub.getId(), count == null ? 0 : count, published));
}
}
// feeds with no unread entries are absent from the query result, default them to zero
for (FeedSubscription sub : subs) {
result.putIfAbsent(sub.getId(), new UnreadCount(sub.getId(), 0, null));
}
return result;
} }
private BooleanBuilder buildUnreadPredicate() { private BooleanBuilder buildUnreadPredicate() {

View File

@@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Singleton @Singleton
@@ -115,10 +114,7 @@ public class FeedSubscriptionService {
} }
public Map<Long, UnreadCount> getUnreadCount(User user) { public Map<Long, UnreadCount> getUnreadCount(User user) {
return feedSubscriptionDAO.findAll(user).stream() return feedEntryStatusDAO.getUnreadCounts(feedSubscriptionDAO.findAll(user));
.collect(
Collectors.toMap(
FeedSubscription::getId, feedEntryStatusDAO::getUnreadCount));
} }
@SuppressWarnings("serial") @SuppressWarnings("serial")