diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_2_0/3664-shorten-first-prefetch.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_2_0/3664-shorten-first-prefetch.yaml new file mode 100644 index 00000000000..0f7fd6044da --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_2_0/3664-shorten-first-prefetch.yaml @@ -0,0 +1,4 @@ +--- +type: fix +issue: 3664 +title: "Initial page loading has been optimized to reduce the number of prefetched resources. This should improve the speed of initial search queries in many cases." diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index a1f26f11fbb..ee793dbd18b 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -92,7 +92,6 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import static ca.uhn.fhir.jpa.util.QueryParameterUtils.DEFAULT_SYNC_SIZE; -import static ca.uhn.fhir.jpa.util.SearchParameterMapCalculator.isWantCount; import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java index 841db5923af..8fcaafafd23 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/SearchBuilder.java @@ -338,7 +338,8 @@ public class SearchBuilder implements ISearchBuilder { private void init(SearchParameterMap theParams, String theSearchUuid, RequestPartitionId theRequestPartitionId) { myCriteriaBuilder = myEntityManager.getCriteriaBuilder(); - myParams = theParams; + // we mutate the params. Make a private copy. + myParams = theParams.clone(); mySearchUuid = theSearchUuid; myRequestPartitionId = theRequestPartitionId; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/tasks/SearchTask.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/tasks/SearchTask.java index d818542a29e..c59fa77ec29 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/tasks/SearchTask.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/tasks/SearchTask.java @@ -581,7 +581,7 @@ public class SearchTask implements Callable { int currentlyLoaded = defaultIfNull(mySearch.getNumFound(), 0); int minWanted = 0; if (myParams.getCount() != null) { - minWanted = myParams.getCount(); + minWanted = myParams.getCount() + 1; // Always fetch one past this page, so we know if there is a next page. minWanted = Math.min(minWanted, myPagingProvider.getMaximumPageSize()); minWanted += currentlyLoaded; } diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ElasticTest.java b/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ElasticTest.java index ac0b22633cb..19e9e40acc6 100644 --- a/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ElasticTest.java +++ b/hapi-fhir-jpaserver-elastic-test-utilities/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4ElasticTest.java @@ -166,7 +166,7 @@ public class ResourceProviderR4ElasticTest extends BaseResourceProviderR4Test { Coding blood_count = new Coding("http://loinc.org", "789-8", "Erythrocytes in Blood by Automated count for code: " + (index + 1)); createObservationWithCode(blood_count); }); - HttpGet countQuery = new HttpGet(BaseResourceProviderR4Test.ourServerBase + "/Observation?code=789-8&_count=5"); + HttpGet countQuery = new HttpGet(BaseResourceProviderR4Test.ourServerBase + "/Observation?code=789-8&_count=5&_total=accurate"); myCaptureQueriesListener.clear(); try (CloseableHttpResponse response = BaseResourceProviderR4Test.ourHttpClient.execute(countQuery)) { myCaptureQueriesListener.logSelectQueriesForCurrentThread(); diff --git a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java index 4edd711f638..d63e31b249a 100644 --- a/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java +++ b/hapi-fhir-jpaserver-test-dstu2/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java @@ -58,6 +58,7 @@ import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.client.api.IGenericClient; import ca.uhn.fhir.rest.gclient.NumberClientParam; @@ -325,6 +326,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { .forResource(Organization.class) .where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")) .count(10) + .totalMode(SearchTotalModeEnum.ACCURATE) .returnBundle(Bundle.class) .execute(); assertEquals(100, found.getTotalElement().getValue().intValue()); @@ -334,10 +336,9 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test { .search() .forResource(Organization.class) .where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")) - .count(999) + .count(50) .returnBundle(Bundle.class) .execute(); - assertEquals(100, found.getTotalElement().getValue().intValue()); assertEquals(50, found.getEntry().size()); } diff --git a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index d8e3c5e1d68..867b07ab2d5 100644 --- a/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-test-dstu3/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -15,6 +15,7 @@ import ca.uhn.fhir.parser.StrictErrorHandler; import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.client.api.IClientInterceptor; @@ -649,12 +650,14 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { } ourClient.transaction().withResources(resources).prettyPrint().encodedXml().execute(); - Bundle found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).count(10).returnBundle(Bundle.class).execute(); + Bundle found = ourClient.search().forResource(Organization.class) + .where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")) + .totalMode(SearchTotalModeEnum.ACCURATE) + .count(10).returnBundle(Bundle.class).execute(); assertEquals(100, found.getTotal()); assertEquals(10, found.getEntry().size()); - found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).count(999).returnBundle(Bundle.class).execute(); - assertEquals(100, found.getTotal()); + found = ourClient.search().forResource(Organization.class).where(Organization.NAME.matches().value("rpdstu2_testCountParam_01")).count(50).returnBundle(Bundle.class).execute(); assertEquals(50, found.getEntry().size()); } diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index 3518d6488a3..867d27556a3 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -1162,7 +1162,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { myCaptureQueriesListener.clear(); ourLog.info("** About to perform search"); - IBundleProvider search = myPatientDao.search(new SearchParameterMap().setLoadSynchronous(false)); + IBundleProvider search = myPatientDao.search(new SearchParameterMap().setCount(50).setLoadSynchronous(false)); ourLog.info("** About to retrieve resources"); search.getResources(0, 20); ourLog.info("** Done retrieving resources"); @@ -1171,13 +1171,13 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { myCaptureQueriesListener.logSelectQueriesForCurrentThread(); assertEquals(4, myCaptureQueriesListener.countSelectQueries()); - // Batches of 30 are written for each query - so 9 inserts total - assertEquals(221, myCaptureQueriesListener.logInsertQueries()); + // first prefetch is 50+1 + assertEquals(51, myCaptureQueriesListener.logInsertQueries()); assertEquals(1, myCaptureQueriesListener.countUpdateQueries()); assertEquals(0, myCaptureQueriesListener.countDeleteQueries()); assertEquals(4, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); - assertEquals(9, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); + assertEquals(3, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); assertEquals(1, myCaptureQueriesListener.countUpdateQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); diff --git a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java index 31043eb153e..8a8d0fb36cb 100644 --- a/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java +++ b/hapi-fhir-jpaserver-test-r4/src/test/java/ca/uhn/fhir/jpa/provider/r4/SystemProviderR4Test.java @@ -24,6 +24,7 @@ import ca.uhn.fhir.rest.api.CacheControlDirective; import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.EncodingEnum; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SearchTotalModeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.client.apache.ResourceEntity; @@ -1001,7 +1002,10 @@ public class SystemProviderR4Test extends BaseJpaR4Test { } private Bundle getAllResourcesOfType(String theResourceName) { - return myClient.search().forResource(theResourceName).cacheControl(new CacheControlDirective().setNoCache(true)).returnBundle(Bundle.class).execute(); + return myClient.search().forResource(theResourceName) + .totalMode(SearchTotalModeEnum.ACCURATE) + .cacheControl(new CacheControlDirective().setNoCache(true)) + .returnBundle(Bundle.class).execute(); } @AfterAll diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java index f54fee6308e..a7ebf6080d2 100644 --- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java +++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/BaseJpaTest.java @@ -535,32 +535,11 @@ public abstract class BaseJpaTest extends BaseTest { protected List toUnqualifiedVersionlessIds(IBundleProvider theProvider) { List retVal = new ArrayList<>(); - Integer size = theProvider.size(); - StopWatch sw = new StopWatch(); - while (size == null) { - int timeout = 20000; - if (sw.getMillis() > timeout) { - String message = "Waited over " + timeout + "ms for search " + theProvider.getUuid(); - ourLog.info(message); - fail(message); - } - try { - Thread.sleep(100); - } catch (InterruptedException theE) { - //ignore - } - if (theProvider instanceof PersistedJpaBundleProvider) { - PersistedJpaBundleProvider provider = (PersistedJpaBundleProvider) theProvider; - provider.clearCachedDataForUnitTest(); - } - size = theProvider.size(); - } + Integer size = theProvider.size(); ourLog.info("Found {} results", size); - List resources = theProvider instanceof PersistedJpaBundleProvider ? - theProvider.getResources(0, size) : - theProvider.getResources(0, Integer.MAX_VALUE); + List resources = theProvider.getResources(0, Integer.MAX_VALUE/4); for (IBaseResource next : resources) { retVal.add(next.getIdElement().toUnqualifiedVersionless()); } diff --git a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java index 9bf77f6baa9..d82d55cdd76 100644 --- a/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java +++ b/hapi-fhir-storage/src/main/java/ca/uhn/fhir/jpa/api/config/DaoConfig.java @@ -183,7 +183,11 @@ public class DaoConfig { private int myExpungeThreadCount; private Set myBundleTypesAllowedForStorage; private boolean myValidateSearchParameterExpressionsOnSave = true; - private List mySearchPreFetchThresholds = Arrays.asList(500, 2000, -1); + + // start with a tiny number so our first page always loads quickly. + // If they fetch the second page, fetch more. + // Use prime sizes to avoid empty next links. + private List mySearchPreFetchThresholds = Arrays.asList(13, 503, 2003, -1); private List myWarmCacheEntries = new ArrayList<>(); private boolean myDisableHashBasedSearches; private boolean myEnableInMemorySubscriptionMatching = true;