User tests: Successful: Unsuccessful:
Smart Search creates an index of all items it is supposed to search through either by indexing it directly when saving the item or by mass-indexing it in the Smart Search component itself. During that indexing process, it splits up the words into tokens, does some processing and saves them in the database in the #__finder_tokens table. From there it copies them over to the #__finder_tokens_aggregate table and sums them up to create the weights for the actual searching. From there the tokens are inserted or updated in the different permanent tables Smart Search uses. As you might have guessed by the word "permanent", the two previous tables are only temporary tables.
These temporary tables are using the MEMORY type in MySQL, which are very fast, but can run into issues. If the document is very large, the number of tokens written to the tables can overrun the tables, resulting in a fatal error. For that case, we currently have a method in place to switch to a "normal" InnoDB table. That makes indexing a lot slower, but at least it works, right? Not exactly. If you are running on a MySQL cluster, you don't have the MEMORY tables available at all (#42414) and our code does not properly handle that.
At the same time, our indexing code is not safe against mutual interference. If two authors click on "save" at the same time, their content might not be indexed correctly and two separate indexing runs would basically completely mess each other up, since they are using the same tables and expect to have each table exclusively for themselfs.
The whole situation is very frustrating and also complicated. I first looked into using different tables and it turns out that MEMORY tables are probably the worst solution for this. A better solution would be temporary tables, something that both MySQL and Postgres both support. (Postgres does NOT have MEMORY tables) Temporary tables basically do the same thing as MEMORY tables, but they are not limited like MEMORY tables are. Instead, they will automatically switch over to hard drive when they become to big (and also back to memory) and they are only present per database session. So exactly what we need, right?
Yes, but why are we using a temporary database table at all? We already have all the data in memory and sending it to the database to sum up a few numbers before saving it to the permanent tables feels strange as well. Can we do all of this in PHP itself? Do we benefit from this? Short answer: yes. Long answer: it depends.
I tested this new implementation on a customers site with ~4200 indexable items, cleared the index and ran the indexing on the CLI with the old and the new code. Phrase searching was disabled and stemming was enabled. The old code took about 5 minutes to completely index everything, the new code took about 4 minutes. This is a pretty good result, since about 75% of the runtime is eaten up by creating the result object, tokenizing the text, stemming everything and only 25% were storing the tokens in the database.
That sounded pretty good, so I chose a few additional test scenarios. First of all, from those 4200 indexable items, 300 were selected at random to compare the runtime and memory usage. Then I also used "War and Peace" from Tolstoi and the complete works of Shakespear and ran them both with Phrase searching enabled and disabled. These are the results:
| Scenario | Phrase searching | php vs. sql | additional memory consumption by PHP |
|---|---|---|---|
| 300 real articles | no | −23,5 % | +213 KB |
| 300 real articles | yes | −14,5 % | +558 KB |
| War and Peace (~1.300 S.) | no | −61,6 % | +1,9 MB |
| Shakespeare (~2.000 S.) | no | −71,1 % | +1,9 MB |
| War and Peace | yes | +42,2 % | +77 MB |
| Shakespeare | yes | +19,4 % | +153 MB |
In all cases, with the old and the new code, the index was the same after the respective run. The observations for me from these tests is:
I've tested this on a Windows machine. On a real webserver the gains would be slightly smaller, since the I/O on Linux tends to be better.
This PR switches the implementation from using the database as temporary storage and instead calculates the necessary data in PHP in memory. In order to prevent the memory running full for very large documents, there is a mechanism to flush the calculated data to the permanent tables at a certain threshold.
Long story short: You want a reasonably large set of data to index. If you have a few hundred articles, that would be good. Ideally, you would also have access to the CLI of your site, to run the tests there. The CLI will return the time and memory usage automatically. Alternatively you can run the normal indexing process in the backend component.
Possible test #1:
php cli/joomla.php finder:index purge THIS WILL CLEAR YOUR INDEX BEFORE RE-INDEXING!!#__finder_links_terms and #__finder_terms.Possible test #2:
Smart Search -> Index and clear the index.#__finder_links_terms and #__finder_terms.In both cases you should end up at the same numbers for the database tables. Ideally your indexing should take less time and less memory when these changes are applied.
Please select:
Documentation link for guide.joomla.org:
No documentation changes for guide.joomla.org needed
Pull Request link for manual.joomla.org:
No documentation changes for manual.joomla.org needed
| Status | New | ⇒ | Pending |
| Category | ⇒ | Administration com_finder |
| Labels |
Added:
PR-6.2-dev
|
||
| Labels |
Added:
Conflicting Files
|
||
| Labels |
Removed:
Conflicting Files
|
||
This pull request has conflicts, please resolve those before we can evaluate the pull request.