Commit Graph

820 Commits

Author SHA1 Message Date
Matthew Chen
e70302ac8f Refine NSE completion blocking, message processing. 2021-09-27 19:04:46 -03:00
Matthew Chen
ee8f3dddb8 Merge branch 'release/5.20.4' 2021-09-23 23:51:34 -03:00
Matthew Chen
73b019f8da Refine decrypt deduplication. 2021-09-23 23:22:26 -03:00
Matthew Chen
ad1bdf48d6 Refine decrypt deduplication. 2021-09-23 23:16:41 -03:00
Matthew Chen
59e33de962 Refine decrypt deduplication. 2021-09-23 22:49:44 -03:00
Matthew Chen
2b1945f672 Clean up test logging. 2021-09-23 10:40:05 -03:00
Matthew Chen
b058317777 Assert that GRDB schema/data migrations are complete before we warm caches. 2021-09-23 10:15:34 -03:00
Matthew Chen
1b46d06336 De-duplicate by serverGuid. 2021-09-23 00:08:02 -03:00
Matthew Chen
026b02c267 Deduplication message decryption to avoid spurious errors when NSE and main app message processing race. 2021-09-23 00:08:02 -03:00
Michelle Linington
04c7d8eff4 Update schema definition 2021-09-22 14:05:13 -07:00
Matthew Chen
7356ee90dc Deduplication message decryption to avoid spurious errors when NSE and main app message processing race. 2021-09-22 12:05:28 -07:00
Matthew Chen
8aa213d25b Deduplication message decryption to avoid spurious errors when NSE and main app message processing race. 2021-09-22 12:05:28 -07:00
Michelle Linington
998a98ec88 Migrate from distribution UUID to KeyID 2021-09-09 11:19:57 -07:00
Matthew Chen
feef75a1a4 Tweak CVC loads. 2021-09-08 21:41:13 -03:00
Matthew Chen
9271f83624 Remove databaseChangesWillUpdate. 2021-09-08 21:41:13 -03:00
Matthew Chen
e2920653f6 Start DB change observer display link sooner. 2021-09-08 21:41:13 -03:00
Matthew Chen
f8765e3a0e Rework DebouncedEvent. 2021-09-08 15:04:26 -03:00
Matthew Chen
752bd45e2e Streamline publishing of database change events. 2021-09-08 15:04:26 -03:00
Nora Trapp
2814ab7629 Convert to new Promise library 2021-09-03 11:41:34 -07:00
Michelle Linington
1012d7b42d Tune indices for more performant conversation loads
- Replaces the existing placeholder excluding index for two new indices,
  optimized to reduce disk I/O. These are partial indices, only
  applicable to the placeholder excluding queries performed during
  conversation load. They're also covering indices. Most queries will be
  resolved by only consulting the index. There's no need to consult the
  backing rows in the model_TSInteraction table.
- Adds a helper SQL bytecode explainer for debugging

Tracking down these optimizations took a great deal of time. I promised
in a comment that this commit message will have a thorough explanation
of why these indices are constructed the way they are. The TL;DR:
- There are a bunch of limitations on index column ordering that you
  should be aware of.
- Partial indices can help you avoid some of those limitations
- SQLite can be finnicky when trying to decide if a partial index is
  applicable to a query.
- When an index covers a given query, it can avoid a whole bunch of
  disk I/O.
- Index row size matters, and SQLite may choose a less efficient index
  if it looks like it might be smaller.

The long explanation of lessons learned along the way. All of this is
based on observation, documentation, and trial-and-error:

1. SQLite can be *very* strict when deciding whether or not to use a
   partial index. Compare these two index declarations:
  > CREATE INDEX index1 ON table(uniqueThreadId, id) WHERE 'recordType IS NOT 70';
  > CREATE INDEX index2 ON table(uniqueThreadId, id) WHERE recordType IS NOT 70;

  Note the only difference between the two partial index declarations is
  wrapping the WHERE clause in quotes. The GRDB index builder we were
  using before would do this on our behalf.

  Now let's run this query against both indices. The indices we declared
  *should* be a perfect choice for this query:
  > SELECT COUNT(*) FROM table WHERE uniqueThreadId = 'abc123'
  > AND id > 100 AND recordType IS NOT 70;

  With the first index:
  > `--SEARCH TABLE model_TSInteraction USING INDEX index_interactions_on_threadUniqueId_and_id (uniqueThreadId=? AND id>?)
  With the second index:
  > `--SEARCH TABLE model_TSInteraction USING INDEX index2 (uniqueThreadId=? AND id>?)

  By dropping the quotes around the WHERE clause, SQLite's query planner
  is now convinced that the partial index is applicable to the query
  we're running. This is good, since as SQLite iterates over the index,
  it can skip any recordType comparisons. It also reduces the search
  space, since it doesn't even need to read rows it should exclude.

2. Covering indices can be a huge performance win. An index is covering
   when every column in the index matches some element. Let's again
   compare some indices:
  > CREATE INDEX index1 ON table(uniqueThreadId, id) WHERE recordType IS NOT 70;
  > CREATE INDEX index2 ON table(uniqueThreadId, id, uniqueId) WHERE recordType IS NOT 70;
  > CREATE INDEX index3 ON table(uniqueThreadId, id, recordType, uniqueId) WHERE recordType IS NOT 70;
  (Why a difference between index2 and index3? See the next section)

  When run on this query:
  > SELECT uniqueId FROM table WHERE uniqueThreadId = 'abc123'
  > AND id > 100 AND recordType IS NOT 70;

  The first index:
  > --SEARCH TABLE model_TSInteraction USING INDEX index1 (uniqueThreadId=? AND id>?)
  The second index:
  > --SEARCH TABLE model_TSInteraction USING INDEX index2 (uniqueThreadId=? AND id>?)
  The third index:
  > --SEARCH TABLE model_TSInteraction USING COVERING INDEX index3 (uniqueThreadId=? AND id>?)

  You can see the difference in the actual query bytecode by running:
  "EXPLAIN [query]" (not "EXPLAIN QUERY PLAN"). Notice that when run
  against the first index, it needs to perform two reads. One against
  the table, and another against the index. The only reason it's
  fetching the model is to grab the uniqueId in operation 9.
  > 0     Init           0     12    0                    0   Start at 12
  > 1     OpenRead       0     10    0     67             0   root=10 iDb=0; model_TSInteraction
  > 2     OpenRead       1     162160  0     k(3,,,)        0   root=162160 iDb=0; index1
  > 3     String8        0     1     0     abc123         0   r[1]='abc123'
  > 4     Integer        100   2     0                    0   r[2]=100
  > 5     SeekGT         1     11    1     2              0   key=r[1..2]
  > 6       IdxGT          1     11    1     1              0   key=r[1]
  > 7       DeferredSeek   1     0     0                    0   Move 0 to 1.rowid if needed
  > 8       Column         0     2     3                    0   r[3]=model_TSInteraction.uniqueId
  > 9       ResultRow      3     1     0                    0   output=r[3]
  > 10    Next           1     6     0                    0
  > 11    Halt           0     0     0                    0
  > 12    Transaction    0     0     198   0              1   usesStmtJournal=0
  > 13    Goto           0     1     0                    0

3. SQLite will not consider a partial index covering unless the columns
   defining the index condition are also included in the index content.
   No idea why, but this is why index2 above isn't considered covering
   while index3 is.

4. Index column ordering matters. Briefly, if a WHERE clause in a query
   has any sort of inequality comparisons (everything but IS, IN and =),
   then every subsequent column in an index cannot be used. (mostly)

  For example:
  > CREATE INDEX ON Table(a,b,c);
  > SELECT * WHERE AND b > 100 AND c = "hi";

  Column C isn't going to be usable on the index since we're performing
  an inequality comparison on B. There are loads of exceptions SQLite
  can try to work around this. You can learn more here:
  https://www.sqlite.org/optoverview.html

  How this applies to this change. Consider these two indices:
  > CREATE INDEX index1 ON table(uniqueThreadId, id, recordType, uniqueId) WHERE recordType IS NOT 70;
  > CREATE INDEX index2 ON table(uniqueThreadId, recordType, id, uniqueId) WHERE recordType IS NOT 70;
  > SELECT uniqueId FROM table WHERE uniqueThreadId = 'abc123'
  > AND id > 100 AND recordType IS NOT 70;

  index1 is absolutely going to be the better choice here. Since index1
  is sorted by id first, it's easy for SQLite to binary search to the
  correct offset in the index and then just scan down the index.
  (verifying that each recordType IS NOT 70 along the way, which will
   always hold true because of the partial index condition).

5. Index size matters. At this point, we've constructed this index:
   > CREATE INDEX index1 ON table(uniqueThreadId, id, recordType, uniqueId) WHERE recordType IS NOT 70;

  And SQLite is using it for queries like this. Great!
  > SELECT uniqueId FROM model_TSInteraction
  > WHERE uniqueThreadId = 'whatever' AND recordType IS NOT 70
  > ORDER BY id LIMIT 2 OFFSET 0
  > `--SEARCH TABLE model_TSInteraction USING COVERING INDEX index1 (uniqueThreadId=?)

  Here's a simpler query that we need to run to. It is even less
  specific than the one above so we should expect it to use our
  index.
  > SELECT COUNT(*) FROM model_TSInteraction
  > WHERE uniqueThreadId = 'blah' AND recordType IS NOT 70;
  > `--SEARCH TABLE model_TSInteraction USING COVERING INDEX index_model_TSInteraction_on_uniqueThreadId_and_hasEnded_and_recordType (uniqueThreadId=?)

  As best I can tell, the reason SQLite is picking this existing index
  is because each row in the index is smaller.
  Each row in our index: [String, Int, Int, String]
  Each row in the chosen index: [String, Bool, Int]

  The query planner had to choose, was it better off picking the index
  that's more specific but larger, or less specific but smaller. It
  picked the latter.

  To work around this we can add a *second* index, that's even smaller
  that the index it's choosing.
  > CREATE INDEX index2 ON table(uniqueThreadId, recordType) WHERE recordType IS NOT 70;

  Now the query picks our new index to run the count.
  --SEARCH TABLE model_TSInteraction USING COVERING INDEX index2 (uniqueThreadId=?)
2021-08-30 08:55:53 -03:00
Michelle Linington
94f3d297a4 Fixes a few final sender key issues:
- The excludePlaceholders parameter was inverted, but the DebugFlag
  wasn't
- Fix an issue with how we detect content-less sync messages
2021-08-27 16:12:24 -07:00
Michelle Linington
c37312478d More index tuning 2021-08-26 20:57:06 -07:00
Michelle Linington
d69483e489 Update index to use expression instead of column 2021-08-26 20:57:06 -07:00
Michelle Linington
c7444cb01d Remove isHidden column, use recordType instead 2021-08-26 20:57:06 -07:00
Michelle Linington
5b1b0d3bd0 Make hiddenUntilTimestamp a boolean column 2021-08-26 20:57:06 -07:00
Michelle Linington
276837b62e Bug fixes around cleanup scheduling 2021-08-26 20:29:21 -07:00
Michelle Linington
ee85ecc965 Migrations, renames, more aggressive scheduling of cleanup 2021-08-26 20:29:21 -07:00
Michelle Linington
86310a1855 Handling of invisible interactions 2021-08-26 20:29:21 -07:00
Michelle Linington
596926e63b Re-include sendComplete condition in payload cleanup trigger 2021-08-19 17:01:07 -07:00
Michelle Linington
e9bd6b55ff Swap out column types for more appropriate replacements 2021-08-19 17:01:07 -07:00
Eugene Bistolas
c70817eb1c Group call avatars incorrectly configured, ensure incoming donations do not occur for correct notification settings 2021-08-18 13:26:24 -10:00
Matthew Chen
4b77df38fa Refine names. 2021-08-18 15:01:04 -03:00
Matthew Chen
d51213a8f5 Refine error localizedDescription. 2021-08-18 15:01:04 -03:00
Nora Trapp
1cbe9c3ee7 PR Feedback 2021-08-16 13:28:08 -07:00
Nora Trapp
42b2575775 Add isHighPriority field and promise support to message sender job queue 2021-08-16 13:27:43 -07:00
Nora Trapp
fee1bbddad Add exclusiveProcessIdentifier to SSKJobRecord 2021-08-16 13:27:00 -07:00
Michelle Linington
2470c2108f PR Feedback: sendComplete should default false 2021-08-16 13:14:19 -07:00
Michelle Linington
93d584b239 IOS-1718: Foreign constraint failure on MessageSendLog
Fixes a bug where a incoming delivery receipt can race with an
in-progress send.

- When a message is preparing to be sent, a payload is inserted
- As sends are successful, recipient entries are added to indicate that
  we're awaiting delivery acknowledgement.
- Once all recipient entries have been cleared (every recipient has
  acked), we delete the payload.

If a message is being sent to A and B: A succeeds, but B is delayed, and
A acks before B can be sent, the payload entry will be cleared.

This change adds a "sendComplete" bit to the MSL table to indicate
whether or not the entry should be preserved even if all recipients have
acked.
2021-08-16 13:14:19 -07:00
Michelle Linington
f69c76c1ab Migrate schema to account for new PendingReceipt properties
Most of this was done by Matthew. Thanks Matthew!
2021-08-04 22:17:42 -07:00
Michelle Linington
fdc98cf603 Fix a crash in hasUserInitiatedInteraction
SQL syntax was broken. The IN operand was incorrect.

Before: "recordType IN [9, 70]"
After:  "recordType IN (9, 70)"
2021-07-30 13:38:29 -07:00
Michelle Linington
deab70379a Lint 2021-07-29 15:06:30 -07:00
Michelle Linington
157f024b55 Add deprecated overload for storing optionals in keyValueStore
To store an optional your non-deprecated options are:
- setCodable(optional🔑transaction) which will encode the nil value
- Switching to unwrap the optional before calling either
  setCodable(_🔑transaction) or removeValue(forKey:transaction:)
2021-07-29 15:06:30 -07:00
Michelle Linington
c329d64565 Improved handling of placeholder interactions
- Deferred display now works for "resendable" messages
- Replacement expiration now works
- Error messages now show up correctly in conversation view
2021-07-29 14:59:06 -07:00
Nora Trapp
540df3934d Merge branch 'release/5.17.0' 2021-07-26 14:47:30 -07:00
Nora Trapp
f391708cdc Clear avatarBuilder megaphone if you already have an avatar 2021-07-26 14:45:34 -07:00
Michelle Linington
5b2312787b PR Feedback and bug fixes
- Our SQL schema generator doesn't like "=="
- Various terrible compile time issues that I had missed
- Migrated OWSOutgoingResendResponse from Swift to ObjC. Mantle doesn't
  do great with coding Swift objects. This fixed the bug that was
  preventing resend from working. Resend works correctly now!
2021-07-21 22:58:51 -07:00
Michelle Linington
51f3a58a61 Message send log recording and resend responses 2021-07-21 22:58:51 -07:00
Michelle Linington
023b080dc3 PR Feedback: Remove leftover SDS definitions 2021-07-21 22:58:51 -07:00
Michelle Linington
2eb60643b7 Sender key decryption failure handling
Most of the work for message resend requests. Includes some rudimentary
failure UI.
2021-07-21 22:58:51 -07:00
Nora Trapp
a7e20324fc Merge branch 'release/5.16.0' 2021-07-09 14:52:51 -07:00