Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove dictionary from ILoaderOptions #19306

Merged
merged 14 commits into from
Feb 2, 2024
Merged

Conversation

ChumpChief
Copy link
Contributor

Effectively this change would clarify "ILoaderOptions is for known options on the Loader layer only".

Currently, ILoaderOptions permits setting any option, as it's a dictionary type. This random dictionary functionality is used by:

  1. client: IClient (a long-standing secret handshake here)
  2. enableOfflineLoad: boolean (a newer secret handshake)
  3. A few DDS-level features, mostly sequence stuff and attribution stuff.

This change would make the first two explicit but marked deprecated (assuming their secrecy means we don't want anyone to use them). It would disallow the third category at the typing layer but avoid making functional changes immediately.

The third category seems an inappropriate use, since it's referring to a container-global options object for what are generally per-DDS settings. This seems to have been convenient for testing purposes, but seems dangerous for a customer to use (e.g. imagine two DataObjects fighting over their desired configs on the global object). Perhaps this was even done accidentally, as it's not obvious that dataStoreRuntime.options is secretly referring to a container-global options object - it really does sound like it should be options for just that dataStoreRuntime. My hope is that we can stem further usage with this change and work towards replacing the existing usage with something more appropriate.

Eventually this may allow us to remove the plumbing of the ILoaderOptions through the layers, since the specified properties aren't super interesting to the other layers.

Acknowledge that this is a breaking change (to typing, not to functionality at runtime), and I need to remember what we're doing with those now.

@github-actions github-actions bot added area: dds Issues related to distributed data structures area: dds: sharedstring area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct area: loader Loader related issues area: tests Tests to add, test infrastructure improvements, etc public api change Changes to a public API base: main PRs targeted against main branch labels Jan 19, 2024
@@ -24,7 +24,10 @@ function createConnectedCell(
): ISharedCell {
// Create and connect a second SharedCell.
const dataStoreRuntime = new MockFluidDataStoreRuntime();
dataStoreRuntime.options = options ?? dataStoreRuntime.options;
// TODO this option shouldn't live here - this options object is global to the container
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think the first thing is to change the type of dataStoreRuntime be something other than ILoaderOptions, probably Record<string | number, any> to start. that would also get rid of all the casting this change introduces

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm also not positive that the province of this object actually comes all the way from the loader. the type might just be a lie

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left this annoying intentionally - my thought here was to discourage folks from writing new values to options (with this PR as-is, they'll get an error). This is with the goal of deterring new (possibly dangerous) usage.

I think the likely end state being that the dataStoreRuntime.options member goes away entirely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm also not positive that the province of this object actually comes all the way from the loader. the type might just be a lie

The loader options get shallow-copied per-Container - so shallow modifications are global to the Container, but deep modifications of true loader options (e.g. individual IClient properties) are global to the Loader. But yeah they get plumbed all the way through.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at an option to split the typing (maybe this is what you originally meant with your comment) - ILoaderOptions with only known types for the Loader constructor param, but everywhere else gets JUST the record-like. Which then would point to a followup change that just severs the connection at the loader/runtime boundary, with the runtime creating an empty object as its starting options and getting nothing from the loader.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in latest!

@msfluid-bot
Copy link
Collaborator

msfluid-bot commented Jan 19, 2024

Baseline CI build failed, cannot generate bundle analysis at this time


Baseline commit: 22904a9

Generated by 🚫 dangerJS against db7fd39

@ChumpChief
Copy link
Contributor Author

@Abe27342 particularly interested to get your thought on attribution and other DDS usage, and what alternatives we might consider.

@Abe27342
Copy link
Contributor

@Abe27342 particularly interested to get your thought on attribution and other DDS usage, and what alternatives we might consider.

I looked into this a while back. IMO all the DDS-level "options" should live somewhere near IFluidDataStoreRegistry in the API surface. #15397 (never merged) took a stab at that. One problem in the realm it doesn't address is that we will need clean ways to declare migrations for options which need consensus across clients. At one point Tony suggested more heavy tweaks to the data store registry pattern which might allow that kind of flow, but whatever we do will need some design work.

I know one of the stated goals of this PR is trying to discourage adding further options to ILoaderOptions which shouldn't be there, but I don't love that this also obfuscates how apps using Fluid can set these options. If we do end up with this intermediate state, I think we'll need some clear partner communication around what we're planning.

@ChumpChief
Copy link
Contributor Author

I know one of the stated goals of this PR is trying to discourage adding further options to ILoaderOptions which shouldn't be there, but I don't love that this also obfuscates how apps using Fluid can set these options. If we do end up with this intermediate state, I think we'll need some clear partner communication around what we're planning.

My goal is not to obfuscate, but ultimately to make it illegal and stop passing loader options through to the runtime at all. If the registry-based approach works out (or another alternative) then the communication to partners would be to use that instead. Are you aware of customers using loader options to control these features already today?

@anthony-murphy
Copy link
Contributor

Just pasting this here, as i think it is relevant to the discussion, but doesn't immediately impact this specific change

We currently have two different mechanisms for controlling behavior: Options and Configurations

The first and oldest are our options objects, here are some examples:
• ILoaderOptions:

readonly options?: ILoaderOptions;

• IContainerRuntimeOptions:
export interface IContainerRuntimeOptions {

There are also several dds and drivers that use a similar mechanism, specifically that mechanism is that they take some object, and that object defines behavior. Generally, these objects are hard coded by our consumers.

The second and newer is configurations. These come from a host specified configProvider and are generally dynamically accessed from somewhere. By default, we support reading them from sessionStorage for easy dev testing, and partners like FFX support configuration services like ecs and control tower. Here are some examples:

const realAllowPackaging = mc.config.getBoolean("FluidAggregateBlobs") ?? allowPacking ?? false;

this.mc.config.getBoolean("Fluid.Driver.Odsp.binaryFormatSnapshot");

Given these two mechanisms, I thought it would be useful to start some discussion about when and where to use the different mechanisms. My perspective is the following, but hoping to discuss other’s perspectives as well:

• Options objects should only be used where it is explicitly expected and supported for consumer of our library to take advantage then long term.
• Configurations should be used for all new features. They should start off by default and be tested. In a following release be turned on by default. If no problems, they should then be removed. They are short term.
o One caveat may be using config to override options, these configs may live long term to ease testing different option values.

I think this will become quite important going forward, and changes to options objects will generally be breaking changes, whereas configs will not be, as they are not exposed on types. In general, we should stop using options objects except for features that need explicit consumer control which are few and far between.

@Abe27342
Copy link
Contributor

Yeah, if plan is to check something like this in alongside an alternative way for partners to set these options then awesome, I'd love that.

I don't know exactly how our partners set all of these options, but several techniques that I've seen which don't involve passing thru loader actually have correctness issues. All the other methods I've seen basically rely on mutation of some options objects, and it's a crapshoot whether that applies before or after the option is read for whatever it affects.

E.g. one of the merge-tree flags we've since removed was mutated on construction of the dataStoreRuntime in a hasInitialized call, but when I looked into it I discovered that the option was inconsistent depending on whether you were creating or loading the DDS, and also the mutating code never ran for the summarizer client at all.

I'd guess that there are some partners which do pass in some of these as loader options.

Obviously not a great state to be in.

@Abe27342
Copy link
Contributor

+1 to Tony's point on options vs config. Looking at merge-tree as a case study, I'd argue the following are more inline with "options":

  • catchUpBlobName
  • mergeTreeSnapshotChunkSize
  • newMergeTreeSnapshotFormat (though rather than boolean this should probably be something more extensible)
  • attribution

and these should be config:

  • mergeTreeEnableObliterate
  • mergeTreeReferencesCanSlideToEndpoint

Of the options objects, it will also make sense to document options' impact on compatibility. E.g. mergeTreeSnapshotChunkSize can probably be changed at will and different clients are free to disagree since it's just a heuristic. It so happens that the two snapshot formats for merge-tree are also compatible so that one as well, but one could imagine certain types of snapshot format changes which would have compatibility guarantees. The other options have compat implications.

@ChumpChief
Copy link
Contributor Author

All this sounds good to me I think, though I'd also emphasize the importance of not just the mechanism (options vs. config) but more particularly the site of setting (i.e. at what layer). Anything relating to the specific contents of the runtime/DDS layer seems inappropriate to be configured from the loader layer (or the consumer of the loader layer). Conceptually it even seems odd that the consumer of a Loader would try to load some container code and say "by the way, if you happen to have any merge-trees in there I have an opinion about your snapshot chunk size".

I'd argue that these should probably be set in the runtime layer, probably by the author of the runtime factory code or DataObject code. And even if the Loader does have some unique information that should figure in ("I'm using a driver that likes 5kb chunks") then this info should instead be injected for the runtime to respond to ("As the runtime author, I'll use that preferred chunk size to tweak my merge-tree behaviors").

I'd guess that there are some partners which do pass in some of these as loader options.

Since these are all hidden/undocumented, I don't think a partner could have reasonably found these and taken a dependency unless we personally advised them to? And as you mention many are not safe to vary across loads, making it unlikely that customers with varying deployments of their loader-consuming code could have safely used them.

@Abe27342
Copy link
Contributor

I'd argue that these should probably be set in the runtime layer, probably by the author of the runtime factory code or DataObject code.

Yeah, definitely agreed. Just want to emphasize that we don't have a safe way to do that today.

@ChumpChief ChumpChief marked this pull request as ready for review January 23, 2024 18:33
@ChumpChief ChumpChief requested review from a team as code owners January 23, 2024 18:33
@ChumpChief ChumpChief requested a review from a team as a code owner January 23, 2024 21:50
@github-actions github-actions bot added area: runtime Runtime related issues and removed area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct labels Jan 23, 2024
readonly options: ILoaderOptions;
// Used to be ILoaderOptions, this is staging for eventual removal.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
readonly options: Record<string | number, any>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be a separate PR that decouples context from loader options. that would be non-breaking

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that could include container runtime, and data store runtime too, bascially decouple the lower layer first

@@ -533,10 +533,8 @@ export interface IHostLoader extends ILoader {
/**
* @public
*/
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
export type ILoaderOptions = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i like removing it, but i think its used today, so we need a transition path, i don't think we can just unilaterally remove

ChumpChief and others added 2 commits February 1, 2024 13:26
Co-authored-by: Tony Murphy <anthony.murphy@microsoft.com>
@ChumpChief ChumpChief merged commit 741926e into microsoft:main Feb 2, 2024
30 checks passed
@ChumpChief ChumpChief deleted the LessOptions branch February 2, 2024 00:08
alexvy86 pushed a commit to alexvy86/FluidFramework that referenced this pull request Feb 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: dds: sharedstring area: dds Issues related to distributed data structures area: loader Loader related issues area: runtime Runtime related issues area: tests Tests to add, test infrastructure improvements, etc base: main PRs targeted against main branch public api change Changes to a public API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants