datagouv / data.gouv.fr

Ce dépôt rassemble les tickets techniques qui portent sur data.gouv.fr.
https://www.data.gouv.fr
76 stars 14 forks source link

Enquêter sur les erreurs créées lors d'upload de fichiers #1274

Open maudetes opened 9 months ago

maudetes commented 9 months ago

Lors de l'upload de ressources de 3G+, des erreurs flask_storage occurrent (systématiquement?).

L'upload a lieu chunks par chunks, avec une combinaison des chunks à la fin en un seul fichier.

Les erreurs sont les suivantes :

FileExists lors de l'upload d'un chunk [Erreur Sentry](https://errors.data.gouv.fr/organizations/sentry/issues/131578) ``` FileExists: 8a03a5a5-60bb-4754-865e-f4fc8c878395/197 File "flask/app.py", line 1523, in full_dispatch_request rv = self.dispatch_request() File "flask/app.py", line 1509, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "udata/api/__init__.py", line 132, in wrapper return func(*args, **kwargs) File "flask_restx/api.py", line 404, in wrapper resp = resource(*args, **kwargs) File "flask/views.py", line 84, in view return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs) File "flask_restx/resource.py", line 46, in dispatch_request resp = meth(*args, **kwargs) File "udata/api/__init__.py", line 123, in wrapper return func(*args, **kwargs) File "flask_restx/marshalling.py", line 244, in wrapper resp = f(*args, **kwargs) File "udata/core/dataset/api.py", line 400, in post infos = self.handle_upload(dataset) File "udata/core/dataset/api.py", line 378, in handle_upload infos = handle_upload(storages.resources, prefix) File "udata/core/storages/api.py", line 136, in handle_upload save_chunk(uploaded_file, args) File "udata/core/storages/api.py", line 97, in save_chunk chunks.save(file, filename=filename) File "flask_storage/storage.py", line 296, in save raise FileExists(filename) ```
FileNotFound lors de la combinaison des chunks [Erreur Sentry](https://errors.data.gouv.fr/organizations/sentry/issues/132713/) ``` FileNotFound: 3abea732-2729-421d-a9ea-cbe688eb8ac2/0 File "flask/app.py", line 1523, in full_dispatch_request rv = self.dispatch_request() File "flask/app.py", line 1509, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "udata/api/__init__.py", line 132, in wrapper return func(*args, **kwargs) File "flask_restx/api.py", line 404, in wrapper resp = resource(*args, **kwargs) File "flask/views.py", line 84, in view return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs) File "flask_restx/resource.py", line 46, in dispatch_request resp = meth(*args, **kwargs) File "udata/api/__init__.py", line 123, in wrapper return func(*args, **kwargs) File "flask_restx/marshalling.py", line 244, in wrapper resp = f(*args, **kwargs) File "udata/core/dataset/api.py", line 400, in post infos = self.handle_upload(dataset) File "udata/core/dataset/api.py", line 378, in handle_upload infos = handle_upload(storages.resources, prefix) File "udata/core/storages/api.py", line 138, in handle_upload fs_filename = combine_chunks(storage, args, prefix=prefix) File "udata/core/storages/api.py", line 123, in combine_chunks out.write(chunks.read(partname)) File "flask_storage/storage.py", line 226, in read raise FileNotFound(filename) ```

Les erreurs sont probablement la conséquence d'erreurs réseaux, c'est pourquoi nous recommandons aux producteurs de regarder des solutions d'hébergement spécialisées si leurs donnée sont volumineuses.

maudetes commented 9 months ago

L'erreur est bien reproduite systématiquement lors de l'upload d'un fichier de taille volumineuse. L'ensemble des chunks réussissent bien à être envoyés*, mais la combinaison des chunks dans le fichier cible prend trop de temps. L'appel POST finit par timeout et semble considéré en échec. La lib fine-uploader qui gère l'upload côté front est configurée pour retry, un nouvel appel POST est donc effectué. Celui-ci échoue car les chunks traités précédemment ont déjà été supprimés.

*je n'ai pas reproduit l'erreur de FileExists

Conclusions

L'admin étant legacy, un correctif ne semble pas nécessaire, étant donné les solutions de contournement. Il faut cependant étudier le comportement sur le nouveau parcours de publication. Les solutions de contournement identifiées :

maudetes commented 8 months ago

Autre cas de reproduction d'un erreur FileExists (non reliée à une question de taille de fichiers):

Voir les logs ``` FileExists: fichiers-consolides-des-donnees-respectant-le-schema-part-des-vehicules-a-faibles-emissions-dans-le-renouvellement-dun-parc/20240216-064139/consolidation-etalab-schema-vehicules-faibles-emissions-renouvellement-parc-v-0.1.3-20240216.csv File "flask/app.py", line 1523, in full_dispatch_request rv = self.dispatch_request() File "flask/app.py", line 1509, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "udata/api/__init__.py", line 132, in wrapper return func(*args, **kwargs) File "flask_restx/api.py", line 404, in wrapper resp = resource(*args, **kwargs) File "flask/views.py", line 84, in view return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs) File "flask_restx/resource.py", line 46, in dispatch_request resp = meth(*args, **kwargs) File "udata/api/__init__.py", line 123, in wrapper return func(*args, **kwargs) File "flask_restx/marshalling.py", line 244, in wrapper resp = f(*args, **kwargs) File "udata/core/dataset/api.py", line 446, in post infos = self.handle_upload(dataset) File "udata/core/dataset/api.py", line 378, in handle_upload infos = handle_upload(storages.resources, prefix) File "udata/core/storages/api.py", line 147, in handle_upload filename=filename File "flask_storage/storage.py", line 296, in save raise FileExists(filename) ```

Elle peut être reproduite en tentant deux fois l'upload d'un fichier avec le même nom sur un même dataset dans la même seconde. En effet, le path de sauvegarde se construit sur <dataset-slug>/<date-seconde-incluse>/<filename.csv>, il n'est donc pas 100% unique.