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

Version counters #109

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/grumpy-penguins-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@zarrita/storage": patch
"@zarrita/core": patch
---

Use a counter to prioritize v2/v3 when opening.
32 changes: 30 additions & 2 deletions packages/core/src/open.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,25 @@ import {
v2_to_v3_group_metadata,
} from "./util.js";

function create_version_counter() {
let version_counts = new WeakMap<Readable, { v2: number; v3: number }>();
function get_counts(store: Readable) {
let counts = version_counts.get(store) ?? { v2: 0, v3: 0 };
version_counts.set(store, counts);
return counts;
}
return {
increment(store: Readable, version: "v2" | "v3") {
get_counts(store)[version] += 1;
},
version_max(store: Readable): "v2" | "v3" {
let counts = get_counts(store);
return counts.v3 > counts.v2 ? "v3" : "v2";
},
};
}
let VERSION_COUNTER = create_version_counter();

async function load_attrs(
location: Location<Readable>,
): Promise<Attributes> {
Expand Down Expand Up @@ -43,6 +62,7 @@ async function open_v2<Store extends Readable>(
let loc = "store" in location ? location : new Location(location);
let attrs = {};
if (options.attrs ?? true) attrs = await load_attrs(loc);
VERSION_COUNTER.increment(loc.store, "v2");
Copy link
Owner

Choose a reason for hiding this comment

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

For v2, maybe we only increment if a node has successfully been found? this within open_array_v2 and open_group_v2 but only if they are successful?

async function open_array_v2<Store extends Readable>(
	location: Location<Store>,
	attrs: Attributes,
) {
	let { path } = location.resolve(".zarray");
	let meta = await location.store.get(path);
	if (!meta) {
		throw new NodeNotFoundError(path);
	}
++	VERSION_COUNTER.increment(loc.store, "v2");
	return new Array(
		location.store,
		location.path,
		v2_to_v3_array_metadata(json_decode_object(meta), attrs),
	);
}

Or we add some wrappers functions:

async function open_v2_with_counter<Store extends Readable>(...args: Parameters<typeof open_v2<Store>>) {
	let node = await open_v2(...args);
	VERSION_COUNTER.increment(loc.store, "v2");
	return node;
}

if (options.kind === "array") return open_array_v2(loc, attrs);
if (options.kind === "group") return open_group_v2(loc, attrs);
return open_array_v2(loc, attrs).catch((err) => {
Expand Down Expand Up @@ -130,6 +150,7 @@ async function open_v3<Store extends Readable>(
): Promise<Array<DataType, Store> | Group<Store>> {
let loc = "store" in location ? location : new Location(location);
let node = await _open_v3(loc);
VERSION_COUNTER.increment(loc.store, "v3");
if (options.kind === undefined) return node;
if (options.kind === "array" && node instanceof Array) return node;
if (options.kind === "group" && node instanceof Group) return node;
Expand Down Expand Up @@ -159,9 +180,16 @@ export async function open<Store extends Readable>(
location: Location<Store> | Store,
options: { kind?: "array" | "group" } = {},
): Promise<Array<DataType, Store> | Group<Store>> {
return open_v3(location, options as any).catch((err) => {
const store = "store" in location ? location.store : location;
const version_max = VERSION_COUNTER.version_max(store);
// Use the open function for the version with the most successful opens.
// Note that here we use the dot syntax to access the open functions
// because this enables us to use vi.spyOn during testing.
let open_primary = version_max === "v2" ? open.v2 : open.v3;
let open_secondary = version_max === "v2" ? open.v3 : open.v2;
return open_primary(location, options as any).catch((err) => {
if (err instanceof NodeNotFoundError) {
return open_v2(location, options as any);
return open_secondary(location, options as any);
}
throw err;
});
Expand Down
19 changes: 19 additions & 0 deletions packages/storage/__tests__/fetch.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { afterEach, describe, expect, it, vi } from "vitest";

import { open, root } from "@zarrita/core";

import FetchStore from "../src/fetch.js";

// `vitest --api` exposes the port 51204
// ref: https://vitest.dev/config/#api
let href = "http://localhost:51204/fixtures/v3/data.zarr";
let href_v2 = "http://localhost:51204/fixtures/v2/data.zarr";

describe("FetchStore", () => {
afterEach(() => {
Expand Down Expand Up @@ -138,4 +141,20 @@ describe("FetchStore", () => {
'"tributes\\": {}, \\"zarr_format\\": 3, \\"node_type\\": \\"gro"',
);
});

it("prioritizes v2 over v3 based on count of successful opens by version", async () => {
let storeRoot = root(new FetchStore(href_v2));

const v2_spy = vi.spyOn(open, "v2");
const v3_spy = vi.spyOn(open, "v3");

let arr1 = await open(storeRoot.resolve("1d.chunked.i2"), {
kind: "array",
});
let arr2 = await open(storeRoot.resolve("1d.chunked.ragged.i2"), {
kind: "array",
});
expect(v2_spy).toHaveBeenCalledTimes(2);
expect(v3_spy).toHaveBeenCalledTimes(0);
});
});
3 changes: 3 additions & 0 deletions packages/storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"reference-spec-reader": "^0.2.0",
"unzipit": "^1.3.6"
},
"devDependencies": {
"@zarrita/core": "workspace:^"
},
"publishConfig": {
"main": "dist/src/index.js",
"exports": {
Expand Down
4 changes: 4 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.