Skip to content
This repository was archived by the owner on Oct 17, 2022. It is now read-only.

Conversation

@garrensmith
Copy link
Member

RFC for Mango indexes on FoundationDB

\x50 Array
\x60 Objects

An example for a number key would be (\x30, 1). Just too note, Null and Boolean values won’t need to be composite keys as the type key is the value.
Copy link

Choose a reason for hiding this comment

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

s/too note/to note

Copy link

Choose a reason for hiding this comment

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

or simply Note: Null and Boolean...

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks. I updated it.

This design has certain defined limits for it to work correctly:

* The index definition (name, fields and partial_filter_selector) cannot exceed 100 KB FDB value limit
* The sorted keys for an index cannot exceed the 10 KB key limit
Copy link

Choose a reason for hiding this comment

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

"sorted keys" - is this the same as the keys emitted for indexing records?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct. They are.


## Terminology

`Sequence`: a 13 byte value formed by combining the current `Incarnation` of the database and the `Versionstamp` of the transaction. Sequences are monotonically increasing even when a database is relocated across FoundationDB clusters. See (RFC002)[LINK TBD] for a full explanation.
Copy link

@ryanworl ryanworl Apr 30, 2019

Choose a reason for hiding this comment

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

You will probably be using the tuple encoding for the keys, and the Incarnation will therefore be a variable length integer that will be one byte the vast majority of the time (under values of 249 I believe), but will not be strictly speaking 13 bytes. And if you don't use the tuple encoding, I would worry about the potential limiting factor of only one byte. You may find yourself moving databases back and forth across clusters with some regularity depending on how you choose to deploy and load balance logical databases across physical FDB clusters.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks @ryanworl, yes the incarnation will be a single byte. I just copied and pasted that section from here https://github.com/apache/couchdb-documentation/pull/397/files#diff-5a51b92701c50f4d70a06d3a85daf8e9R40, maybe I should be a little more explicit in what I mean there.

Copy link

@ryanworl ryanworl May 1, 2019

Choose a reason for hiding this comment

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

I would recommend not making it a fixed-length byte string of length one and instead use an integer. It takes three additional bytes to encode it as a fixed-length byte string in the tuple encoding if the Incarnation is zero and two additional bytes otherwise.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah thanks @ryanworl, I'll mention that in the other RFC.

Copy link
Member

Choose a reason for hiding this comment

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

@ryanworl the motivation behind the use of a fixed-length byte string was to ensure the sortability of Sequences in their external hexadecimal string representation in CouchDB's HTTP API. @davisp reminded me that the variable-length integer encoding in the tuple layer does generate bytes that always sort correctly (of course), so I can see where this is a good improvement. Thanks for pointing it out.


* The index definition (name, fields and partial_filter_selector) cannot exceed 100 KB FDB value limit
* The sorted keys for an index cannot exceed the 10 KB key limit
* To be able to update the index in the transaction that a document is updated in, there will have to be a limit on number of Mango indexes for a database so that the transaction stays within the 10MB transaction limit. This limit is still TBD based on testing.
Copy link
Member

Choose a reason for hiding this comment

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

3. At the same time a background process will start reading sections of the changes feed and building the index, this background process will keep processing the changes read until it reaches the sequence number that the index was saved at. Once it reaches that point, the index is up to date and `build_status` will be marked as `active` and the index will be used to service queries.
4. There are some subtle behaviour around step 3 that is worth mentioning. The background process will have the 5 second transaction limit, so it will process smaller parts of the changes feed. Which means that it won’t have one consistent view of the changes feed throughout the index building process. This will lead to a conflict situation when the background process transaction is adding a document to the index while at the same time a write request has a transaction that is updating the same document. There are two possible outcomes to this, if the background process wins, the write request will get a conflict. At that point the write request will try to process the document again, read the old values for that document, remove them from the index and add the new values to the index. If the write request wins, and the background process gets a conflict, then the background process can try again, the document would have been removed from its old position in the changes feed and moved to the later position, so the background process won’t see the document and will then move on to the next one.
5. An index progress tracker will also be added. This will use `doc_count` for the database, and then have a counter value that the background workers can increment with the number of documents it updated for each batch update. It would also be updated on write requests while the index is in building mode.
6. Some thing to explore is splitting the building of the index across multiple worker, it should be possible to use the [`get_boundary_keys` ](https://apple.github.io/foundationdb/api-python.html?highlight=boundary_keys#fdb.locality.fdb.locality.get_boundary_keys) api call on the changes feed to get the full list of changes feed keys grouped by partition boundaries and then split that by workers.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
6. Some thing to explore is splitting the building of the index across multiple worker, it should be possible to use the [`get_boundary_keys` ](https://apple.github.io/foundationdb/api-python.html?highlight=boundary_keys#fdb.locality.fdb.locality.get_boundary_keys) api call on the changes feed to get the full list of changes feed keys grouped by partition boundaries and then split that by workers.
6. Something to explore is splitting the building of the index across multiple worker, it should be possible to use the [`get_boundary_keys` ](https://apple.github.io/foundationdb/api-python.html?highlight=boundary_keys#fdb.locality.fdb.locality.get_boundary_keys) api call on the changes feed to get the full list of changes feed keys grouped by partition boundaries and then split that by workers.

},
partial_filter_selector {} - optional filter to process documents before adding to the index
}
```
Copy link
Member

Choose a reason for hiding this comment

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

Format trick:

	{
	  name: "view-name" // optional will be auto-generated
	  index: {
	    fields: ["fieldA", "fieldB"] // fields to be indexed
	  },
	  partial_filter_selector: {} // optional filter to process documents before adding to the index
	}


1. When a user defines a new index on an existing database, save the index definition along with the `sequence` the index was added at and set the `build_status` to `building` so it won’t be used to service queries.
2. Any write requests (document updates) after that must read the new index definition and update the index. When updating the new index, the index writers should assume that previous versions of the document have already been indexed.
3. At the same time a background process will start reading sections of the changes feed and building the index, this background process will keep processing the changes read until it reaches the sequence number that the index was saved at. Once it reaches that point, the index is up to date and `build_status` will be marked as `active` and the index will be used to service queries.
Copy link
Contributor

Choose a reason for hiding this comment

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

revisiting: @garrensmith this background process will now likely be a couch_jobs processs right??

When an index is created on an existing database, the index will need to be built for all existing documents in the database. The process for building a new index would be:

1. When a user defines a new index on an existing database, save the index definition along with the `sequence` the index was added at and set the `build_status` to `building` so it won’t be used to service queries.
2. Any write requests (document updates) after that must read the new index definition and update the index. When updating the new index, the index writers should assume that previous versions of the document have already been indexed.
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 elaborate to this? Say you have a transaction that does a write/update. In that same transaction, I'm assuming you want the index to incorporate this new write/update. What do you mean by "read the new index definition"?

@garrensmith garrensmith force-pushed the rfc/006-mango-on-fdb branch from 9e80aed to 5a78a6a Compare January 9, 2020 14:32
Copy link
Contributor

@jaydoane jaydoane left a comment

Choose a reason for hiding this comment

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

Found just a few nits that could be addressed


This document details the data model for storing Mango indexes. The basic model is that we would have a namespace for storing defined indexes and then a dedicated namespace per index for the key/values for a given index. Indexes will be updated in the transaction that a document is written to FoundationDB. When an index is created on an existing database, a background task will build the index up to the Sequence that the index was created at.
This document details the data model for storing Mango indexes. Indexes will be updated in the transaction that a document is written to FoundationDB. When an index is created on an existing database, a background task will build the index up to the Sequence that the index was created at.

Copy link
Contributor

Choose a reason for hiding this comment

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

maybe "... up to the Sequence when the index was created" instead?

# Detailed Description

Mango is a declarative JSON querying syntax that allows a user to retrieve documents based on a given selector. It supports defining indexes for queries which will improve query performance. In CouchDB 2.x Mango is a query layer built on top of Map/Reduce indexes. Each Mango query follows a two step process, first a subset of the selector is converted into a map query to be used with a predefined index or falling back to `_all_docs` if no indexes are available. Each document retrieved from the index is then matched against the query selector.
Mango is a declarative JSON querying syntax that allows a user to retrieve documents based on a selector. Indexes can be defined to improve query performance. In CouchDB 2.x Mango is a query layer built on top of Map/Reduce indexes. Each Mango query follows a two-step process, first a subset of the selector is converted into a map query to be used with a predefined index or falling back to `_all_docs` if no indexes are available. Each document retrieved from the index is then matched against the full query selector.
Copy link
Contributor

Choose a reason for hiding this comment

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

"In CouchDB 2.x Mango..." isn't it actually: "In CouchDB < 4.0 Mango..."?

\x40 Text converted into a sort string
\x50 Array
\x60 Objects
In CouchDB 2.x ICU collation is used to sort string key’s when added to the index’s b-tree. The current way of using ICU string collation won’t work with FoundationDB. To resolve this strings will be converted to an ICU sort string before being stored in FDB. This is an extra performance overhead but will only be done when one when writing a key into the index.
Copy link
Contributor

Choose a reason for hiding this comment

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

only be done when one when writing a key into the index.

typo

1. When a user defines a new index on an existing database, save the index definition along with the `sequence` the index was added at and set the `build_status` to `building` so it won’t be used to service queries.
2. Any write requests (document updates) after the saved index definition will update the index with the document update. Index writers should assume that previous versions of the document have already been indexed.
3. At the same time a background process via `couch_jobs` will start reading sections of the changes feed and building the index, this background process will keep processing the changes read until it reaches the sequence number that the index was saved at. Once it reaches that point, the index is up to date and `build_status` will be marked as `active` and the index can be used to service queries.
4. There is some subtle behavior around step 3 that is worth mentioning. The background process will have the 5-second transaction limit, so it will process smaller parts of the changes feed. Which means that it won’t have one consistent view of the changes feed throughout the index building process. This will lead to a conflict situation when the background process transaction is adding a document to the index while at the same time a write request has a transaction that is updating the same document. There are two possible outcomes to this, if the background process wins, the write request will get a conflict. At that point the write request will try to process the document again, read the old values for that document, remove them from the index and add the new values to the index. If the write request wins, and the background process gets a conflict, then the background process can try again, the document would have been removed from its old position in the changes feed and moved to the later position, so the background process won’t see the document and will then move on to the next one.
Copy link
Contributor

Choose a reason for hiding this comment

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

This will lead to a conflict situation

Is this a conflict or a race? Maybe both?

@garrensmith garrensmith force-pushed the rfc/006-mango-on-fdb branch from 61803f7 to 672d6dd Compare May 18, 2020 11:47
@garrensmith
Copy link
Member Author

I've updated this mango RFC so it is up to date with how it is currently implemented.

@wohali
Copy link
Member

wohali commented May 19, 2020

@garrensmith You have approval from Jay; please merge this.

@garrensmith garrensmith merged commit 6370d8a into master May 20, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants