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

Fix logic bug in external file clean up #956

Merged
merged 19 commits into from
Sep 14, 2021

Conversation

zitrosolrac
Copy link
Contributor

fix #953

@zitrosolrac zitrosolrac force-pushed the external_removal_fail branch from 49206ef to e0bd234 Compare September 9, 2021 20:04
@zitrosolrac zitrosolrac marked this pull request as ready for review September 9, 2021 21:11
@@ -338,7 +338,7 @@ def delete(self, *, delete_external_files=None, limit=None, display_progress=Tru
error_list = []
for uuid, external_path in items:
try:
count = (self & {'hash': uuid}).delete_quick(get_count=True) # optimize
count = len(self & {'hash': uuid}) # optimize
except Exception:
pass # if delete failed, do not remove the external file
Copy link
Member

Choose a reason for hiding this comment

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

The comment is stale. No delete has been attempted.

@@ -338,7 +338,7 @@ def delete(self, *, delete_external_files=None, limit=None, display_progress=Tru
error_list = []
for uuid, external_path in items:
try:
count = (self & {'hash': uuid}).delete_quick(get_count=True) # optimize
count = len(self & {'hash': uuid}) # optimize
except Exception:
pass # if delete failed, do not remove the external file
Copy link
Member

Choose a reason for hiding this comment

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

What exception is expected from the len query? Why is this helpful?

@@ -338,7 +338,7 @@ def delete(self, *, delete_external_files=None, limit=None, display_progress=Tru
error_list = []
for uuid, external_path in items:
try:
count = (self & {'hash': uuid}).delete_quick(get_count=True) # optimize
count = len(self & {'hash': uuid}) # optimize
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need to get the count? Why do we need an extra query rather than deleting directly?

@@ -347,6 +347,8 @@ def delete(self, *, delete_external_files=None, limit=None, display_progress=Tru
self._remove_external_file(external_path)
Copy link
Member

Choose a reason for hiding this comment

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

Why is it better to remove the external file before deleting from the external table. What if the delete from the external table fails?

Copy link
Collaborator

@guzman-raphael guzman-raphael left a comment

Choose a reason for hiding this comment

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

@zitrosolrac Thanks for the PR! 🤝

Need a few more refinements to the delete algorithm, test, and a bit more clean up.
Also, make sure to remove the binary files that were accidentally committed.

@@ -20,7 +20,7 @@
__author__ = 'Edgar Walker, Fabian Sinz, Dimitri Yatsenko, Raphael Guzman'

# turn on verbose logging
logging.basicConfig(level=logging.DEBUG)
#logging.basicConfig(level=logging.DEBUG)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Uncomment this. Let's not commit debug changes.

Suggested change
#logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.DEBUG)

from .schema_external import SimpleRemote
from .schema_external import stores_config, SimpleRemote, Simple, schema

import json
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems like this is no longer necessary.

Suggested change
import json


import json
import os
from os import stat
Copy link
Collaborator

Choose a reason for hiding this comment

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

No longer necessary

Suggested change
from os import stat

Comment on lines 14 to 15
import pwd
from pwd import getpwuid
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
import pwd
from pwd import getpwuid

@@ -21,32 +26,14 @@ def tearDown(self):
dj.config['stores']['local']['location'] = current_location_local


def test_external_put():
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why was this deleted?

def test_remove_fail():
#https://github.com/datajoint/datajoint-python/issues/953

#print(json.dumps(dj.config['stores'], indent=4))
Copy link
Collaborator

Choose a reason for hiding this comment

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

These unnecessary comments should be cleaned up if they don't contain notes.


#print('location')
# print('\n IN TEST: BEFORE DELETE: list of dir stores, local, location')
print('stores location -----------\n')
Copy link
Collaborator

Choose a reason for hiding this comment

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

These prints do not look like they are necessary for the test.

Comment on lines 121 to 123
path1 = dj.config['stores']['local']['location'] + '/djtest_extern/4/c/'

argDir = dj.config['stores']['local']['location'] + '/djtest_extern/4/c/'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could be simplified to one variable.

@@ -338,7 +338,7 @@ def delete(self, *, delete_external_files=None, limit=None, display_progress=Tru
error_list = []
for uuid, external_path in items:
try:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Looks like lines 340-345 are unnecessary here.

datajoint/external.py Show resolved Hide resolved
@guzman-raphael
Copy link
Collaborator

@zitrosolrac oh forgot one thing. Can you please update the changelog and doc-parts release notes as well.

…delete method, and re write test_removal_fail
Copy link
Collaborator

@guzman-raphael guzman-raphael left a comment

Choose a reason for hiding this comment

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

@zitrosolrac Nice work man! Just some minor things left. 👏

datajoint/external.py Show resolved Hide resolved
tests/test_external.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@guzman-raphael guzman-raphael left a comment

Choose a reason for hiding this comment

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

@zitrosolrac 🎉 WooHoo! 🎉 Nice job on a tricky one 👍

self._remove_external_file(external_path)
except Exception as error:
# adding row back into table after failed delete
self.insert1(row[0])
Copy link
Member

Choose a reason for hiding this comment

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

set skip_duplicates=True to address potential race conditions.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's okay not to re-insert if the delete fails. The procedure will return the undeleted files to the user for special handling. Without implementing an actual transaction management process, we can allow some orphaned files in the external storage and provide an additional cleanup utility.

Copy link
Collaborator

@guzman-raphael guzman-raphael Sep 13, 2021

Choose a reason for hiding this comment

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

@dimitri-yatsenko That is a good suggestion for the skipping duplicates. Regarding the cleanup, I feel this is actually necessary. There is basically 2 primary concerns:

  • The user has only one chance to catch the errors. Meaning that running the external delete/clean up multiple times will yield no change though it has only been removed from the external tracking table. This gives a false sense that the system has been 'cleaned-up' but it has not been removed from the actual store (this is the main complaint from the user in issue ExternalTable.delete should not remove row on error #953). We can provide an additional utility to resolve this but that would be expensive to run as it would have to 'crawl' the entire store to find objects that don't exist within the external tracking table. By simply inserting it back when there is an exception, we are allowing the error to be more visible but most importantly reproducible.
  • schema.external[store].delete() is not currently documented that it returns a list of the errors. Users are most likely unaware of this feature and therefore aren't using it properly. @zitrosolrac Could you open an issue on this in our datajoint-docs and reference it in ExternalTable.delete should not remove row on error #953? (Filing for now since our new team members haven't been oriented to the docs setup yet).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
self.insert1(row[0])
self.insert1(row[0], skip_duplicates=True)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@guzman-raphael Got it! I'll open an issue on this in the datajoint-docs and add the necessary reference.

self._remove_external_file(external_path)
except Exception as error:
# adding row back into table after failed delete
self.insert1(row[0])
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
self.insert1(row[0])
self.insert1(row[0], skip_duplicates=True)

Co-authored-by: Raphael Guzman <[email protected]>
@dimitri-yatsenko dimitri-yatsenko merged commit 77fefd4 into datajoint:master Sep 14, 2021
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.

ExternalTable.delete should not remove row on error
3 participants