Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Oops ho sovrascritto le tue modifiche

Oops ho sovrascritto le tue modifiche

Mai capitato di sovrascrivere dati involontariamente dall’interfaccia web o tramite il pannello di amministrazione di una applicazione Django? In questo talk vedremo qualche strategia per ovviare al problema.

Vedremo delle strategie generiche come salvare un log delle nostre modifiche, fare il locking delle righe del database oppure usare la concorrenza ottimistica. Quindi vedremo una strategia specifica di PostgreSQL implementando un sistema di concorrenza ottimistica per le modifiche fatte da utenti tramite il pannello di amministrazione di Django.

61ba6f6b1fb82707b9344259f74a81b3?s=128

Riccardo Magliocchetti

June 06, 2022
Tweet

More Decks by Riccardo Magliocchetti

Other Decks in Programming

Transcript

  1. Oops ho sovrascritto le tue modifiche Riccardo Magliocchetti

  2. whoami Cat herder @ Dev @ Maieutical Labs Djungle Studio

  3. Mai sovrascritto dati involontariamente dall'admin di Django?

  4. Capita solo da admin? # processo 1 # processo 2

    t = Talk.objects.get( name=”oops” ) t.done = True Talk.objects.update( name=”more oops” ) t.save()
  5. save() in SQL UPDATE "pycon_talk" SET "name" = 'oops', "done"

    = true WHERE "pycon_talk"."id" = 1
  6. In questo caso è facile # processo 1 # processo

    2 t = Talk.objects.get( name=”oops” ) t.done = True Talk.objects.update( name=”more oops” ) t.save(update_fields=["done"])
  7. save(update_fields=[...]) in SQL UPDATE "pycon_talk" SET "done" = true WHERE

    "pycon_talk"."id" = 1
  8. 3 possibili strategie

  9. 1. Salvare un log delle modifiche

  10. 1. Salvare un log delle modifiche 2. Locking delle righe

  11. 1. Salvare un log delle modifiche 2. Locking delle righe

    3. Concorrenza ottimistica
  12. Salvare un log delle modifiche

  13. Ad ogni aggiornamento o cancellazione di un modello serializzo la

    versione precedente con un nome di versione e dei metadati. Con lo storico delle modifiche è possibile provare a ripristinare i dati.
  14. Applicazioni per Django django-reversion django-reversion-rest-framework django-simple-history django-auditlog

  15. Salvare un log delle modifiche: recap abbiamo un log di

    audit incluso abbiamo una buona chance di ripristinare i dati
  16. Locking delle righe o concorrenza pessimistica

  17. select_for_update SELECT ... FOR UPDATE

  18. select_for_update blocca la transazione per default DatabaseError con nowait=True

  19. select_for_update talks = ( Talk.objects.select_for_update() .filter(day=Talk.Day.SUNDAY) ) with transaction.atomic(): for

    talk in talks: ...
  20. Locking delle righe: recap risolviamo corse critiche sull'aggiornamento non possiamo

    tenere il lock troppo a lungo per mantenere concorrenza
  21. Concorrenza ottimistica

  22. Concorrenza ottimistica Aggiorno solo quello che mi aspetto senza prendere

    lock
  23. Concorrenza ottimistica in SQL UPDATE pycon_talk SET version = version

    + 1, ... WHERE id = %s AND version = %s
  24. Implementazioni per Django django-optimistic-lock django-concurrency

  25. Concorrenza ottimistica: recap non prendiamo lock dobbiamo gestire il caso

    in cui non vengono aggiornate righe
  26. Concorrenza in PostgreSQL MVCC (Multi-Version Concurrency Control): alla modifica di

    una riga si crea una nuova riga con id di transazione maggiore e poi il VACUUM elimina quelle vecchie
  27. Concorrenza in PostgreSQL L'id di transazione si chiama xmin: SELECT

    xmin from pycon_talk LIMIT 1;
  28. xmin in Django from django.db import models from django.db.models import

    Expression class XMin(Expression): output_field = models.PositiveIntegerField() def as_postgresql(self, compiler, connection): return f'"{compiler.query.base_table}"."xmin"', () Talk.objects.all().annotate(row_version=XMin())
  29. Bonus: una piccola applicazione

  30. Problema: corsa critica tra admin e sistema ad eventi Condizioni

    basta impedire l'override di modifiche da admin basta che funzioni su PostgreSQL
  31. riduce la possibilità di sovrascrivere dati tramite l'admin implementa concorrenza

    ottimistica su PostgreSQL senza modificare i modelli semplice: due mixin da usare, 62 righe in totale django-optimistic-admin-pg
  32. from optimisticadmin.mixins import OptimisticAdminModelFormM class TalkForm(OptimisticAdminModelFormMixin, forms.ModelFor row_version = forms.IntegerField(

    required=False, widget=forms.HiddenInput() ) from optimisticadmin.mixins import OptimisticAdminMixin from .forms import TalkForm @admin.register(Talk) class TalkAdmin(OptimisticAdminMixin, admin.ModelAdmin): form = TalkForm
  33. Risultato Non è possibile salvare se row_version è stata aggiornata

    nel frattempo
  34. Conclusioni tenere un log delle modifiche per evitare di perdere

    dati usare dei lock per serializzare gli aggiornamenti implementare un sistema di concorrenza ottimistica
  35. Conclusioni strategia rischio tradeoff quando Log modifiche no robusta audit,

    recovery Locking righe no robusta assegnazione risorse, soldi :) Concorrenza ottimistica si perf poco rischio di collisione Una combinazione delle precedenti
  36. Grazie! Domande? github.com/xrmx speakerdeck.com/xrmx

  37. Riferimenti con implementazione semplice di concorrenza ottimistica su PostgreSQL ed

    API con Django Rest Framework di Haki Benita su concorrenza pessimistica vs ottimistica su concorrenza MVCC di PostgreSQL MVCC PostgreSQL Articolo articolo articolo Heroku Documentazione ufficiale