Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... ,...

42
Database Integrity in Django: Safely Handling Critical Data in Distributed Systems Dealing with concurrency, money, and log-structured data in the Django ORM Nick Sweeting (@theSquashSH) Slides: github.com/pirate/django-concurrency-talk

Transcript of Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... ,...

Page 1: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Database Integrity in Django: Safely Handling Critical Data

in Distributed Systems

Dealing with concurrency, money, and log-structured data in the Django ORM

Nick Sweeting (@theSquashSH)

Slides: github.com/pirate/django-concurrency-talk

Page 2: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

NickSwee)ngTwi-er:theSquashSH|Github:pirateCo-Founder/[email protected]

WebuiltOddSlingers.com,afast,cleanonlinepoker

experiencemadewithDjango+Channels&React/Redux.Welearnedalotaboutdatabaseintegrityalongtheway.

Background

Disclaimer:Iamnotadistributedsystemsexpert.

Ijustthinkthey'reneat.

Page 3: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Itallstartswithasinglesalamislice.

Itendswithmillionsofdollarsmissing.

Page 4: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Dealingwithmoney float,Decimal,andmath

Avoidingconcurrencylinearizingwritesinaqueue

Dealingwithconcurrency transac)ons,locking,compare-and-swaps

Schemadesignlog-structureddata,minimizinglocks

Thebiggerpicture codelayout,storagelayer,NewSQLdatabases

Page 5: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Dealing With Money

float,Decimal,&math

Page 6: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Losingtrackoffrac)onalcents(akasalamislicing)

I know you're tempted,don't even try it...salami slicers all

get caught eventually

Page 7: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

floatvsDecimal

>>>0.1+0.2==0.3False

Page 8: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

RoundinginPython3

>>>round(1.5)

>>>round(2.5)

2

2

wat.

Page 9: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Dealingwithmoney float,Decimal,andmath

Avoidingconcurrencylinearizingwritesinaqueue

Dealingwithconcurrency transac)ons,locking,compare-and-swaps

Schemadesignlog-structureddata,minimizinglocks

Thebiggerpicture codelayout,storagelayer,NewSQLdatabases

Page 10: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Avoid Concurrency

Eliminatethedragons.

?

Page 11: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Whatisadistributedsystem?EveryDjangoapp.

>EachDjangorequesthandlerisaseparatethread

>Whathappenswhentwothreadstrytodosomethingcri?calatthesame?me?e.g.updateauser'sbalance

Page 12: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Otherthreadswri)ngwillbreakthebank

$0 instead of $-100

Page 13: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

TheChallengeDealingwithconcurrentwriteconflicts

Page 14: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Asolu?on:removetheconcurrency

Strictlyorderallstatemuta)onsby)mestamp

Page 15: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Linearizeallthewritesintoasinglequeue

transactions = [ # timestamp condition action

(1523518620, "can_deposit(241)" , "deposit_usd(241, 50)"), (1523518634, "balance_gt(241, 50)", "buy_chips(241, 50)"), ]

Ifonlyonechangehappensata)me,

noconflic)ngwritescanoccur.

Page 16: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Executethewrites1by1inadedicatedprocess

while True: ts, condition, action = transaction_queue.pop() if eval(condition): eval(action)

(usingaRedisQueue,orDrama)q,Celery,etc.)

Don'tletanyotherprocessestouchthesametables.

Allchecks&writesarenowlinearized.

Page 17: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Ifyouvalueyoursanity,linearizecri)caltransac)onsintoasinglequeue

wheneverpossible.

Don'tevenwatchtherestofthetalk,juststopnow,really,youprobablydon'tneedconcurrency...

Eliminateconcurrencyatallcosts.

Page 18: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Dealingwithmoney float,Decimal,andmath

Avoidingconcurrencylinearizingwritesinaqueue

Dealingwithconcurrency transac)ons,locking,compare-and-swaps

Schemadesignlog-structureddata,minimizinglocks

Thebiggerpicture codelayout,storagelayer,NewSQLdatabases

Page 19: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Dealing With Concurrency

transac)ons,locking,compare-and-swaps

Iwarnedyouaboutthedragons...

!

Page 20: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

ToolstheORMprovides

>Atomictransac)onstransac?on.atomic()

>LockingModel.objects.select_for_update()

>Compare-and-swaps.filter(val=expected).update(val=new)

Page 21: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

AtomicTransac)ons

with transaction.atomic(): thing = SomeModel.objects.create(...) other_thing = SomeModel.objects.create(...) if error_condition(...): raise Exception('Rolls back entire transaction')

Excep)onsrollbacktheen)retransac)onblock.

NeitherobjectwillbesavedtotheDB.

Transac)onscanbenested.

Page 22: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

RowLocking

with transaction.atomic(): to_update = SomeModel.objects.select_for_update().filter(id=thing.id) ... to_update.update(val=new)

.select_for_update()allowsyoutolockrows

Lockingpreventsotherthreadsfromchangingtherowun)ltheendofthecurrenttransac)on,

whenthelockisreleased.

(pessimis)cconcurrency)

Page 23: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Atomiccompare-and-swaps

last_changed = obj.modified

...SomeModel.objects.filter(id=obj.id, modified=last_changed).update(val=new_val)

Onlyupdatesifthedbrowisunchangedbyotherthreads.>anymodifiedobjindbwilldifferfromourstalein-memoryobjts>filter()wontmatchanyrows,update()fails>overwri)ngnewerrowindbwithstaledataisprevented

Thisisveryhardtogetright,lockingisbe-erfor90%ofusecases!

(op)mis)cconcurrency)

Page 24: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

HybridSolu)on

last_changed = obj.modified

... read phaseSomeModel.objects.select_for_update().filter(id=obj.id, modified=last_changed)... write phase

Bestofbothworlds>lockingislimitedtowrite-phaseonly>noneedforcomplexmul)-modelcompare-and-swaps

MVCCisusedinternallybyPostgreSQL

Alterna)ve:SQLgap-lockingw/filterqueryonindexedcol.

(op)mis)cconcurrency+pessimis)corMul)versionConcurrencyControl)

Page 25: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Dealingwithmoney float,Decimal,andmath

Avoidingconcurrencylinearizingwritesinaqueue

Dealingwithconcurrency transac)ons,locking,compare-and-swaps

Schemadesignlog-structureddata,minimizinglocks

Thebiggerpicture codelayout,storagelayer,NewSQLdatabases

Page 26: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Schema Design

log-structureddata,minimizinglocks

Page 27: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Whatislog-structureddata?Append-onlytablesvsmutabletables

>MutableexampleUser.balance=100

>Log-structuredexample(immutable,append-only)User.balance=()=>sum(BalanceTransfer.objects.filter(user=user).values_list('amt',flat=True))

Page 28: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Log-structuredstorageisafounda?onalbuildingblockofsafe,distributedsystems.

-Providesstrictorderingofwrites-Immutablelogofeverychange

-Abilitytoreverttoanypointin)me

See:redux,CouchDB,Redis

Page 29: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Butlog-structuredtablesmakelockinghard...

we'dhavetolocktheen)reBalanceTransfertabletopreventconcurrentprocessesfromaddingnewtransfersthatchangethetotal.

Howelsecanwepreventconcurrentwritesfromchanginga

user'sbalance?

Becauseanynewrowaddedcanchangethetotal,

Page 30: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Storeatotalseparatelyfromthelog,requiretheybeupdatedtogether

class UserBalance(models.model): user = models.OneToOneField(User) total = models.DecimalField(max_digits=20, decimal_places=2)

Asingle-rowlockmustnowbeobtainedonthetotalbeforeaddingnewBalanceTransferrowsforthatuser.

Page 31: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Fullexampleusinglocking

def send_money(src, dst, amt): with transaction.atomic(): # Lock balance rows, preventing other threads from making changes src_bal = UserBalance.objects.select_for_update().filter(id=src) dst_bal = UserBalance.objects.select_for_update().filter(id=dst) if src_bal[0].total < amt: raise Exception('Not enough balance to complete transaction')

# Update the totals and add a BalanceTransfer log row together BalanceTransfer.objects.create(src=src, dst=dst, amt=amt) src_bal.update(total=F('total') - amt) dst_bal.update(total=F('total') + amt)

Sidebenefit:noneedtoscanen)reBalanceTransfertableanymoretogetauser'sbalance

Page 32: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Log-structureddataisgreat,but...

itrequirescarefulthoughtto:

-minimizedetrimentalwhole-tablelocking-accessaggregatevalueswithoutscanning

Page 33: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Dealingwithmoney float,Decimal,andmath

Avoidingconcurrencylinearizingwritesinaqueue

Dealingwithconcurrency transac)ons,locking,compare-and-swaps

Schemadesignlog-structureddata,minimizinglocks

Thebiggerpicture codelayout,storagelayer,NewSQLdatabases

Page 34: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

The bigger picture

codelayout,storagelayer,NewSQLdatabases

Whathappenswhenthebu-erfliesflipyourbits?

Page 35: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

CodeLayout

>Onlyperformwritesviahelperfuncs,neverupdatemodelsdirectly>Putalltransac)onsinonefileforeasieraudi)ng&tes)ng

banking/transactions.py:def transfer_money(src, dst, amt): with transaction.atomic(): ...

def merge_accounts(user_a, user_b): with transaction.atomic(): ...

def archive_account(user): with transaction.atomic(): ...

from banking.transactions import transfer_money

...

Page 36: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

OFFSITE BACKUPS. OFFSITE BACKUPS. SET UP YOUR OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. TEST YOUR OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS. OFFSITE BACKUPS.

HardwareLayerConcerns

>Bitflipsarecommon,useECCRAM(andZFS!)

>Thedatabasecan'tguaranteedataintegrityonitsown

>Streamingreplica)onorsnapshotstodooffsite-backups

>Synchronizingclocksbetweensystemsisveryhard

Page 37: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

DatabaseIsola)onLevelsInsomemodes,par)altransac)onstatecanleakintootherthreads.

DATABASES = { 'OPTIONS': { 'isolation_level':psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE,

Highlycomplextopic,muchmoreinfocanbefoundelsewhere...

Page 38: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

It'spossibletouseaseparatedatabasewithahigherisola)onlevelforcri)caldata

with transaction.atomic(using='default'): with transaction.atomic(using='banking'): MyModel_one(...).save(using='default') MyModel_two(...).save(using='banking')

Djangosupportstransac)onsacrossmul)pledatabases.

Page 39: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

WhattheFutureLooksLike

Serializable,distributedSQLwithoutsharding.

SQLontopof>key:valstoreontopof>

rao-basedlog-structuredstorage

CockroachDB&TiDBworkwithPython

Page 40: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Dealingwithmoney float,Decimal,andmath

Avoidingconcurrencylinearizingwritesinaqueue

Dealingwithconcurrency transac)ons,locking,compare-and-swaps

Schemadesignlog-structureddata,minimizinglocks

Thebiggerpicture codelayout,storagelayer,NewSQLdatabases

The End

Page 41: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Keytakeaways:don'tstopworrying,butloveatomic()

Page 42: Database Integrity in Django: Safely Handling Critical ... · What the Future Looks Like ... , NewSQL databases The End. Key takeaways: don't stop worrying, but love atomic() Special

Specialthanksto:DjangoCoreTeam&Contributors,

PyGothamOrganizers,AndrewGodwin,Aphyr,TylerNeely

FinalDisclaimer:I'mnotqualifiedtotellyouhowtodesignyourdistributedsystem.Getaprofessionalforthat.

Icanonlyshowyouchallengesandsolu)onsI'vediscoveredinmypersonal

adventureswithDjango.Pleaseletmeknowifyouhavecorrec)ons!

Monadicalishiringandtakinginvestmentrightnow!contactme:talks@swee)ng.me

Q&A

Slides & Info: github.com/pirate/django-concurrency-talk