Introduce DataSourceResolver for multi-datasource support in JDBC per…#3960
Introduce DataSourceResolver for multi-datasource support in JDBC per…#3960Subham-KRLX wants to merge 11 commits intoapache:mainfrom
Conversation
|
@Subham-KRLX : thanks for the proposal... I'll review presently, but please fix conflicts to allow CI to run. |
…sistence This commit introduces the DataSourceResolver interface and a DefaultDataSourceResolver implementation to enable flexible routing of DataSources based on realm and store type (e.g., main, metrics, events). Key changes: - New DataSourceResolver interface with resolve(realmId, storeType) and getAllUniqueDataSources() methods - DefaultDataSourceResolver that routes all requests to the default DataSource (backward-compatible no-op) - Refactored JdbcMetaStoreManagerFactory to use DataSourceResolver instead of direct DataSource injection This lays the groundwork for isolating different persistence workloads (entity metadata vs metrics vs events) into separate connection pools or databases to mitigate noisy neighbor effects. Closes apache#3890
c082f09 to
136e381
Compare
dimas-b
left a comment
There was a problem hiding this comment.
Very nice idea, @Subham-KRLX ! +1 to this approach in general.
Posting quite a few comments, but they are just to "streamline" the code (I hope).
| */ | ||
| public interface DataSourceResolver { | ||
|
|
||
| String STORE_TYPE_MAIN = "main"; |
There was a problem hiding this comment.
I have refactored the store types into the StoreType enum as suggested. This makes the resolution logic much cleaner and more type-safe.
| * | ||
| * @return a set of all DataSources | ||
| */ | ||
| Set<DataSource> getAllUniqueDataSources(); |
There was a problem hiding this comment.
It's fine to have the "list all" method, but I tend to think it needs a separate interface because it's a different use case.
Specifically, providing a DataSource per realm does not have to be co-location with the code to list all data sources. The former is a Server runtime concerns, the later is more of an Admin tool concern.
|
|
||
| private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class); | ||
|
|
||
| private final Instance<DataSource> defaultDataSource; |
There was a problem hiding this comment.
Does this have to be an Instance? Why not use the plain DataSource as type here?
.../src/main/java/org/apache/polaris/persistence/relational/jdbc/DefaultDataSourceResolver.java
Show resolved
Hide resolved
- Refactored StoreType to an enum in DataSourceResolver - Removed getAllUniqueDataSources() from DataSourceResolver (Admin use case) - Moved DefaultDataSourceResolver to runtime/common/config/jdbc - Inject plain DataSource instead of Instance<DataSource> in DefaultDataSourceResolver - Updated JdbcMetaStoreManagerFactory to use the new StoreType enum
|
@dimas-b thanks for the review I have streamlined the implementation based on your feedback by refactoring StoreType into an enum within DataSourceResolver and removing the getAllUniqueDataSources() method to keep the interface focused strictly on runtime resolution. Additionally I moved DefaultDataSourceResolver |
dimas-b
left a comment
There was a problem hiding this comment.
A couple more comments, but LGTM overall 👍
@Subham-KRLX : Do you want to remove the "draft" status and open this for general review?
I'd suggest to also send an email about this to dev for visibility.
|
|
||
| /** The type of store representing the workload pattern. */ | ||
| enum StoreType { | ||
| MAIN, |
|
|
||
| protected JdbcMetaStoreManagerFactory() {} | ||
| @Inject | ||
| Clock clock; |
There was a problem hiding this comment.
nit: I wonder if the Spotless is going to complain about this 😅
| * @param storeType the type of store (e.g., main, metrics, events) | ||
| * @return the resolved DataSource | ||
| */ | ||
| DataSource resolve(String realmId, StoreType storeType); |
There was a problem hiding this comment.
I'm thinking that we may want to use RealmContext instead of String realm ID here... just to continue the pattern of using RealmContext in pluggable components... e.g. RealmConfigurationSource , TokenBucketFactory, etc. 🤔
There was a problem hiding this comment.
I have applied the final round of nits: renamed MAIN to METASTORE, switched to RealmContext in resolve()
for consistency, and fixed the @Inject field formatting. I'm also sending an email to dev@polaris.apache.org
now to give this more visibility.
- Renamed StoreType.MAIN to METASTORE - Use RealmContext instead of String realmId in DataSourceResolver.resolve - Added missing polaris-core dependency to runtime-common - Fixed formatting of @Inject fields for Spotless
| * @return the resolved DataSource | ||
| */ | ||
| DataSource resolve( | ||
| org.apache.polaris.core.context.RealmContext realmContext, StoreType storeType); |
There was a problem hiding this comment.
@dimas-b I have pushed an update to resolve the CI failures. It turned out to be a CDI injection issue with the @Identifier annotation that I have now resolved.
|
Thanks for the approval @dimas-b. I have fixed that last import nit and have sent the proposal email to the dev list [1] for broader visibility as you suggested. Appreciate all the guidance on streamlining this. |
| * @param storeType the type of store (e.g., main, metrics, events) | ||
| * @return the resolved DataSource | ||
| */ | ||
| DataSource resolve(RealmContext realmContext, StoreType storeType); |
There was a problem hiding this comment.
I'm supportive on realm, but we will need to think about the storage type.
There was a problem hiding this comment.
@flyrain Could you clarify your concerns regarding StoreType? Are you suggesting a more extensible approach (e.g., String identifiers) or perhaps routing at a different layer? Happy to refine this to better fit the project's direction.
|
@Subham-KRLX : the PR LGTM, but please check CI failures out 😅 |
|
@dimas-b @flyrain I’ve pushed an update to fix the CI failures in the non-JDBC test suites. The root cause was DefaultDataSourceResolver unconditionally injecting a JDBC DataSource bean, which caused an io.quarkus.arc.InactiveBeanException when booting profiles where JDBC is disabled (like NoSQL/Mongo). I’ve updated the implementation to use Instance for lazy resolution, allowing the service to boot safely across all persistence modes. All CI checks should now pass cleanly. |
Current code in this PR LGTM, however, I still wonder why would @Subham-KRLX : Could you explore this a bit more? Perhaps |
|
As I said in the mailing thread https://lists.apache.org/thread/nokyythvrdzsfwz26hx0w5rpxrw5wjtd, I think this effort deserve more thoughts before we start coding. cc @singhpk234 |
| * single default {@link DataSource}. This serves as both the production default and the base for | ||
| * multi-datasource extensions. | ||
| */ | ||
| @ApplicationScoped |
There was a problem hiding this comment.
IIUC, the current extension story is still a bit fragile here. This default resolver is just an unqualified @ApplicationScoped bean, while the factory later resolves DataSourceResolver via Instance<DataSourceResolver>.get(). Would a custom unqualified resolver now turn this into ambiguous CDI resolution rather than a clean override? If this is meant to be a public SPI, I wonder if we should make the replacement model explicit up front.
There was a problem hiding this comment.
Whoever introduces an alternative implementation for DataSourceResolver should provide a custom producer method or annotate the preferred bean with a higher @Priority than this default.
This is a common pattern in Polaris in a few other extension points.
There was a problem hiding this comment.
I'd rather use @Identifier.
There was a problem hiding this comment.
Ok, let' use @Identifier. In this case, I suppose @ApplicationScoped will move to the producer method, which will resolve the identifier based on runtime config.
| @Inject PolarisStorageIntegrationProvider storageIntegrationProvider; | ||
| @Inject Instance<DataSource> dataSource; | ||
|
|
||
| @Inject Instance<DataSourceResolver> dataSourceResolver; |
There was a problem hiding this comment.
Why is this injected as Instance<DataSourceResolver> instead of a plain DataSourceResolver? In the current code we unconditionally call .get(), so it seems we are paying the runtime ambiguity cost without really using dynamic selection. Unless we expect multiple qualified resolvers here, would direct injection give us a cleaner and safer contract?
| * physical databases or connection pools. | ||
| */ | ||
| public interface DataSourceResolver { | ||
|
|
There was a problem hiding this comment.
I wonder if this contract is getting a bit ahead of the implementation. This PR only wires METASTORE, but the SPI already publishes METRICS and EVENTS as if those routing modes were part of the current supported surface. Would it make sense to keep the first step narrower and add new StoreType values only once the corresponding paths are actually routed through this resolver?
| import org.apache.polaris.core.context.RealmContext; | ||
|
|
||
| /** | ||
| * Service to resolve the correct {@link DataSource} for a given realm and store type. This enables |
There was a problem hiding this comment.
IIUC, this Javadoc is describing the longer-term shape more than what the PR actually does today. Right now this is a metastore-routing foundation, not a system-wide metadata/metrics/events routing layer. I wonder if tightening the wording to match the current behavior would make the contract less confusing for future implementors/readers.
There was a problem hiding this comment.
Thanks for the feedback @flyingImer I completely agree. I have pushed an update that narrows the scope of this first step to strictly cover METASTORE routing, completely removing METRICS and EVENTS from StoreType and updating the Javadoc to reflect this tighter scope. I also removed the unnecessary runtime ambiguity by injecting DataSourceResolver directly instead of Instance. Finally, I added @DefaultBean to DefaultDataSourceResolver so downstream users can cleanly override the routing logic without ambiguous CDI resolution errors.
Hi @dimas-b the root issue is Quarkus ArC's eager boot-time validation.Since DefaultDataSourceResolver |
|
@Subham-KRLX : thanks for your investigation into CDI startup behaviour (above) 👍 |
| * provide their own {@link DataSourceResolver} bean to implement custom routing logic. | ||
| */ | ||
| @ApplicationScoped | ||
| @DefaultBean |
There was a problem hiding this comment.
Hmm I'm torn on @DefaultBean. It's not a bad idea per se, but 1) it's a Quarkus-specific, non-standard CDI extension and 2) in Polaris so far we've been using @Identifier instead, and at runtime we would select the bean via a producer method + a configuration option.
There was a problem hiding this comment.
@Identifier works too. Yet, in this PR we do not have any alternatives, so using no qualifiers should work too, AFAIK.
Custom implementations can use @Alternative + @Priority to override.
|
|
||
| /** The type of store representing the workload pattern. */ | ||
| enum StoreType { | ||
| METASTORE |
There was a problem hiding this comment.
Sorry, I might have missed this before, but this enum appears to be rather confusing now because JdbcBasePersistenceImpl implements both the MetaStore interfaces and MetricsPersistence.
Given the current state of the JDBC-related code I'd prefer to remove this parameter completely.
We can add it late if/when MetricsPersistence is isolated from MetaStore Persistence.
There was a problem hiding this comment.
Agreed, StoreType has been removed entirely in the latest push. As you noted, it doesn't add value while JdbcBasePersistenceImpl handles both MetaStore and MetricsPersistence. We can reintroduce it once the workloads are actually isolated.
| * provide their own {@link DataSourceResolver} bean to implement custom routing logic. | ||
| */ | ||
| @ApplicationScoped | ||
| @DefaultBean |
There was a problem hiding this comment.
Hmm I'm torn on @DefaultBean. It's not a bad idea per se, but 1) it's a Quarkus-specific, non-standard CDI extension and 2) in Polaris so far we've been using @Identifier instead, and at runtime we would select the bean via a producer method + a configuration option.
|
|
||
| private final Instance<DataSource> defaultDataSource; | ||
|
|
||
| @Inject |
There was a problem hiding this comment.
You are probably missing the @Any annotation here.
But more broadly: if this impl can only handle one datasource, the default one, why not just inject it directly?
There was a problem hiding this comment.
IIRC, there were some issues with bean not being available in non-JDBC situations (e.g. tests).... that's how I interpret @Subham-KRLX 's message : #3960 (comment)
| */ | ||
| @ApplicationScoped | ||
| @DefaultBean | ||
| public class DefaultDataSourceResolver implements DataSourceResolver { |
There was a problem hiding this comment.
I'm not sure I understand why this class was placed in runtime/common. This creates a strange inheritance. This class could live in polaris-relational-jdbc alongside its interface. That module already has application-scoped beans. (The only thing it doesn't have is the @DefaultBean annotation – see my other comment.)
There was a problem hiding this comment.
Actually the question of where to place implementations of this interface is a tricky one.
When designing a true multi-datasource implementation, it will need the Quarkus Agroal extension, and therefore, would likely have to live in runtime/service.
It would look like this:
@ApplicationScoped
@Identifier("per-realm")
public class PerRealmDataSourceResolver implements DataSourceResolver {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultDataSourceResolver.class);
@Override
public DataSource resolve(RealmContext realmContext, StoreType storeType) {
String dataSourceName = findDataSourceName(realmContext, storeType);
LOGGER.debug(
"Using DataSource '{}' for realm '{}' and store '{}'",
dataSourceName,
realmContext.getRealmIdentifier(),
storeType);
return AgroalDataSourceUtil.dataSourceIfActive(dataSourceName)
.orElseThrow(
() ->
new IllegalStateException(
"DataSource '" + dataSourceName + "' is not active or does not exist"));
}
private String findDataSourceName(RealmContext realmContext, StoreType storeType) {
...
}
}But the problem is that polaris-relational-jdbc is not in the compile classpath of runtime/service, only in its runtime classpath... Another option is to add the quarkus-agroal extension to polaris-relational-jdbc and make it a quarkus module.
I think this needs some more thinking.
There was a problem hiding this comment.
How about we add a new module like polaris-relational-jdbc-runtime with Quarkus + Agroal dependencies?
The idea is for polaris-relational-jdbc to remain free from Quarkus-specific dependencies (Jakarta annotations are ok). IIRC, we tried doing something like that with JDBC persistence initially, but it probably did not materialize completely 😅
There was a problem hiding this comment.
If we use @Identifier for selecting a specific DataSourceResolver impl., I suppose the producer code will go into polaris-relational-jdbc-runtime as well.
Downstream alternatives will have the the option of reusing polaris-relational-jdbc-runtime and configuring a custom identifier value, or not including polaris-relational-jdbc-runtime and making a custom producer.
There was a problem hiding this comment.
Do we have a compelling reason not to turn polaris-relational-jdbc into a Quarkus module? But if we do, I'm OK with polaris-relational-jdbc-runtime.
There was a problem hiding this comment.
Only "general principles" kind of reason 🙂
I tend to think it valuable to isolate runtime / CDI concerns from the basic code / logic of a particular component like JDBC persistence. In that regard, ideally, polaris-relational-jdbc could be reused in any runtime env. (Quarkus or something else). polaris-relational-jdbc-runtime would have the code specific to the way Polaris uses Quarkus.
I believe the NoSQL Persistence impl and the OPA Authorizer follow similar principles (perhaps varying in degree, but similar in essence). I also proposed a similar refactoring for the Admin tool in #3947.
I do not really know whether this matters for downstream Polaris users right now 🤔 🤷 In my personal experience, I find that it is much easier to include another module into a build than struggle with excluding intruding dependencies 😅
I know some concerns were raised about module proliferation in Polaris, but from my POV, Gradle is able to deal with a large number of modules very well, so I personally do not see it as an issue.
There was a problem hiding this comment.
Looking at the evolution of this PR (and this class specifically), I think it's probably fine to move CDI/quarkus-related code into the polaris-relational-jdbc module.
| * single default {@link DataSource}. This serves as both the production default and the base for | ||
| * multi-datasource extensions. | ||
| */ | ||
| @ApplicationScoped |
There was a problem hiding this comment.
I'd rather use @Identifier.
…pattern - Removed StoreType enum from DataSourceResolver interface - Simplified resolve() to accept only RealmContext - Moved DefaultDataSourceResolver back to polaris-relational-jdbc - Replaced @DefaultBean with @Identifier("polaris") per project convention - Added @Any to Instance<DataSource> injection - Added DataSourceResolver producer in ServiceProducers - Added dataSourceResolver config property to PersistenceConfiguration - Updated all getDatasourceOperations() call sites
|
@adutra @dimas-b @flyingImer Thanks for the detailed feedback! I've pushed an update that addresses all comments. StoreType has been removed entirely since JdbcBasePersistenceImpl currently handles both MetaStore and MetricsPersistence — we can reintroduce it once workloads are actually isolated. @DefaultBean has been replaced with @Identifier("polaris") and a producer method in ServiceProducers, following the standard Polaris pattern for pluggable components. DefaultDataSourceResolver now lives in polaris-relational-jdbc alongside its interface, and JdbcMetaStoreManagerFactory injects DataSourceResolver directly. I kept Instance (with @Any) in the default resolver to avoid InactiveBeanException in non-JDBC profiles. |
| */ | ||
| @ApplicationScoped | ||
| @DefaultBean | ||
| @Identifier("polaris") |
There was a problem hiding this comment.
Let's use @Identifier("default") instead.
| * use. Only applicable when using JDBC persistence. | ||
| */ | ||
| @WithDefault("polaris") | ||
| String dataSourceResolver(); |
There was a problem hiding this comment.
You don't need this new method, just use the existing type() method above.
There was a problem hiding this comment.
Oh sorry - I read too quickly 😅
But to align with other similar cases, it would be good to turn this into polaris.persistence.datasource-resolver.type:
@WithDefault("default")
@WithName("polaris.persistence.datasource-resolver.type")
String dataSourceResolver();There was a problem hiding this comment.
@adutra @dimas-b I will rename the identifier to "default" and update the config property to polaris.persistence.datasource-resolver.type as suggested. Regarding the broader discussion on module placement — I agree a dedicated polaris-relational-jdbc-runtime module sounds like a clean approach for keeping Quarkus/Agroal dependencies separate. Happy to tackle that as a follow-up or incorporate it here if you'd prefer. Will push the fixes along with CI fixes shortly.
There was a problem hiding this comment.
Imho a follow-up is fine, because DefaultDataSourceResolver doesn't need Agroal, so no need for a Quarkus module.
There was a problem hiding this comment.
BTW I think my example above is wrong, should be:
@WithDefault("default")
@WithName("datasource-resolver.type")
String dataSourceResolver();There was a problem hiding this comment.
Would it be possible to isolate JDBC-specific config from the general Persistence config?
I believe NoSQL persistence has some local config classes already.
dataSourceResolver() is only relevant to JDBC, so any Polaris deployments that do not use JDBC, should not be burdened with this config, IMHO.
Upd: I believe it is best to resolve this in current PR. I know it's extra work, but why introduce a config skew only to have to fix it in a follow-up PR right after?
There was a problem hiding this comment.
@adutra Got it will use @Identifier("default") and @withname("datasource-resolver.type") with @WithDefault("default"). Pushing the fix along with CI fixes shortly.
There was a problem hiding this comment.
+1 to @WithName("datasource-resolver.type")
…DBC configuration
| default Optional<String> dataSourceResolverType() { | ||
| return Optional.of("default"); | ||
| } |
There was a problem hiding this comment.
nit: if there is a known default, using Optional is generally superfluous.
Also, it would be better to leave this unimplemented here :
String dataSourceResolverType();But override it in QuarkusRelationalJdbcConfiguration as follows:
@ConfigMapping(prefix = "polaris.persistence.relational.jdbc")
public interface QuarkusRelationalJdbcConfiguration extends RelationalJdbcConfiguration {
@WithDefault("default")
@Override
String dataSourceResolverType();
}There was a problem hiding this comment.
I have applied the final dataSourceResolverType / QuarkusRelationalJdbcConfiguration / ServiceProducers changes per review.
dimas-b
left a comment
There was a problem hiding this comment.
Main point from my side in this review round: Moving all CDI code for JDBC to the polaris-relational-jdbc module... WDYT?
runtime/service/build.gradle.kts
Outdated
| implementation(project(":polaris-api-catalog-service")) | ||
|
|
||
| runtimeOnly(project(":polaris-relational-jdbc")) | ||
| implementation(project(":polaris-relational-jdbc")) |
There was a problem hiding this comment.
I'd prefer to avoid making this dependency more prominent.
Ideally, only runtime/server should depend on polaris-relational-jdbc, but keep the old runtime-only dep for now is fine too.
There was a problem hiding this comment.
I completely agree. I have moved the CDI logic into a new
JdbcCdiProducers class directly within polaris-relational-jdbc. Because ServiceProducers no longer imports from the JDBC module, I’ve reverted the dependency in runtime/service/build.gradle.kts back to runtimeOnly so it doesn't bleed into the compile classpath.
| Optional<String> databaseType(); | ||
|
|
||
| /** The type of the {@link DataSourceResolver} to use. */ | ||
| String dataSourceResolverType(); |
There was a problem hiding this comment.
Does this method have to be here? The JDBC persistence code (configured via this interface) does not actually call it (and does not need to call).
There was a problem hiding this comment.
I had to keep it here because of the producer move. Since JdbcCdiProducers is now bundled inside this module, our own internal wiring logic relies on dataSourceResolverType() to determine which bean to retrieve. The low-level connection logic ignores it, but the CDI injection needs it. I've updated the Javadoc to clarify this.
| } | ||
|
|
||
| @Produces | ||
| public DataSourceResolver dataSourceResolver( |
There was a problem hiding this comment.
Should it be @ApplicationScoped? I do not believe it can change in runtime.
There was a problem hiding this comment.
I have marked the relocated producer method as @ApplicationScoped in the new JdbcCdiProducers
class.
| RelationalJdbcConfiguration jdbcConfig, | ||
| @Any Instance<DataSourceResolver> dataSourceResolvers) { | ||
| String type = jdbcConfig.dataSourceResolverType(); | ||
| return dataSourceResolvers.select(Identifier.Literal.of(type)).get(); |
There was a problem hiding this comment.
It looks like we're leaning towards moving CDI/Quarkus-related code into the polaris-relational-jdbc module.
Let's move this produce there too. This way the more general-purpose runtime/service code can remain free from JDBC-specific logic and dependencies.
| */ | ||
| @ApplicationScoped | ||
| @DefaultBean | ||
| public class DefaultDataSourceResolver implements DataSourceResolver { |
There was a problem hiding this comment.
Looking at the evolution of this PR (and this class specifically), I think it's probably fine to move CDI/quarkus-related code into the polaris-relational-jdbc module.
|
|
||
| @ConfigMapping(prefix = "polaris.persistence.relational.jdbc") | ||
| public interface QuarkusRelationalJdbcConfiguration extends RelationalJdbcConfiguration {} | ||
| public interface QuarkusRelationalJdbcConfiguration extends RelationalJdbcConfiguration { |
There was a problem hiding this comment.
As noted in other comment threads, this class should probably go to polaris-relational-jdbc, where the it would be co-located with CDI producers for JDBC stuff.
There was a problem hiding this comment.
I left QuarkusRelationalJdbcConfiguration in runtime/common for now. Moving it into polaris-relational-jdbc would require introducing smallrye-config (and Quarkus-specific annotations) as a dependency to the generic JDBC module, which goes against keeping the core persistence layer free of Quarkus dependencies.
Thanks @flyingImer Heads up before your next pass based on @dimas-b's latest feedback all CDI producer wiring for |
There was a problem hiding this comment.
I am not sure if I understand the motivation of having realm specific Datasource.
The most simple use case i can think of is having a dedicated datasource that connect to entity table, events table and idempotency table respectively, so each of them have their own connection pool, things like entity tables connections, should keep on working and features like events table and idempotency doesn't effect core functionality of polaris. I recommend we should start from there.
If people wanna split these tables into their individual nodes they can use things like https://docs.citusdata.com/en/stable/get_started/what_is_citus.html
Or
we now support Cocroach too does auto sharding of table to different node.
@singhpk234 I completely agree that separating connection pools by workload (entities vs events vs idempotency) is the end goal! In fact, my previous iteration of this PR included a StoreType parameter exactly for that. However, @dimas-b and @flyingImer correctly pointed out that since JdbcBasePersistenceImpl currently processes all of these tables together, adding the workload routing logic now is getting ahead of the actual implementation. |
I beleive we should start from there then, TBH i dont see much value in having realm specific datasource, if thats some one wants they can just 2 different jvm of polaris 1 only hosting realm 1 another hosting only realm2 and then route between the JVMs in an outer layer. And nevertheless given that we eventually want to have table level connection pool, we should get the isolation in place. |
@singhpk234 The primary motivation for realm-specific routing is to support scalable multi-tenancy. For a SaaS deployment hosting thousands of tenants (realms), spinning up thousands of separate JVMs isn't feasible. |
Why would you spin up 1000s of seperate JVMs just club realms you wanna go in 1 JVM, lets say you have enterprise tier (realm1,......relam n) all go to you enterprise tier jvm and rest all go to free tier, you can router your request to polaris instance based on your tier/realm. you don't have to create 1 realm from 1 jvm. Again I am not sure what the foundation is being established here, this is just adding an interface which is very soon goona change when StoreType comes, so just to reiterate I think we should focus on having dedicated connection pool and achieve that isolation. |
@singhpk234 Routing at the outer layer tightly couples Polaris JVMs to specific database instances. A DataSourceResolver solves this by keeping compute stateless—allowing operators to migrate high-growth realms to dedicated databases seamlessly in the background.Regarding StoreType for workload isolation I completely agree I actually had it in my previous commits. I removed it because @dimas-b and @flyingImer felt updating JdbcBasePersistenceImpl to actually utilize it was too much scope creep for this specific PR.I am more than happy to add StoreType back in if we agree it belongs in this first iteration. |
I don;t think in this scenario without application restart a migration can happen ? of all things i don't think your example of enterprise tier vs free tier fits this case too, one time you are advocating i need to give enterprise tier a dedidacte DS, but making the enterpise and free tier share the same JVM, this seems fundamentally wrong to me, as this is not true isolation. Also migration of user from 1 DS to another is not something we can do at the moment one needs to copy and keep the sources in sync and we don't have tools for that. |
|
@singhpk234 That's a fair point true isolation requires physically separate JVM clusters to prevent noisy neighbors and migration tooling isn't there yet.The core intent of this PR is just to introduce the extensibility hook (SPI), so Polaris isn't locked to a static single DataSource connection pool like it is today.That being said I agree that workload isolation (entities vs events) via dedicated connection pools is a critical use case. To fully support your vision, would you be comfortable moving forward if I simply re add StoreType to the resolve() signature so it supports BOTH (RealmContext, StoreType)right from the start? |
|
@Subham-KRLX @singhpk234 @flyingImer :
If you prefer to resolve the workload isolation problem in code first, I'd support that. We could put this PR on hold, isolate MetaStore, Metrics, Idempotency, then resume work on the DataSource resolver. Going the other way is fine too 🙂 The end result is going to be the same (as least that how it see it). |
|
Re: DataSource / realm mapping at deployment time - I think this is a concern of the Polaris "owner". I believe Polaris as an OSS project should empower users to make customizations that fit their specific deployment choices. I believe Polaris should remain neutral / flexible and not prescribe any specific deployment strategies. @Subham-KRLX 's comments related to this resonate with my personal view on this matter. |
|
As I said in the dev mailing list[1], introducing multiple data sources requires a proper design before jumping to a implementation. There are multiple factors to consider. We may gather more feedback given JDBC is widely used by the community. Blindly introduce a data source resolver doesn't make sense to me. I'd suggest to work on a design doc first. |
|
@flyrain, @singhpk234, and @dimas-b. I agree that a formal design document is appropriate and will prepare one that covers both the multi-tenancy requirements and the immediate need for workload-based isolation. To ensure a proper foundation, I am re-adding StoreType (Metastore, Metrics, Events) to the DataSourceResolver |
|
Agreed here with some of the comments asking what value is bringing realm-based datasource resolution into the codebase - especially with a shared JVM that is controlling the request flow. I am afraid that adding this type of functionality without any metrics or considering alternatives may bring unnecessary complication to the codebase. If a design doc is being written to support this use case, it would be good to include any such data there may exist that prove the need for this feature. I do think that we are all on the same page that workload-based isolation (different datasource for events, metrics, metadata, etc.) - it does make sense and we do need this feature sooner than later. I would also suggest starting the implementation from that angle instead. |
|
Thanks @adnanhemani, @flyrain, and @singhpk234. I completely agree that workload-based isolation (Metrics vs Events vs Metastore) is the highest priority. I've updated the PR to re-instate StoreType (METASTORE, METRICS, EVENTS) so the immediate focus is on establishing the foundation for separating connection pools. |
Introduces a DataSourceResolver interface to decouple DataSource selection from JdbcMetaStoreManagerFactory, enabling per-realm and per-store-type routing (main, metrics, events).
Changes:
Instance<DataSource>This is the foundation for #3890. Named DS resolution and config properties will follow.
Closes #3890
Checklist