-
Notifications
You must be signed in to change notification settings - Fork 340
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
Typesense Import: Index Race Condition #845
Comments
Seems like #822 is related |
Thanks a lot for your investigation @saibotk. If you're up for it, definitely feel free to send in a PR with a proposed change. |
@saibotk thank you for digging into this. Let me know if you're planning to do a PR for this. If not, @tharropoulos from the Typesense team is also available to look into this. |
I could try to come up with some fix for this, but am currently pretty swamped. Obviously we could just drop the cache and always do an exists query but im also not sure if there might be some better solution using other typesense methods. Im open to discuss approaches and would appreciate any insights |
When I tried to take a look at the server logs, if you use If you then restart the worker and execute the import once again, these requests are sent to Typesense: If you then use It must be applicable to the static state generated when flushing the collection, since the change is reflected on the EDIT: provided some more info regarding the differences between |
The scout/src/Engines/TypesenseEngine.php Lines 497 to 505 in 3174219
To ensure matching the state from the server to the worker, if a collection is found, it should then call out to the Typesense server to see if the collection exists on the server as well, if not, it will have to create it on the server. protected function getOrCreateCollectionFromModel($model, bool $indexOperation = true): TypesenseCollection
{
$method = $indexOperation ? 'indexableAs' : 'searchableAs';
$collection = $this->typesense->getCollections()->{$model->{$method}()};
// Initialize a flag to check if the collection exists
$collectionExists = false;
if ($collection->exists()) {
$collectionName = $model->{$method}();
try {
$this->typesense->collections[$collectionName]->retrieve();
$collectionExists = true;
} catch (TypesenseClientError $e){
$collectionExists = false;
}
}
// Use the flag to determine the next steps
if ($collectionExists) {
return $this->typesense->getCollections()->{$collectionName};
}
... |
@saibotk First of all, thank you for taking the time to investigate so thoroughly and getting us in the right track. Could you verify that this change fixes the problems on your side as well? EDIT: fix typo |
…846) * feat(typesense): sync server state in getOrCreateCollectionFromModel This commit updates the `getOrCreateCollectionFromModel` method in `TypesenseEngine` to verify if a collection exists on the server. If not found, it is created. This ensures consistency between the worker and server states. * style: fix styleCI errors refactor: remove uneeded cast of flag The collectionExists flag was already cast as false, so recasting it serves no purpose * Update TypesenseEngine.php --------- Co-authored-by: Taylor Otwell <[email protected]>
Yes i will do so on friday when im back at work thanks for taking the time 🙌 |
@tharropoulos Okay got time to test this now and thats my result:
What do you think? Thank you for the quick response! |
In //laravel#820 we introduced an exists check method, which then lead to static state issues as described in laravel#845 Those where addressed in laravel#846 But now that the function always double-checks the existence, we can remove the exists check entirely and only rely on the Typesense server response for this state. This "fixes" issues where the server already has a collection and the client would try to recreate it. E.g. when it has flushed the index and another process or worker then creates the collection.
In laravel#820 we introduced an exists check method, which then lead to static state issues as described in laravel#845 Those where addressed in laravel#846 But now that the function always double-checks the existence, we can remove the exists check entirely and only rely on the Typesense server response for this state. This "fixes" issues where the server already has a collection and the client would try to recreate it. E.g. when it has flushed the index and another process or worker then creates the collection.
In laravel#820 we introduced an exists check method, which then lead to static state issues as described in laravel#845 Those where addressed in laravel#846 But now that the function always double-checks the existence, we can remove the exists check entirely and only rely on the Typesense server response for this state. This "fixes" issues where the server already has a collection and the client would try to recreate it. E.g. when it has flushed the index and another process or worker then creates the collection.
In laravel#820 we introduced an exists check method, which then lead to static state issues as described in laravel#845 Those where addressed in laravel#846 But now that the function always double-checks the existence, we can remove the exists check entirely and only rely on the Typesense server response for this state. This "fixes" issues where the server already has a collection and the client would try to recreate it. E.g. when it has flushed the index and another process or worker then creates the collection.
Just checking in after being out for a bit: was this fixed now? |
@driesvints Yes the issue itself is fixed and this can be closed, the followup PR is just to improve the fix. |
* refactor(typesense): remove unused exists checks In #820 we introduced an exists check method, which then lead to static state issues as described in #845 Those where addressed in #846 But now that the function always double-checks the existence, we can remove the exists check entirely and only rely on the Typesense server response for this state. This "fixes" issues where the server already has a collection and the client would try to recreate it. E.g. when it has flushed the index and another process or worker then creates the collection. * Update TypesenseEngine.php --------- Co-authored-by: Taylor Otwell <[email protected]>
I just tried to import ~1.6m rows in chunks of 500 and hit this error 431 times before hitting a memory issue on a node. I'm on TypeSense cloud v26 and Scout v10.11. I ran the import directly after a flush. Is there a recommended way to do an initial import. I'll turn the queue off and try again once I have upgraded my nodes in TypeSense Cloud. |
Weird, the main issue should be fixed in that version. Though can you try out the latest commit? I'm curious whether that fixes something for your use case @patrickomeara. |
OK, after the node upgrade had finished I flushed and re-imported and only got the Thanks for your swift reply @saibotk |
I believe the issue reported here still exists after #846. If you import greater than the chunk size (500) on a queue server, there exists a race condition that can cause the |
Throwing a try/catch around the collection creation call at line 526 fixes the issue. It occurs when two chunks are picked up by two queue processes at the same time, causing a race condition:
|
Scout Version
10.9.0
Scout Driver
Typesense
Laravel Version
10.48.10
PHP Version
8.3.9
Database Driver & Version
Typesense 0.25.2
SDK Version
Typesense PHP 4.9.3
Meilisearch CLI Version
No response
Description
When deleting the index and afterwards importing all models, we can reach a race condition, where the import fails because some jobs run in parallel and all try to create or access the index.
Only one succeeds and the others will, for example, throw this error:
Typesense\Exceptions\ObjectNotFound: Not Found
or interestingly, only rarely,
Typesense\Exceptions\ObjectAlreadyExists: A collection with name
already exists.
.This happens when using queued imports. I don't exactly understand why it returns Not Found instead of the more rare collection already exists error, which is logical, but this might be some technical detail in the typesense engine.
E.g. using Horizon with
minProcesses = 5
and running the following command on a table with enough entries e.g. ~ 40.000 will sometimes result in those exceptions on some of the jobs.We should try to make the import more robust and maybe ensure that the index is created before the documents are imported? I'm happy to hear your solutions on this, but i think we should fix this flaw in scout directly.
Steps To Reproduce
In any scout repository with Horizon or some queue workers and a table with enough elements such that chunking happens, run:
And sometimes the race condition will cause some jobs to fail.
The text was updated successfully, but these errors were encountered: