Skip to content
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

Management Command Database Locking #219

Merged
merged 20 commits into from Feb 10, 2022

Conversation

ajslater
Copy link
Contributor

@ajslater ajslater commented Feb 5, 2022

Problem

If xapian-haystack management commands are used today with more than one worker it breaks with xapian.DatabaseLockError

Solution

This solution was cribbed from @karolyi in his issue report: #174
.
I implement a lockfile decorator around the update() and remove() methods.

Decisions

It paces the lockfile in the xapian-index directory, which should not affect performance.

Tests

This PR includes a new test_management_commands.py test suite that runs the management commands. This test suite fails if the lockfile isn't implemented as you can see in this branch: https://github.com/ajslater/xapian-haystack/actions/runs/1798091039

Workarounds

This PR can be used as inspiration for an easy workaround for this issue. Subclass XapianSearchBackend and BaseEngine with these changes. and use that in your Haystack settings. Example here: https://github.com/ajslater/codex/blob/develop/codex/search_engine.py

@ajslater ajslater changed the title Filelock management tests Management Command Database Locking Feb 5, 2022
Copy link
Collaborator

@claudep claudep left a comment

Choose a reason for hiding this comment

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

Thanks! We are on the right track :-) I'll work on removing Python 3.6 support to simplify things a bit.

.github/workflows/test.yml Outdated Show resolved Hide resolved
requirements.txt Outdated Show resolved Hide resolved
python-version: ["3.6", "3.9", "3.10"]
django-version: ["2.2", "3.1", "3.2"]
xapian-version: ["1.4.18"]
filelock-version: ["3.4.1"]
Copy link
Collaborator

Choose a reason for hiding this comment

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

As already commented, I'd rather suggest dropping Python 3.6 support and we can get rid of filelock-version stuff in this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is my first time using github actions, but this test suite appears to fail if filelock isn't explicitly installed in the "Install Django and other Python dependencies" step below. The matrix here seems a convenient place to store the version number next to the other version numbers. Also worth looking at is that you may have a comment on that i've added django 4.0 to the python 3.10 test.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I would simply drop any change to test.yml in this patch. Testing Python 3.10 or Django 4.0 should be another issue/PR. And filelock should be installed as part of dependencies installation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Still todo?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • I've restored the django 4.0 include version back to 3.2
  • The python 3.10 matrix & include requirements were added six months ago.
  • The filelock-version additions appear to be necessary. Specifically, the tests fail importing filelock if the dependency installations on line 75 does not install filelock.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, I understand now, the test.yml doesn't use the requirements.txt. Sorry, you are right.

tests/xapian_tests/tests/test_management_commands.py Outdated Show resolved Hide resolved
tests/xapian_tests/tests/test_management_commands.py Outdated Show resolved Hide resolved
tests/xapian_tests/tests/test_management_commands.py Outdated Show resolved Hide resolved

# Ensure the lockfile exists
try:
self.lockfile.parent.mkdir(parents=True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't the parent path be created earlier in the backend __init__ method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's what i thought too. However the backend.clear() method destroys the entire xapian-index directory with shutil. xapian database or something ends up recreating it, maybe, but that happens after our file locking with update, so tests will fail without this and it seems that users who use the clear() or rebuild_index() method could jam themselves up on it too.

xapian_backend.py Outdated Show resolved Hide resolved
@claudep
Copy link
Collaborator

claudep commented Feb 5, 2022

I think we should still add a new custom connection option to opt out of the lock, so people not needing a multiprocess access have a way to keep better performances by not using the lock.

@claudep
Copy link
Collaborator

claudep commented Feb 5, 2022

By the way, the minimal Python version is now 3.7.
77d1fc8

@ajslater
Copy link
Contributor Author

ajslater commented Feb 6, 2022

I've fixed or commented on all the requested changes. Take another look. An addition I've made is that I noticed that backend.path can be an in-memory database so i've disabled the filelock in that case.

I can't find the comment, but I recall you asking if we might provide an option for people not to use filelocking in general. Should I add a boolean flag to connection_options?

xapian_backend.py Outdated Show resolved Hide resolved
@claudep
Copy link
Collaborator

claudep commented Feb 6, 2022

I can't find the comment, but I recall you asking if we might provide an option for people not to use filelocking in general. Should I add a boolean flag to connection_options?

Yes, please.

@ajslater
Copy link
Contributor Author

ajslater commented Feb 7, 2022

Latest commits include the USE_LOCKFILE connection parameter with a short documentation line.

@@ -175,13 +195,18 @@ def __init__(self, connection_alias, **connection_options):
% connection_alias)

self.path = connection_options.get('PATH')
self.use_lockfile = connection_options.get('USE_LOCKFILE', True)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for the flag addition. I would pop the option before the super().__init__ call to avoid passing it to the BaseSearchBackend.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea. Done.
I've also casted it to a bool just so it doesn't end up with an unexpected value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was also changed to a setting from a connection option

README.rst Show resolved Hide resolved
@coveralls
Copy link

coveralls commented Feb 7, 2022

Coverage Status

Coverage decreased (-0.02%) to 97.558% when pulling 5633425 on ajslater:filelock-management-tests into 3fc4cfe on notanumber:master.

verbosity=2,
workers=10,
batchsize=2,
)
Copy link
Collaborator

Choose a reason for hiding this comment

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

According to https://docs.djangoproject.com/en/4.0/ref/django-admin/#output-redirection, you should be able to capture stderr with the stderr= param of call_command.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried that and it didn't work.
It turns out for that to work the Command implementer has to explicitly send prints to print("blah", stderr=self.stderr), where self is BaseCommand. And I think these messages aren't even coming from python, but the underlying xapian database implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

And the underlying xapian database implementation is probably where all this file locking nonsense should be, but I digress.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also, the failure mode for this test is only occasionally parsing that stderr string. Most of the time call_command() throws an exception and halts the test before the assert.

@claudep
Copy link
Collaborator

claudep commented Feb 8, 2022

Sorry, I merged another pull request which forces you to rebase. Also, would you mind adding a line in the CHANGELOG?

@ajslater
Copy link
Contributor Author

ajslater commented Feb 8, 2022

Merged from upstream and and added a CHANGELOG item.

@claudep
Copy link
Collaborator

claudep commented Feb 9, 2022

Thanks, I think this is now ready. Only rebase and we are done.

commit 41b85e2
Author: AJ Slater <aj@slater.net>
Date:   Sun Feb 6 11:40:31 2022 -0800

    use static method

commit f8b895d
Author: AJ Slater <aj@slater.net>
Date:   Sun Feb 6 11:38:08 2022 -0800

    restore setup methods

commit 984eb63
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 18:24:31 2022 -0800

    remove some duplication

commit 829799e
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 16:13:13 2022 -0800

    smaller batchsize fewer entries

commit e61f0c5
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 13:54:18 2022 -0800

    more correct stderr check

commit df114f4
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:50:46 2022 -0800

    move the directory creator back to the wrapper

commit 0d23dac
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:40:12 2022 -0800

    move directory creator to test setup

commit 50258e6
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:35:10 2022 -0800

    move directory recreation to the test. remove self.lockfile

commit 8760482
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:27:08 2022 -0800

    try to create the lockfile in backend.init

commit 6d73f04
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:24:21 2022 -0800

    revise setup filelock version

commit 839ed6a
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:23:04 2022 -0800

    include doesn't inherit?

commit 6b916fe
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:17:40 2022 -0800

    forgot to install it

commit 61d1788
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:13:46 2022 -0800

    add filelock version back

commit 7debc3d
Author: AJ Slater <aj@slater.net>
Date:   Sat Feb 5 12:02:07 2022 -0800

    smaller number of blog entries
@ajslater
Copy link
Contributor Author

ajslater commented Feb 9, 2022

Rebased on current master.

@claudep
Copy link
Collaborator

claudep commented Feb 10, 2022

Thanks a lot for your work!

@claudep claudep merged commit 870b48d into notanumber:master Feb 10, 2022
@ajslater
Copy link
Contributor Author

Nice, looking forward to the release. Thanks for enforcing quality on this project!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants