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

Snapshot name collisions #4485

Merged
merged 18 commits into from
Jan 4, 2021
Merged

Snapshot name collisions #4485

merged 18 commits into from
Jan 4, 2021

Conversation

arinwt
Copy link
Contributor

@arinwt arinwt commented Nov 30, 2020

First step towards fixing #3542

Adds a subtree at each layer of the stack (except DDS for now) to isolate dynamic tree keys, such as data store IDs and channel IDs.

  • This shifts all data store trees under a ".channels" tree of the root node.
    • Protects against ".protocol", ".blobs" name collisions, and some driver/server ones as well
  • This shifts all channel trees under a ".channels" tree of each data store node.
    • Does not add protection, because blobs are already isolated from trees
  • This does not shift all dds subtrees/blobs to a subtree of each channel node.
    • Current design does not require this, since we only return ITreeEntrys from internal snapshot
  • This does not take care of isolating server/driver from runtime at the root

Old tree structure:

/
    [blob] .chunks
    [tree] .protocol/
    [tree] .blobs/
    [tree] <dataStoreIds>/
        [blob] .component
        [tree] <channelIds>/
            [blob] .attributes
            [tree] .../

New tree structure:

/
    [blob] .metadata **provides snapshot version
    [blob] .chunks
    [tree] .protocol/
    [tree] .blobs/
    [tree] .channels/
        [tree] <dataStoreIds>/
            [blob] .component
            [tree] .channels/
                [tree] <channelIds>/
                    [blob] .attributes
                    [tree] .../

With this change, ContainerRuntime only gives upper .channels tree to DataStores, restricting access.
Each DataStoreContext only gives the lower .channels tree to its DataStoreRuntime.

Changes in this PR are back/forwards compatible reading of new format only, it does not write the new snapshotFormatVersion. So after a few versions with this released, we can start writing in the new snapshotFormatVersion. This is because an old client runtime will not be able to understand the new format.

SummarizerNode handles this by calling parseSummaryForSubtrees which gives a tree to use for children nodes as well as the additional path part for the child nodes.

@arinwt arinwt changed the title Name collisions Snapshot name collisions Nov 30, 2020
trees: {
[protocolTreeName]: ISnapshotTree;
[blobsTreeName]: ISnapshotTree;
".dataStores": ISnapshotTree;
Copy link
Contributor Author

@arinwt arinwt Nov 30, 2020

Choose a reason for hiding this comment

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

Open to suggestions for the names.

".channels" - refers to only data store trees for now, but we could store blobs, etc. that the DataStores class can access.

".channels" - refers to only the channel trees for now, but we could store blobs, etc. that the DataStoreRuntime can access. Was considering naming this something else like ".runtime" in case components wanted to store additional info here they could.


export const dataStoreAttributesBlobName = ".component";

export interface IRuntimeSnapshot {
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 was thinking to more strongly type the other layers as well (and actually wrote out the types as seen in the first commit), but it requires more work across the boundaries with not much gain right now. Will look more into it after fixing SummarizerNode.

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 id (at that level) should be always null., no?
Based on latest discussion with SPO, I think we should start asserting in our layers that we provide either value (content but id === null) or reference (id !== null, but no other fields are provided) for trees, similar how we do for blobs - we either reuse existing one, or write out new one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding was that storage always returns ID + all tree contents (i.e. this is the full skeleton). I think it could make sense for them to sometimes return partial trees, but I don't know exactly how that would be specified right now.

If there's a better way to type it that you know of, I can change it, but I know just from debugging that currently it will be id !== null and other fields are provided so that type would be misleading or at least limiting (maybe intentionally?).

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, I though this interface is for writing snapshots (summaries), not for reading.
Runtime never reads "snapshots", it operates in individual (shallow) trees that always have content and ID.

BTW, we should be try to eliminate "snapshot" word from our code base. It is used as in "snapshot tree but I think naming is from the past. I think they are always shallow trees (i.e. only contain one level of data), right?

@msfluid-bot
Copy link
Collaborator

msfluid-bot commented Nov 30, 2020

@fluidframework/base-host: No change
Metric NameBaseline SizeCompare SizeSize Diff
main.js 164.76 KB 164.76 KB No change
Total Size 164.76 KB 164.76 KB No change
@fluid-example/bundle-size-tests: +894 Bytes
Metric NameBaseline SizeCompare SizeSize Diff
container.js 190.29 KB 191.17 KB +894 Bytes
map.js 45.84 KB 45.84 KB No change
matrix.js 144.52 KB 144.52 KB No change
odspDriver.js 193.22 KB 193.22 KB No change
sharedString.js 158.46 KB 158.46 KB No change
Total Size 732.33 KB 733.2 KB +894 Bytes

Baseline commit: fa35947

Generated by 🚫 dangerJS against 06323cf

@vladsud
Copy link
Contributor

vladsud commented Dec 1, 2020

This does not shift all dds subtrees/blobs to a subtree of each channel node.
Current design does not require this, since we only return ITreeEntrys from internal snapshot

I already violate it with mixinSummaryHandler() (and using it in Bohemia repo to write out /DataStoreId/_search/01 blobs

@vladsud
Copy link
Contributor

vladsud commented Dec 1, 2020

[tree] .dataStores/

Can we please call it channels?
That's already what I use for routing (active PR, will try to merge this week).
And that's consistent with future we want to get to, where channels can be nested, and data store and DDS differ only in implementation, not in structure (i.e. it will be Ok for DDS to have DDS underneath it - formalizing what Daniel already did with 3 DDSs sitting on one channel)

export type PropertyValues<T> = T[keyof T];

export const containerSnapshotFormatVersions = {
missing: undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

can you please add some comments on what these fields mean?
Also should be defined interface properly (with all of the comments that go to documentation) then derive it from this value?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added comments.

Not sure what exactly you mean by interface.. If I add an interface, I need to list the versions/types twice I think?
I have changed to more simple union type.

The alternative I could think (without listing all versions twice) was the same as it was before but change current to v1 and next to v2 etc. so that we don't have to update all references every time.

const blobId = context.baseSnapshot?.blobs[blobName];
if (context.baseSnapshot && blobId) {
return context.storage ?
readAndParse<T>(context.storage, blobId) :
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious, should it be reverse? I.e. if we have it in snapshot, why do we read it from storage?

Copy link
Contributor Author

@arinwt arinwt Dec 7, 2020

Choose a reason for hiding this comment

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

Would be an unrelated change I think, but it makes sense to me. I don't know as much about what it means when the blobs are stored directly in the snapshot though, maybe we prioritize the ones in storage for some reason? i.e. out of date or something?

@jatgarg would know I think.

let dataStoresSnapshot = context.baseSnapshot;
let dataStoresSnapshotType: BaseSnapshotType = "legacy";

if (!!dataStoresSnapshot && metadata.snapshotFormatVersion !== containerSnapshotFormatVersions.missing) {
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 a bit at a loss in terms of using "missing" & "next". What if we need to change format again?
Would approach of just saying that existing format is 1.0 (and that's default value if attributes are missing) and new format is 2.0 work? Then we can clearly articulate what is 1.0 format, what is 2.0 format, write documentation, and someone willing to write code on the side to parse our files can actually do it.
Maybe that's what I'm missing - formal description of format, i.e. what is the instruction to me as a side developer of how to read and write these formats, without looking in the code to understand formats and expectations.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see - you essentially have 3 versions. I'd rather call them 1.0, 2.0 and 3.0 and make 1.0 default - a bit cleaner IMHO

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This makes sense to me, I was trying to not diverge as far from the pre-existing "current" logic, but I agree it probably makes more sense not to use sliding version names when reading.


export const containerSnapshotFormatVersions = {
missing: undefined,
next: "0.1",
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not see containerSnapshotFormatVersions.next used anywhere in he code.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It isn't used, but we do check for undefined and treat that else scenario specially.

// However the feature of loading a detached container from snapshot, is added when the
// snapshotFormatVersion is "0.1", so we don't expect it to be anything else.
if (snapshotFormatVersion === currentSnapshotFormatVersion) {
if (snapshotFormatVersion === dataStoreSnapshotFormatVersions.current
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 a bit confused here. Existing files will take the else branch, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's intentional based on the comment:

// However the feature of loading a detached container from snapshot, is added when the
// snapshotFormatVersion is "0.1", so we don't expect it to be anything else.

@github-actions github-actions bot requested a review from vladsud December 7, 2020 05:37
@arinwt arinwt marked this pull request as ready for review December 7, 2020 18:53
} from "./dataStoreContext";
} from "./dataStoreContext";

export type BaseSnapshotType = "legacy" | "next";
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe put some comment in here describing what legacy and next mean in practice?
Should be given them more descriptive names? While this is only runtime data, I image that we might have "next next" format some day :)

/** Tree to use to find children subtrees */
childrenTree: ISnapshotTree,
/** Additional path part where children are isolated */
childrenPathPart: string | undefined,
Copy link
Contributor

Choose a reason for hiding this comment

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

I probably did not stair too long at the code to better understand it, but it is not clear to me from glancing what the actual meaning of childrenPathPart and how callers of API below use it. Maybe it's just me (and not having enough focus), but would it be helpful to have a bit more in comment on when it's undefined, and when it's not, what it means and how this data is expected to be used?

@anthony-murphy
Copy link
Contributor

this needs tests, specifically for forward/back comapt

@anthony-murphy
Copy link
Contributor

anthony-murphy commented Dec 9, 2020

why do we need this hear? seems like it could be defined in container-runtime


Refers to: packages/runtime/runtime-definitions/src/summary.ts:184 in 597decc. [](commit_id = 597decc, deletion_comment = False)

*/
export function parseSummaryForSubtrees(baseSummary: ISnapshotTree): ISubtreeInfo {
// New versions of snapshots have child nodes isolated in .channels subtree
const channelsSubtree = baseSummary.trees[channelsTreeName];
Copy link
Contributor

Choose a reason for hiding this comment

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

channelsTreeName [](start = 46, length = 16)

this seem coupled to our specific container runtime. i feel like this should live in container runtime

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 agree, but will need to revisit later to extract this functionality.

Copy link
Contributor

@anthony-murphy anthony-murphy left a comment

Choose a reason for hiding this comment

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

I don't want to block this going in. I'd like BaseSnapshotType removed, but the file moves, and testing can come in a follow up

@vladsud
Copy link
Contributor

vladsud commented Dec 27, 2020

@arinwt, it would be great to address this area sooner than later. Do you need any help with moving this PR forward?

@arinwt
Copy link
Contributor Author

arinwt commented Dec 28, 2020

@vladsud no sorry, I just want to add support for DDS's and address a few of Tony's comments first (version as number if possible and move interface definition).

@github-actions github-actions bot requested a review from vladsud January 2, 2021 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Summaries: detecting mistakes early: blob names
4 participants