-
Notifications
You must be signed in to change notification settings - Fork 25
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
yjs Map not recommended for key-value data. #535
Comments
Thanks, I didn't know that existed Looking into it it seems it only supports primitives as values like you said, which would be quite limiting 😔 What if each of your points (or even the whole array of points) were frozen data (https://mobx-keystone.js.org/frozen-data) and the bindings would treat those frozen objects as plain values instead of YMaps/YArrays? would that help? So for example, an snapshot like
would bind into
|
I'm not entirely sure what the implications of that would be. What I've done is store things based on whether or not it is a simple value. {
$modelType: 'object',
x: 10,
y: 10,
width: 100,
height: 100,
background: 'red',
color: 'blue',
children: [{
$modelType: 'object',
/* and so on... */
}]
/* and so on... */
} In my yjs adapter I split out the data depending on whether or not it is a simple type, something like: Y.Map {
$modelType: 'object',
data: Y.Array (controlled by YKeyValue) [
{ key: 'x', val: 10 },
{ key: 'y', val: 10 },
{ key: 'width', val: 100 },
{ key: 'y', val: 100 },
/* and so on, this structure is maintained by YKeyValue, so I never directly interact with it as an array */
]
children: Y.Array [
Y.Map {
$modelType: 'object',
data: Y.Array (controlled by YKeyValue) [
{ key: 'x', val: 10 },
{ key: 'y', val: 10 },
/* and so on... */
},
children: Y.Array []
]
}]
} Sort of like that. Each model is stored in a Y.Map. ( In practice I actually have one root Document model that has a flat map of all the objects in the document. Then all the relationships between the models are maintained as refs that resolve to the root map. That way when I move models around I don't actually have to move large parts of the YJS document around, just lightweight refs. ) |
Hi, I've been looking into it and doing some tests:
YKeyValue map seems to save quite a bit of storage like you pointed out (at least for plain values, since that's all it supports right now), but at the same time it seems to be 46 times slower :-/ I've been looking into other CRDT libs and they seem to have the same issue though. For example, automerge uses around the same space as V2 (around 200KB) while LORO uses the same space as V1 (around 490KB). I'll try to examine the source code to figure out the trick that makes it save space as well as what makes it slow (Perhaps there's a way to make it faster and, who knows, to support nested Y.js structures?). In the meanwhile I released a v1.3 that saves frozen values as plain values in Y.js instead of transforming them deeply into Y.Map and Y.Array (I don't know if that might help in your particular case, but just FYI). |
Yeah, that's a pretty 👀 speed difference. Running a similar shootout, it seems like the number of keys makes a big difference. I ran with 10_000 random updates:
Not very rigorous benchmarking, just one run, but the trend seems credible. looking at the implementation, this makes a lot of sense. It looks like Benchmark codeimport * as Y from "yjs";
import { YKeyValue } from "y-utility/y-keyvalue";
const count = 10_000;
const keys = 1
const mapDoc = new Y.Doc();
const map = mapDoc.getMap("map");
let now = performance.now();
for (let i = 0; i < count; i++) {
const key = (Math.random() * keys).toFixed(0);
map.set(key, i);
}
console.log("Time Map: " + (performance.now() - now));
const kvDoc = new Y.Doc();
const array = kvDoc.getArray<{
key: string;
val: unknown;
}>("array");
const kv = new YKeyValue(array);
now = performance.now();
for (let i = 0; i < count; i++) {
const key = (Math.random() * keys).toFixed(0);
kv.set(key, i);
}
console.log("Time KV: " + (performance.now() - now)); For my case, I'm unlikely to do more than tens of updates per frame at 60FPS, and I'm in the range of < 100 keys. At that scale updates always sub-millisecond no matter which data type, so document size matters much more to me. |
I was looking at the recent release and saw there are yjs bindings. I've just spent some time building out yjs bindings for a mobx-keystone backed application.
One thing I encountered was using Y.Map for simple props can lead to very inefficient data storage. This is mentioned somewhat out-of-the-way in the yjs/y-utility repository.
The specific case I had was a model storing positional data: ~like
Due to the lack of data pruning in
Y.Map
while doing interleaved writes of plain values, a relatively short editing session on the order of minutes would lead to storing hundreds ok kb of data.When I switched to using the
YKeyValue
utility, the stored size of my documents stays very small. I would be quite happy to use the new official bindings, but would need to find a way to useYKeyValue
for plain values. Is that something you think could be ~easily supported by the official bindings?The text was updated successfully, but these errors were encountered: