-
Notifications
You must be signed in to change notification settings - Fork 488
[ADR] JMAP: Avoid ElasticSearch on critical reads #259
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
Changes from all commits
8deb190
a4ae094
532cccf
bc12fbc
5a3640f
2fe64cb
25c7d92
70f7898
5a03c80
892da60
3907a83
a95aa3d
f8d1b5d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| # 43. Avoid ElasticSearch on critical reads | ||
|
|
||
| Date: 2020-11-11 | ||
|
|
||
| ## Status | ||
|
|
||
| Accepted (lazy consensus). | ||
|
|
||
| Scope: Distributed James | ||
|
|
||
| ## Context | ||
|
|
||
| A user willing to use a webmail powered by the JMAP protocol will end up doing the following operations: | ||
| - `Mailbox/get` to retrieve the mailboxes. This call is resolved against metadata stored in Cassandra. | ||
| - `Email/query` to retrieve the list of emails. This call is nowadays resolved on ElasticSearch for Email search after | ||
| a right resolution pass against Cassandra. | ||
| - `Email/get` to retrieve various levels of details. Depending on requested properties, this is either | ||
| retrieved from Cassandra alone or from ObjectStorage. | ||
|
|
||
| So, ElasticSearch is queried on every JMAP interaction for listing emails. Administrators thus need to enforce availability and good performance | ||
| for this component. | ||
|
|
||
| Relying on more services for every read also harms our resiliency as ElasticSearch outages have major impacts. | ||
|
|
||
| Also we should mention our ElasticSearch implementation in Distributed James suffers the following flaws: | ||
| - Updates of flags lead to updates of the all Email object, leading to sparse segments | ||
| - We currently rely on scrolling for JMAP (in order to ensure messageId uniqueness in the response while respecting limit & position) | ||
| - We noticed some very slow traces against ElasticSearch, even for simple queries. | ||
|
|
||
| Regarding Distributed James data-stores responsibilities: | ||
| - Cassandra is the source of truth for metadata, its storage needs to be adapted to known access patterns. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand this sentence
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cassandra is the source of truth for metadata -> I think you have no problem understanding this its storage needs to be adapted to known access patterns. -> This come from Cassandra storage constraints. You need to plan your reads ahead (or allow filtering and kill your cluster) It seems pretty clear to me as it is, please do not hesitate to suggest enhencements.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh yes, i now understand. The ambiguity comes from the fact I expect responsibilities in this list, not details about how Cassandra works.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's responsibility is to handle known, common data access pattern, that's not mutually exclusive. |
||
| - ElasticSearch allows resolution of arbitrary queries, and performs full text search. | ||
|
|
||
| ## Decision | ||
|
|
||
| Provide an optional view for most common `Email/query` requests both on Draft and RFC-8621 implementations. | ||
| This includes filters and sorts on 'sentAt'. | ||
|
|
||
| This view will be stored into Cassandra, and updated asynchronously via a MailboxListener. | ||
|
|
||
| ## Consequences | ||
|
|
||
| A migration task will be provided for new adopters. | ||
|
|
||
| Administrators would be offered a configuration option to turn this view on and off as needed. | ||
|
|
||
| If enabled, given clients following well defined Email/query requests, administrators would no longer need | ||
| to ensure high availability and good performances for ElasticSearch to ensure availability of basic usages | ||
| (mailbox content listing). | ||
|
|
||
| Given these pre-requisites, we thus expect a decrease in overall ElasticSearch load, allowing savings compared | ||
| to actual deployments. Furthermore, we expect better performances by resolving such queries against Cassandra. | ||
|
|
||
| The expected added load to Cassandra is low, as the search is a simple Cassandra read. As we only store messageId, | ||
| Cassandra dataset size will only grow of a few percents if enabled. | ||
|
|
||
| ## Alternatives | ||
|
|
||
| Those not willing to adopt this view will not be affected. By disabling the listener and the view usage, they will keep | ||
|
chibenwa marked this conversation as resolved.
|
||
| resolving all `Email/query` against ElasticSearch. | ||
|
chibenwa marked this conversation as resolved.
|
||
|
|
||
| Another solution is to implement the projecting using a in-memory datagrid such as infinispan. The projection | ||
| would be computed using a MailboxListener and the data would be first fetched from this cache and fallback to | ||
| ElasticSearch. We did not choose it as Cassandra is already there, well mastered, as disk storage is cheaper than | ||
| memory. InfiniSpan would moreover need additional datastore to allow a persistent state. Infinispan on the other hand | ||
| would be faster and would have less restrictions on data filtering and sorting. Also this would require one more software dependency. | ||
|
|
||
| ## Example of optimized JMAP requests | ||
|
|
||
| ### A: Email list sorted by sentAt, with limit | ||
|
chibenwa marked this conversation as resolved.
|
||
|
|
||
| RFC-8621: | ||
|
|
||
| ``` | ||
| ["Email/query", | ||
| { | ||
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | ||
| "filter: { | ||
| "inMailbox":"abcd" | ||
| } | ||
| "comparator": [{ | ||
| "property":"sentAt", | ||
| "isAscending": false | ||
| }], | ||
| "position": 30, | ||
| "limit": 30 | ||
| }, | ||
| "c1"] | ||
| ``` | ||
|
|
||
| Draft: | ||
|
|
||
| ``` | ||
| [["getMessageList", {"filter":{"inMailboxes": ["abcd"]}, "sort": ["date desc"]}, "#0"]] | ||
| ``` | ||
|
|
||
| ### B: Email list sorted by sentAt, with limit, after a given receivedAt date | ||
|
chibenwa marked this conversation as resolved.
|
||
|
|
||
| RFC-8621: | ||
|
|
||
| ``` | ||
| ["Email/query", | ||
| { | ||
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", | ||
| "filter: { | ||
| "inMailbox":"abcd", | ||
| "after": "aDate" | ||
| } | ||
| "comparator": [{ | ||
| "property":"sentAt", | ||
| "isAscending": false | ||
| }], | ||
| "position": 30, | ||
| "limit": 30 | ||
| }, | ||
| "c1"] | ||
| ``` | ||
|
chibenwa marked this conversation as resolved.
|
||
|
|
||
| Draft: Draft do only expose a single date property thus do not differenciate sentAt from receivedAt. Draft adopts sentAt | ||
| to back the date property up, thus the above request cannot be written using draft syntax. | ||
|
|
||
| ### C: Email list sorted by sentAt, with limit, after a given sentAt date | ||
|
|
||
| Draft: | ||
|
chibenwa marked this conversation as resolved.
|
||
|
|
||
| ``` | ||
| [["getMessageList", {"filter":{"after":"aDate", "inMailboxes": ["abcd"]}, "sort": ["date desc"]}, "#0"]] | ||
| ``` | ||
|
|
||
|
chibenwa marked this conversation as resolved.
|
||
| RFC-8621: There is no filter properties targeting "sentAt" thus the above request cannot be written. | ||
|
|
||
| ## Cassandra table structure | ||
|
|
||
| Several tables are required in order to implement this view on top of Cassandra. | ||
|
|
||
| Eventual denormalization consistency can be enforced by using BATCH statements. | ||
|
|
||
| A table allows sorting messages of a mailbox by sentAt, allows answering A and C: | ||
|
|
||
| ``` | ||
| TABLE email_query_view_sent_at | ||
| PRIMARY KEY mailboxId | ||
| CLUSTERING COLUMN sentAt | ||
| CLUSTERING COLUMN messageId | ||
| ORDERED BY sentAt | ||
| ``` | ||
|
|
||
| A table allows filtering emails after a receivedAt date. Given a limited number of results, soft sorting and limits can | ||
| be applied using the sentAt column. This allows answering B: | ||
|
|
||
| ``` | ||
| TABLE email_query_view_sent_at | ||
| PRIMARY KEY mailboxId | ||
| CLUSTERING COLUMN receivedAt | ||
| CLUSTERING COLUMN messageId | ||
| COLUMN sentAt | ||
| ORDERED BY receivedAt | ||
| ``` | ||
|
|
||
| Finally upon deletes, receivedAt and sentAt should be known. Thus we need to provide a lookup table: | ||
|
|
||
| ``` | ||
| TABLE email_query_view_date_lookup | ||
| PRIMARY KEY mailboxId | ||
| CLUSTERING COLUMN messageId | ||
| COLUMN sentAt | ||
| COLUMN receivedAt | ||
| ``` | ||
|
|
||
| Note that to handle position & limit, we need to fetch `position + limit` ordered items then removing `position` firsts items. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any clue why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And clue why.
But ElasticSearch slow performance likely would require its own ADR. That's a lengthy topic.
Paging is one, there's many others. I described scrolling & data mutabilityu above.