On 2019/02/11 16:04:45, Adam Kocoloski <kocol...@apache.org> wrote: 
> Thanks Ilya. Some comments:
> 
> > - `{NS} / {docid} / _info` = '{"scheme": {scheme_name} / {scheme_revision}, 
> > "revision": {revision}}' 
> > - `{NS} / {docid} / _data / {compressed_json_path} = latest_value | part`
> > - `{NS} / {docid} / {revision} / _info` = '{"scheme": {scheme_name} / 
> > {scheme_revision}}'
> > - `{NS} / {docid} / {revision} / _data / {compressed_json_path} = value | 
> > part`
> 
> Is this meant to distinguish between “winning” and “losing” revisions? What I 
> dislike about that distinction is that an update to the currently “losing” 
> branch could promote it to “winning” if it becomes the longest branch. So now 
> you need to go and grab the entire revision tree to know whether the update 
> ought to be written into the {docid} / _data space or not.

Indeed, determining  whether the update ought to be written into the {docid} / 
_data space or not is a problem.
> 
> > ## Read latest revision
> 
> > - We do range read "{NS} / {docid}" and assemble documents using results of 
> > the query. 
> 
> 
> In the model you outlined this means you’re reading the entire revision tree 
> and the body of every revision, when all you really wanted was the body of 
> the winning revision. That’s a lot of unnecessary data transfer that I 
> thought we were trying hard to avoid.
You are right we should read only following two ranges
- `{NS} / {docid} / _info`  
- `{NS} / {docid} / _data 

> 
> > ## Write 
> > ...
> > - read `{NS} / {docid} / _info`, verify that revision is equal to specified 
> > parent_revision and add the key into write conflict range
> 
> I think you meant _index here, not _info. 
I meant _info, but the name doesn't really matter here. 

> 
> >  - `{NS} / {docid} / _index / _revs / {is_deleted} / {rev_pos} / 
> > {new_revision} = {parent_revision}`
> 
> Are you suggesting that every edit creates a new KV in the _revs space? How 
> would you tell which ones are leafs? How would you do stemming of branches?
> 
> I had proposed to have the whole contents of the edit branch as a single KV. 
> When updating a branch, you retrieve that branch KV, clear it, and write a 
> new one with the stemmed content of the ancestry into a key that includes the 
> new leaf revision. This also means you don’t explicitly need to add the old 
> key into the write conflict range (because you’re clearing it, which will do 
> that implicitly).
> 
touche

> The path compression and _changes watcher stuff is being discussed in other 
> threads so I’ll avoid any commentary here. Cheers,
> 
> Adam
> 
> > On Feb 8, 2019, at 7:48 PM, Ilya Khlopotov <iil...@apache.org> wrote:
> > 
> > # Data model without support for per key revisions
> > 
> > In this model "per key revisions" support was sacrificed so we can avoid 
> > doing read of previous revision of the document when we write new version 
> > of it. 
> > 
> > # Ranges used in the model
> > 
> > - `{NS} / _mapping / _last_field_id
> > - `{NS} / _mapping / _by_field / {field_name} = field_id` # we would cache 
> > it in Layer's memory
> > - `{NS} / _mapping / _by_field_id / {field_id} = field_name` # we would 
> > cache it in Layer's memory
> > - `{NS} / {docid} / _info` = '{"scheme": {scheme_name} / {scheme_revision}, 
> > "revision": {revision}}' 
> > - `{NS} / {docid} / _data / {compressed_json_path} = latest_value | part`
> > - `{NS} / {docid} / {revision} / _info` = '{"scheme": {scheme_name} / 
> > {scheme_revision}}'
> > - `{NS} / {docid} / {revision} / _data / {compressed_json_path} = value | 
> > part`
> > - `{NS} / {docid} / _index / _revs / {is_deleted} / {rev_pos} / {revision} 
> > = {parent_revision}`
> > - `{NS} / _index / _by_seq / {seq}` = "{docid} / {revision}" # seq is a FDB 
> > versionstamp
> > 
> > We would have few special documents:
> > - "_schema / {schema_name}" - this doc would contain validation rules for 
> > schema (not used in MVP).
> > - when we start using schema we would be able to populate `{NS} / _mapping 
> > / xxx` range when we write schema document
> > - the schema document MUST fit into 100K (we don't use flatten JSON model 
> > for it)
> > 
> > # JSON path compression
> > 
> > - Assign integer field_id to every unique field_name of a JSON document 
> > starting from 10.
> > - We would use first 10 integers to encode type of the value:
> >  - 0 - the value is an array
> >  - 1 - the value is a big scalar value broken down into multiple parts 
> >  - 2..10 -- reserved for future use
> > - Replace field names in JSON path with field IDs
> > 
> > ## Example of compressed JSON 
> > ```
> > {
> >    foo: {
> >        bar: {
> >          baz: [1, 2, 3]
> >        },
> >        langs: {
> >           "en_US": "English",
> >           "en_UK": "English (UK)" 
> >           "en_CA": "English (Canada)",
> >           "zh_CN": "Chinese (China)" 
> >        },
> >        translations: {
> >           "en_US": {
> >               "license": "200 Kb of text"
> >           }
> >        }
> >    }
> > }
> > ```
> > this document would be compressed into
> > ```
> > # written in separate transaction and cached in the Layer
> > {NS} / _mapping / _by_field / foo = 10
> > {NS} / _mapping / _by_field / bar = 12
> > {NS} / _mapping / _by_field / baz = 11
> > {NS} / _mapping / _by_field / langs = 18
> > {NS} / _mapping / _by_field / en_US = 13
> > {NS} / _mapping / _by_field / en_UK = 14
> > {NS} / _mapping / _by_field / en_CA = 15
> > {NS} / _mapping / _by_field / zh_CN = 16
> > {NS} / _mapping / _by_field / translations = 17
> > {NS} / _mapping / _by_field / license = 19
> > {NS} / _mapping / _by_field_id / 10 = foo
> > {NS} / _mapping / _by_field_id / 12 = bar
> > {NS} / _mapping / _by_field_id / 11 = baz
> > {NS} / _mapping / _by_field_id  / 18 = langs
> > {NS} / _mapping / _by_field_id  / 13 = en_US
> > {NS} / _mapping / _by_field_id  / 14 = en_UK
> > {NS} / _mapping / _by_field_id  / 15 = en_CA
> > {NS} / _mapping / _by_field_id  / 16 = zh_CN
> > {NS} / _mapping / _by_field_id  / 17 = translations
> > {NS} / _mapping / _by_field_id  / 19 = license
> > 
> > # written on document write
> > {NS} / {docid} / _data / 10 /12 / 11 / 0 / 0 = 1
> > {NS} / {docid} / _data / 10 /12 / 11 / 0 / 1 = 2
> > {NS} / {docid} / _data / 10 /12 / 11 / 0 / 2 = 3
> > {NS} / {docid} / _data / 10 / 18 / 13 = English
> > {NS} / {docid} / _data / 10 / 18 / 14 = English (UK)
> > {NS} / {docid} / _data / 10 / 18 / 15 = English (Canada)
> > {NS} / {docid} / _data / 10 / 18 / 16 = Chinese (China)
> > {NS} / {docid} / _data / 10 / 17 / 13 / 19 / 1 / 0 = first 100K of license
> > {NS} / {docid} / _data / 10 / 17 / 13 / 19 / 1 / 1 = second 100K of license
> > ```
> > 
> > # Operations
> > 
> > 
> > ## Read latest revision
> > 
> > - We do range read "{NS} / {docid}" and assemble documents using results of 
> > the query. 
> > - If we cannot find field_id in Layer's cache we would read "{NS} / 
> > _mapping / _by_field_id " range and cache the result.
> > 
> > ## Read specified revision
> > 
> > - Do a range read "`{NS} / {docid} / {revision} /" and assemble document 
> > using result of the query
> > - If we cannot find field_id in Layer's cache we would read "{NS} / 
> > _mapping / _by_field_id " range and cache the result.
> > 
> > ## Write 
> > 
> > - flatten JSON
> > - check if we there are missing fields in field cache of the Layer
> > - if the keys are missing start key allocation transaction 
> >  - read "{NS} / _mapping / _by_field / {field_name}"
> >    - if it doesn't exists add key to the write conflict range (the FDB 
> > would do it by default)
> >  - `field_idx = txn["{NS} / _mapping / _last_field_id"] + 1` and add it to 
> > the write conflict range (the FDB would do it by default)
> >  - write `"{NS} / _mapping / _last_field_id" = field_idx`
> >  - write `"{NS} / _mapping / _by_field / {field_name}" = field_idx` 
> >  - write `"{NS} / _mapping / _by_field_id / {field_idx}" = field_name` 
> > - read `{NS} / {docid} / _info`, verify that revision is equal to specified 
> > parent_revision and add the key into write conflict range
> > - generate new_revision
> > - write all fields into two ranges (split big values as needed)
> >   - "{NS} / {docid} / _data / {compressed_json_path}"
> >   - "{NS} / {docid} / {new_revision} / _data / {compressed_json_path}"
> > - write into following keys
> >  - `{NS} / {docid} / _info` = '{"scheme": {scheme_name} / 
> > {scheme_revision}, "revision": {revision}}' 
> >  - `{NS} / {docid} / {new_revision} / _info` = '{"scheme": {scheme_name} / 
> > {scheme_revision}}'
> >  - `{NS} / {docid} / _index / _revs / {is_deleted} / {rev_pos} / 
> > {new_revision} = {parent_revision}`
> >  - `{NS} / _index / _by_seq / {seq}` = "{docid} / {revision}" # seq is a 
> > FDB versionstamp
> > - update database stats
> >  - `{NS} / _meta / number_of_docs` += 1
> >  - `{NS} / _meta / external_size` += external_size
> > 
> > ## Get list of all known revisions for the document
> > 
> > - range query `{NS} / {docid} / _index / _revs /`
> > 
> > ## Changes feed
> > 
> > - we would set a watch for `{NS} / _meta / external_size` key
> > - when watch is fired we would do a range query starting from "{NS} / 
> > _index / _by_seq / {since_seq}"
> > - remember last key returned by range query to set a new value for since_seq
> > 
> > best regards,
> > iilyak
> > On 2019/02/04 19:25:13, Ilya Khlopotov <iil...@apache.org> wrote: 
> >> This is a beginning of a discussion thread about storage of edit conflicts 
> >> and everything which relates to revisions.
> >> 
> >> 
> >> 
> 
> 

Reply via email to