diff --git a/code/services-application/search-service/java/nu/marginalia/search/svc/SearchSiteInfoService.java b/code/services-application/search-service/java/nu/marginalia/search/svc/SearchSiteInfoService.java
index 8f92af71..d06d5530 100644
--- a/code/services-application/search-service/java/nu/marginalia/search/svc/SearchSiteInfoService.java
+++ b/code/services-application/search-service/java/nu/marginalia/search/svc/SearchSiteInfoService.java
@@ -26,6 +26,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
+import java.time.Duration;
+import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -69,6 +71,8 @@ public class SearchSiteInfoService {
this.searchSiteSubscriptions = searchSiteSubscriptions;
}
+ private volatile SiteOverviewModel model = new SiteOverviewModel(List.of(), Instant.EPOCH);
+
@GET
@Path("/site")
public ModelAndView> handleOverview(@QueryParam String domain) {
@@ -77,6 +81,27 @@ public class SearchSiteInfoService {
return new MapModelAndView("redirect.jte", Map.of("url", "/site/"+domain));
}
+ if (model.age().compareTo(Duration.ofMinutes(15)) > 0) {
+ updateModel();
+ }
+
+ return new MapModelAndView("siteinfo/start.jte",
+ Map.of("navbar", NavbarModel.SITEINFO,
+ "model", model));
+ }
+
+ /** Update the model if it is older than 15 minutes.
+ * This query is expensive and should not be run too often,
+ * and the data doesn't change that often either.
+ *
+ * This method is synchronized to avoid multiple threads updating the model at the same time.
+ */
+ private synchronized void updateModel() {
+ var currentModel = model;
+ if (currentModel.age().compareTo(Duration.ofMinutes(15)) < 0) {
+ return;
+ }
+
List domains = new ArrayList<>();
try (var conn = dataSource.getConnection();
@@ -91,13 +116,20 @@ public class SearchSiteInfoService {
throw new RuntimeException();
}
- return new MapModelAndView("siteinfo/start.jte",
- Map.of("navbar", NavbarModel.SITEINFO,
- "model", new SiteOverviewModel(domains)));
+ model = new SiteOverviewModel(domains);
}
- public record SiteOverviewModel(List domains) {
+ public record SiteOverviewModel(List domains, Instant captureTime) {
+
+ public SiteOverviewModel(List domains) {
+ this(domains, Instant.now());
+ }
+
public record DiscoveredDomain(String name, String timestamp) {}
+
+ public Duration age() {
+ return Duration.between(captureTime, Instant.now());
+ }
}
@GET
diff --git a/code/services-application/search-service/resources/jte/siteinfo/view/overview.jte b/code/services-application/search-service/resources/jte/siteinfo/view/overview.jte
index 91e84a12..c3b86f4f 100644
--- a/code/services-application/search-service/resources/jte/siteinfo/view/overview.jte
+++ b/code/services-application/search-service/resources/jte/siteinfo/view/overview.jte
@@ -81,35 +81,6 @@
@endif
-
- @if (!siteInfo.siblingDomains().isEmpty())
-
-
- Related Subdomains
-
-
-
-
-
- Domain Name |
-
-
-
- @for (DbDomainQueries.DomainWithNode sibling : siteInfo.siblingDomains())
-
-
- ${sibling.domain().toString()}
-
- @if (!sibling.isIndexed())
-
- @endif
- |
-
- @endfor
-
-
- @endif
-
@if (siteInfo.domainInformation().isUnknownDomain())
@@ -178,6 +149,36 @@
@endif
+
+ @if (!siteInfo.siblingDomains().isEmpty())
+
+
+ Related Subdomains
+
+
+
+
+
+ Domain Name |
+
+
+
+ @for (DbDomainQueries.DomainWithNode sibling : siteInfo.siblingDomains())
+
+
+ ${sibling.domain().toString()}
+
+ @if (!sibling.isIndexed())
+
+ @endif
+ |
+
+ @endfor
+
+
+ @endif
+
+
@if (siteInfo.isKnown())