performance

This commit is contained in:
Stefan Hardegger
2025-08-18 19:27:57 +02:00
parent 3dc02420fe
commit 8eb126a304
3 changed files with 21 additions and 16 deletions

View File

@@ -14,6 +14,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
@@ -270,12 +271,13 @@ public class StoryController {
@RequestParam(required = false) Integer minRating,
@RequestParam(required = false) Integer maxRating,
@RequestParam(required = false) String sortBy,
@RequestParam(required = false) String sortDir) {
@RequestParam(required = false) String sortDir,
@RequestParam(required = false) String facetBy) {
if (typesenseService != null) {
SearchResultDto<StorySearchDto> results = typesenseService.searchStories(
query, page, size, authors, tags, minRating, maxRating, sortBy, sortDir);
query, page, size, authors, tags, minRating, maxRating, sortBy, sortDir, facetBy);
return ResponseEntity.ok(results);
} else {
// Fallback to basic search if Typesense is not available

View File

@@ -72,7 +72,6 @@ public class TypesenseService {
new Field().name("title").type("string").facet(false).sort(true),
new Field().name("summary").type("string").facet(false).optional(true),
new Field().name("description").type("string").facet(false),
new Field().name("contentPlain").type("string").facet(false),
new Field().name("authorId").type("string").facet(true),
new Field().name("authorName").type("string").facet(true).sort(true),
new Field().name("seriesId").type("string").facet(true).optional(true),
@@ -82,7 +81,7 @@ public class TypesenseService {
new Field().name("wordCount").type("int32").facet(true).sort(true).optional(true),
new Field().name("volume").type("int32").facet(true).sort(true).optional(true),
new Field().name("createdAt").type("int64").facet(false).sort(true),
new Field().name("lastReadAt").type("int64").facet(false).sort(true).optional(true),
new Field().name("lastReadAt").type("int64").facet(false).sort(true),
new Field().name("sourceUrl").type("string").facet(false).optional(true),
new Field().name("coverPath").type("string").facet(false).optional(true)
);
@@ -206,7 +205,8 @@ public class TypesenseService {
Integer minRating,
Integer maxRating,
String sortBy,
String sortDir) {
String sortDir,
String facetBy) {
try {
long startTime = System.currentTimeMillis();
@@ -223,16 +223,19 @@ public class TypesenseService {
SearchParameters searchParameters = new SearchParameters()
.q(normalizedQuery)
.queryBy("title,description,contentPlain,authorName,seriesName,tagNames")
.queryBy("title,description,authorName,seriesName,tagNames")
.page(typesensePage)
.perPage(perPage)
.highlightFields("title,description")
.highlightStartTag("<mark>")
.highlightEndTag("</mark>")
.facetBy("tagNames,authorName,rating")
.maxFacetValues(100)
.sortBy(buildSortParameter(normalizedQuery, sortBy, sortDir));
// Only add facets if requested
if (facetBy != null && !facetBy.trim().isEmpty()) {
searchParameters.facetBy(facetBy).maxFacetValues(100);
}
logger.debug("Typesense search parameters - facetBy: {}, maxFacetValues: {}",
searchParameters.getFacetBy(), searchParameters.getMaxFacetValues());
@@ -276,7 +279,8 @@ public class TypesenseService {
logger.debug("Search result facet counts: {}", searchResult.getFacetCounts());
List<StorySearchDto> results = convertSearchResult(searchResult);
Map<String, List<FacetCountDto>> facets = processFacetCounts(searchResult);
Map<String, List<FacetCountDto>> facets = (facetBy != null && !facetBy.trim().isEmpty()) ?
processFacetCounts(searchResult) : new HashMap<>();
long searchTime = System.currentTimeMillis() - startTime;
return new SearchResultDto<>(
@@ -364,7 +368,7 @@ public class TypesenseService {
// First, get the total count of matching stories
SearchParameters countParameters = new SearchParameters()
.q(normalizedQuery)
.queryBy("title,description,contentPlain,authorName,seriesName,tagNames")
.queryBy("title,description,authorName,seriesName,tagNames")
.perPage(0); // No results, just count
// Add tag filters if provided
@@ -394,7 +398,7 @@ public class TypesenseService {
// Now get the actual story at that offset
SearchParameters storyParameters = new SearchParameters()
.q(normalizedQuery)
.queryBy("title,description,contentPlain,authorName,seriesName,tagNames")
.queryBy("title,description,authorName,seriesName,tagNames")
.page((int) (randomOffset / 250) + 1) // Calculate page (Typesense uses 1-based pages)
.perPage(250) // Get multiple results to account for offset within page
.sortBy("_text_match:desc,createdAt:desc"); // Use standard sorting
@@ -439,7 +443,6 @@ public class TypesenseService {
document.put("title", story.getTitle());
document.put("summary", story.getSummary() != null ? story.getSummary() : "");
document.put("description", story.getDescription() != null ? story.getDescription() : "");
document.put("contentPlain", story.getContentPlain() != null ? story.getContentPlain() : "");
// Author fields - required in schema, use empty string for missing values
if (story.getAuthor() != null) {
@@ -474,9 +477,9 @@ public class TypesenseService {
story.getCreatedAt().toEpochSecond(java.time.ZoneOffset.UTC) :
java.time.LocalDateTime.now().toEpochSecond(java.time.ZoneOffset.UTC));
if (story.getLastReadAt() != null) {
document.put("lastReadAt", story.getLastReadAt().toEpochSecond(java.time.ZoneOffset.UTC));
}
// Always set lastReadAt for consistent sorting - use 0 (epoch) for unread stories
document.put("lastReadAt", story.getLastReadAt() != null ?
story.getLastReadAt().toEpochSecond(java.time.ZoneOffset.UTC) : 0L);
if (story.getSourceUrl() != null) {
document.put("sourceUrl", story.getSourceUrl());