Add support for Database Partition Mode

This commit is contained in:
James Agnew
2025-02-12 10:54:38 -05:00
parent b3761a499e
commit 509d5ffce0
8 changed files with 248 additions and 29 deletions

View File

@@ -175,11 +175,11 @@ public class AppProperties {
this.cr_enabled = cr_enabled;
}
public Boolean getIps_enabled() {
public Boolean getIps_enabled() {
return ips_enabled;
}
public void setIps_enabled(Boolean ips_enabled) {
public void setIps_enabled(Boolean ips_enabled) {
this.ips_enabled = ips_enabled;
}
@@ -810,8 +810,36 @@ public Cors getCors() {
private Boolean partitioning_include_in_search_hashes = false;
private Boolean allow_references_across_partitions = false;
private Boolean conditional_create_duplicate_identifiers_enabled = false;
private Boolean database_partition_mode_enabled = false;
private Boolean patient_id_partitioning_mode = false;
private Integer default_partition_id = 0;
private boolean request_tenant_partitioning_mode;
public Boolean getPartitioning_include_in_search_hashes() {
public Integer getDefault_partition_id() {
return default_partition_id;
}
public void setDefault_partition_id(Integer theDefault_partition_id) {
default_partition_id = theDefault_partition_id;
}
public Boolean getDatabase_partition_mode_enabled() {
return database_partition_mode_enabled;
}
public void setDatabase_partition_mode_enabled(Boolean theDatabase_partition_mode_enabled) {
database_partition_mode_enabled = theDatabase_partition_mode_enabled;
}
public Boolean getPatient_id_partitioning_mode() {
return patient_id_partitioning_mode;
}
public void setPatient_id_partitioning_mode(Boolean thePatient_id_partitioning_mode) {
patient_id_partitioning_mode = thePatient_id_partitioning_mode;
}
public Boolean getPartitioning_include_in_search_hashes() {
return partitioning_include_in_search_hashes;
}
@@ -833,6 +861,10 @@ public Cors getCors() {
public void setConditional_create_duplicate_identifiers_enabled(Boolean conditional_create_duplicate_identifiers_enabled) {
this.conditional_create_duplicate_identifiers_enabled = conditional_create_duplicate_identifiers_enabled;
}
public boolean getRequest_tenant_partitioning_mode() {
return request_tenant_partitioning_mode;
}
}
public static class Subscription {

View File

@@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.starter.cdshooks;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.i18n.Msg;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.rest.client.api.IClientInterceptor;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.BearerTokenAuthInterceptor;
@@ -53,8 +54,9 @@ public class ModuleConfigurationPrefetchSvc extends CdsPrefetchSvc {
public ModuleConfigurationPrefetchSvc(CdsResolutionStrategySvc theCdsResolutionStrategySvc,
CdsPrefetchDaoSvc theResourcePrefetchDao,
CdsPrefetchFhirClientSvc theResourcePrefetchFhirClient,
ICdsHooksDaoAuthorizationSvc theCdsHooksDaoAuthorizationSvc) {
super(theCdsResolutionStrategySvc, theResourcePrefetchDao, theResourcePrefetchFhirClient, theCdsHooksDaoAuthorizationSvc);
ICdsHooksDaoAuthorizationSvc theCdsHooksDaoAuthorizationSvc,
IInterceptorBroadcaster theInterceptorBroadcaster) {
super(theCdsResolutionStrategySvc, theResourcePrefetchDao, theResourcePrefetchFhirClient, theCdsHooksDaoAuthorizationSvc, theInterceptorBroadcaster);
myResourcePrefetchFhirClient = theResourcePrefetchFhirClient;
fhirContext = theResourcePrefetchDao.getFhirContext();
}

View File

@@ -1,13 +1,17 @@
package ca.uhn.fhir.jpa.starter.common;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.binary.api.IBinaryStorageSvc;
import ca.uhn.fhir.jpa.binstore.DatabaseBinaryContentStorageSvcImpl;
import ca.uhn.fhir.jpa.config.HibernatePropertiesProvider;
import ca.uhn.fhir.jpa.interceptor.PatientIdPartitionInterceptor;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.model.config.PartitionSettings.CrossPartitionReferenceMode;
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
import ca.uhn.fhir.jpa.model.entity.StorageSettings;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.starter.AppProperties;
import ca.uhn.fhir.jpa.starter.util.JpaHibernatePropertiesProvider;
import ca.uhn.fhir.jpa.subscription.match.deliver.email.EmailSenderImpl;
@@ -27,6 +31,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.util.HashSet;
import java.util.stream.Collectors;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
/**
* This is the primary configuration file for the example server
*/
@@ -171,7 +177,7 @@ public class FhirServerConfigCommon {
jpaStorageSettings.setFilterParameterEnabled(appProperties.getFilter_search_enabled());
jpaStorageSettings.setAdvancedHSearchIndexing(appProperties.getAdvanced_lucene_indexing());
jpaStorageSettings.setHibernateSearchIndexSearchParams(appProperties.getAdvanced_lucene_indexing());
jpaStorageSettings.setTreatBaseUrlsAsLocal(new HashSet<>(appProperties.getLocal_base_urls()));
jpaStorageSettings.setTreatReferencesAsLogical(new HashSet<>(appProperties.getLogical_urls()));
@@ -233,6 +239,7 @@ public class FhirServerConfigCommon {
return new YamlPropertySourceLoader();
}
@Bean
public PartitionSettings partitionSettings(AppProperties appProperties) {
PartitionSettings retVal = new PartitionSettings();
@@ -240,6 +247,13 @@ public class FhirServerConfigCommon {
// Partitioning
if (appProperties.getPartitioning() != null) {
retVal.setPartitioningEnabled(true);
boolean databasePartitionModeEnabled = defaultIfNull(appProperties.getPartitioning().getDatabase_partition_mode_enabled(), Boolean.FALSE);
Integer defaultPartitionId = appProperties.getPartitioning().getDefault_partition_id();
if (databasePartitionModeEnabled) {
retVal.setDatabasePartitionMode(true);
defaultPartitionId = defaultIfNull(defaultPartitionId, 0);
}
retVal.setDefaultPartitionId(defaultPartitionId);
retVal.setIncludePartitionInSearchHashes(
appProperties.getPartitioning().getPartitioning_include_in_search_hashes());
if (appProperties.getPartitioning().getAllow_references_across_partitions()) {
@@ -251,9 +265,16 @@ public class FhirServerConfigCommon {
appProperties.getPartitioning().getConditional_create_duplicate_identifiers_enabled());
}
return retVal;
}
@Bean
public PartitionModeConfigurer partitionModeConfigurer() {
return new PartitionModeConfigurer();
}
@Primary
@Bean
public HibernatePropertiesProvider jpaStarterDialectProvider(

View File

@@ -0,0 +1,53 @@
package ca.uhn.fhir.jpa.starter.common;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.interceptor.PatientIdPartitionInterceptor;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
import ca.uhn.fhir.jpa.starter.AppProperties;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
public class PartitionModeConfigurer {
private static final Logger ourLog = LoggerFactory.getLogger(PartitionModeConfigurer.class);
@Autowired
private AppProperties myAppProperties;
@Autowired
private FhirContext myFhirContext;
@Autowired
private ISearchParamExtractor mySearchParamExtractor;
@Autowired
private PartitionSettings myPartitionSettings;
@Autowired
private RestfulServer myRestfulServer;
@Autowired
private PartitionManagementProvider myPartitionManagementProvider;
@PostConstruct
public void start() {
if (myAppProperties.getPartitioning() != null) {
if (myAppProperties.getPartitioning().getPatient_id_partitioning_mode() == Boolean.TRUE) {
ourLog.info("Partitioning mode enabled in: Patient ID partitioning mode");
PatientIdPartitionInterceptor patientIdInterceptor = new PatientIdPartitionInterceptor(myFhirContext, mySearchParamExtractor, myPartitionSettings);
myRestfulServer.registerInterceptor(patientIdInterceptor);
myPartitionSettings.setUnnamedPartitionMode(true);
} else if (myAppProperties.getPartitioning().getRequest_tenant_partitioning_mode() == Boolean.TRUE) {
ourLog.info("Partitioning mode enabled in: Request tenant partitioning mode");
myRestfulServer.registerInterceptor(new RequestTenantPartitionInterceptor());
myRestfulServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
}
myRestfulServer.registerProviders(myPartitionManagementProvider);
}
}
}

View File

@@ -1,12 +1,9 @@
package ca.uhn.fhir.jpa.starter.common;
import ca.uhn.fhir.batch2.config.Batch2JobRegisterer;
import ca.uhn.fhir.batch2.coordinator.JobDefinitionRegistry;
import ca.uhn.fhir.batch2.jobs.export.BulkDataExportProvider;
import ca.uhn.fhir.batch2.jobs.imprt.BulkDataImportProvider;
import ca.uhn.fhir.batch2.jobs.reindex.ReindexJobParameters;
import ca.uhn.fhir.batch2.jobs.reindex.ReindexProvider;
import ca.uhn.fhir.batch2.model.JobDefinition;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
@@ -21,7 +18,6 @@ import ca.uhn.fhir.jpa.binary.interceptor.BinaryStorageInterceptor;
import ca.uhn.fhir.jpa.binary.provider.BinaryAccessProvider;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
import ca.uhn.fhir.jpa.config.util.ResourceCountCacheUtil;
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.search.HSearchSortHelperImpl;
@@ -35,9 +31,14 @@ import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
import ca.uhn.fhir.jpa.model.config.SubscriptionSettings;
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
import ca.uhn.fhir.jpa.packages.PackageInstallationSpec;
import ca.uhn.fhir.jpa.partition.PartitionManagementProvider;
import ca.uhn.fhir.jpa.provider.DaoRegistryResourceSupportedSvc;
import ca.uhn.fhir.jpa.provider.IJpaSystemProvider;
import ca.uhn.fhir.jpa.provider.JpaCapabilityStatementProvider;
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.provider.ValueSetOperationProvider;
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
import ca.uhn.fhir.jpa.provider.*;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
@@ -49,23 +50,29 @@ import ca.uhn.fhir.jpa.starter.ig.IImplementationGuideOperationProvider;
import ca.uhn.fhir.jpa.starter.util.EnvironmentHelper;
import ca.uhn.fhir.jpa.subscription.util.SubscriptionDebugLogInterceptor;
import ca.uhn.fhir.jpa.util.ResourceCountCache;
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChain;
import ca.uhn.fhir.mdm.provider.MdmProviderLoader;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.narrative2.NullNarrativeGenerator;
import ca.uhn.fhir.rest.api.IResourceSupportedSvc;
import ca.uhn.fhir.rest.openapi.OpenApiInterceptor;
import ca.uhn.fhir.rest.server.interceptor.partition.RequestTenantPartitionInterceptor;
import ca.uhn.fhir.rest.server.*;
import ca.uhn.fhir.rest.server.interceptor.*;
import ca.uhn.fhir.rest.server.ApacheProxyAddressStrategy;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IServerConformanceProvider;
import ca.uhn.fhir.rest.server.IncomingRequestAddressStrategy;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
import ca.uhn.fhir.rest.server.provider.ResourceProviderFactory;
import ca.uhn.fhir.rest.server.tenant.UrlBaseTenantIdentificationStrategy;
import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import com.google.common.base.Strings;
import jakarta.persistence.EntityManagerFactory;
import org.hl7.fhir.common.hapi.validation.support.CachingValidationSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
@@ -73,15 +80,24 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.http.HttpHeaders;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.web.cors.CorsConfiguration;
import java.util.*;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import static ca.uhn.fhir.jpa.starter.common.validation.IRepositoryValidationInterceptorFactory.ENABLE_REPOSITORY_VALIDATING_INTERCEPTOR;
@@ -267,7 +283,6 @@ public class StarterJpaConfig {
BulkDataImportProvider bulkDataImportProvider,
ValueSetOperationProvider theValueSetOperationProvider,
ReindexProvider reindexProvider,
PartitionManagementProvider partitionManagementProvider,
Optional<RepositoryValidatingInterceptor> repositoryValidatingInterceptor,
IPackageInstallerSvc packageInstallerSvc,
ThreadSafeResourceDeleterSvc theThreadSafeResourceDeleterSvc,
@@ -438,12 +453,7 @@ public class StarterJpaConfig {
// reindex Provider $reindex
fhirServer.registerProvider(reindexProvider);
// Partitioning
if (appProperties.getPartitioning() != null) {
fhirServer.registerInterceptor(new RequestTenantPartitionInterceptor());
fhirServer.setTenantIdentificationStrategy(new UrlBaseTenantIdentificationStrategy());
fhirServer.registerProviders(partitionManagementProvider);
}
// Validation
repositoryValidatingInterceptor.ifPresent(fhirServer::registerInterceptor);
// register custom interceptors