SlideShare a Scribd company logo
1 of 92
Download to read offline
Epic South Disasters
Or: Why You Need to Actually Pay Attention to Your DBMS
http://todaysmeet.com/epic-south-disasters
Christopher Adams
Engineering Lead, Scrollmotion
@adamsc64
http://todaysmeet.com/epic-south-disasters
https://github.com/adamsc64/epic-south-disasters
What is South?
South fills a need in the Django ecosystem.
The Django ORM
A tool to write SQL for you
“You can still write SQL if needed.”
You don’t have to write SQL any more?
In other words, it's your responsibility.
Problem: Django does not change a table once
it’s created with syncdb.
So "if needed" is really "when needed."
Most web apps that change often are going to need schema
migrations.
Django 1.5 documentation
The recommended way to migrate schemas:
“If you have made changes to a model and wish
to alter the database tables to match, use the
sql command to display the new SQL structure
and compare that to your existing table schema
to work out the changes.”
https://docs.djangoproject.com/en/1.5/ref/django-admin/#syncdb
In practice...
BEGIN;
CREATE TABLE "foo_foo_bars" (
"id" serial NOT NULL PRIMARY KEY,
- "foo_id" integer NOT NULL,
"bar_id" integer NOT NULL REFERENCES "baz_bar" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "foo_id" integer NOT NULL,
UNIQUE ("foo_id", "bar_id")
)
;
CREATE TABLE "foo_foo_zazzs" (
"id" serial NOT NULL PRIMARY KEY,
"foo_id" integer NOT NULL,
+ "user_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED,
+)
-;
- "bang_id" integer NOT NULL REFERENCES "baz_bang" ("id") DEFERRABLE INITIALLY DEFERRED,
- UNIQUE ("foo_id", "bang_id")
-)
-;
-CREATE TABLE "foo_foo" (
- "user_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED,
+ "properties" text NOT NULL,
- "id" serial NOT NULL PRIMARY KEY,
"name" varchar(100) NOT NULL,
"description" text NOT NULL,
+ "user_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED,
"created_by_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED,
"updated_at" timestamp with time zone NOT NULL,
"bing" text NOT NULL,
+ "properties" text NOT NULL,
- "lft" integer CHECK ("lft" >= 0) NOT NULL,
- "rght" integer CHECK ("rght" >= 0) NOT NULL,
- "tree_id" integer CHECK ("tree_id" >= 0) NOT NULL,
- "level" integer CHECK ("level" >= 0) NOT NULL
)
;
ALTER TABLE "foo_foo_bars" ADD CONSTRAINT "foo_id_refs_id_1401a163" FOREIGN KEY ("foo_id") REFERENCES "foo_foo" ("id") DEFERRABLE INITIALLY
DEFERRED;
ALTER TABLE "foo_foo_zazzs" ADD CONSTRAINT "foo_id_refs_id_ca2b0e5" FOREIGN KEY ("foo_id") REFERENCES "foo_foo" ("id") DEFERRABLE INITIALLY
DEFERRED;
+ALTER TABLE "foo_foo" ADD CONSTRAINT "parent_id_refs_id_1eb29019" FOREIGN KEY ("parent_id") REFERENCES "foo_foo" ("id") DEFERRABLE
INITIALLY DEFERRED;
SQL diffs are no fun
IT’S EASY,
OH SO EASY
South Solves Some Problems for us.
Problems South solves:
1. Automates writing schema migrations for us.
2. In Python (no SQL).
3. Broadly “Don’t Repeat Yourself” (DRY).
4. Migrations are applied in order.
5. Version control migration code.
6. Shared migrations, dev and production.
7. Fast iteration.
Levels of abstraction
● great because they take us away from the
messy details
● risky because they take us away from the
messy details
● can obscure what’s going on
How to Use South (very quick 101)
Our Model
+++ b/minifier/models.py
class MinifiedURL(models.Model):
url = models.CharField(
max_length=100)
datetime = models.DateTimeField(
auto_now_add=True)
Initial Migration
$ ./manage.py schemamigration minifier --
initial
Creating migrations directory at '/...
/minifier/migrations'...
Creating __init__.py in '/...
/minifier/migrations'...
+ Added model minifier.MinifiedURL
Created 0001_initial.py. You can now apply
this migration with: ./manage.py migrate
minifier
Initial Migration
$ ls -l minifier/migrations/
total 8
-rw-r--r-- 1 chris staff 1188 Aug 30 11:40 0001_initial.py
-rw-r--r-- 1 chris staff 0 Aug 30 11:40 __init__.py
Initial Migration
$ ./manage.py syncdb
Syncing...
Creating tables ...
Creating table south_migrationhistory
Synced:
> django.contrib.auth
> south
Not synced (use migrations):
- minifier
(use ./manage.py migrate to migrate these)
Initial Migration
$ ./manage.py migrate
Running migrations for minifier:
- Migrating forwards to 0001_initial.
> minifier:0001_initial
- Loading initial data for minifier.
Installed 0 object(s) from 0 fixture(s)
Ok, let’s add a field.
Adding a Field
class MinifiedURL(models.Model):
+ submitter = models.ForeignKey(
+ 'auth.user', null=True)
url = models.CharField(
max_length=100)
datetime = models.DateTimeField(
auto_now_add=True)
$ ./manage.py schemamigration minifier --auto
+ Added field submitter on minifier.MinifiedURL
Created 0002_auto__add_field_minifiedurl_submitt
er.py.
You can now apply this migration with: ./manage.
py migrate minifier
Adding a Field
class Migration(SchemaMigration):
def forwards(self, orm):
# Adding field 'MinifiedURL.submitter'
db.add_column(u'minifier_minifiedurl',
'submitter',
...
)
def backwards(self, orm):
# Deleting field 'MinifiedURL.submitter'
db.delete_column(u'minifier_minifiedurl',
'submitter_id'
)
Adding a Field
Adding a Field
$ ./manage.py migrate minifier 0002
- Soft matched migration 0002 to
0002_auto__add_field_minifiedurl_submitter.
Running migrations for minifier:
- Migrating forwards to
0002_auto__add_field_minifiedurl_submitter.
> minifier:
0002_auto__add_field_minifiedurl_submitter
- Loading initial data for minifier.
Installed 0 object(s) from 0 fixture(s)
$ ./manage.py dbshell
psql-# d+ minifier_minifiedurl;
Table "public.minifier_minifiedurl"
Column | Type
--------------+-------------------------
id | integer
url | character varying(100)
submitter_id | integer
datetime | timestamp with time zone
It worked!
More details: follow the South Tutorial
http://south.readthedocs.org/en/latest/tutorial/
● Many people approach a new tool with a
broad set of expectations as to what they
think it will do for them.
● This may have little correlation with
what the project actually has
implemented.
Expectations
Disaster situations
Don't panic.
Our Model
class MinifiedURL(models.Model):
submitter = models.ForeignKey(
'auth.user', null=True)
url = models.CharField(
max_length=100)
datetime = models.DateTimeField(
auto_now_add=True)
Our Model
class MinifiedURL(models.Model):
submitter = models.ForeignKey(
'auth.user', null=True)
url = models.CharField(
max_length=100)
+ created = models.DateTimeField(
auto_now_add=True)
$ vim minifier/models.py
$ ./manage.py schemamigration minifier --auto
$ git commit -am "Rename field."
$ git push
$ ./deploy-to-production.sh
Done!
Fast iteration!
Fast iteration!
+++ b/minifier/migrations/0003_auto_del_field.
py
# Deleting field 'MinifiedURL.datetime'
db.delete_column(u'minifier_minifiedurl',
'datetime')
# Adding field 'MinifiedURL.created'
db.add_column(u'minifier_minifiedurl',
'created',
...
)
Lesson #1
Always read migrations that are generated with --auto.
So how do we do this?
class MinifiedURL(models.Model):
submitter = models.ForeignKey(
'auth.user', null=True)
url = models.CharField(
max_length=100)
+ created = models.DateTimeField(
auto_now_add=True)
Data migration - basic example
1. schemamigration - Create the new field.
2. datamigration - Copy the data to the new
field from the old field.
3. schemamigration - Delete the old field.
Data migration - basic example
class MinifiedURL(models.Model):
submitter = models.ForeignKey(
'auth.user', null=True)
url = models.CharField(
max_length=100)
datetime = models.DateTimeField(
auto_now_add=True)
+ created = models.DateTimeField(
auto_now_add=True)
Data migration - basic example
$ ./manage.py schemamigration minifier --auto
Created 0003_auto__add_field_minifiedurl_crea
ted.py.
$ ./manage.py datamigration minifier 
datetime_to_created
Created 0004_datetime_to_created.py.
$ vim minifier/migrations/0004_datetime_to_
created.py
# Note: Don't use "from
# appname.models import ModelName".
# Use orm.ModelName to refer to
# models in this application...
Data migration - basic example
Data migration - basic example
class Migration(DataMigration):
def forwards(self, orm):
+ for minified_url in orm.MinifiedURL.objects.all():
+ minified_url.created = minified_url.datetime
+ minified_url.save()
def backwards(self, orm):
+ for minified_url in orm.MinifiedURL.objects.all():
+ minified_url.datetime = minified_url.created
+ minified_url.save()
Data migration - basic example
class MinifiedURL(models.Model):
submitter = models.ForeignKey(
'auth.user', null=True)
url = models.CharField(
max_length=100)
- datetime = models.DateTimeField(
auto_now_add=True)
created = models.DateTimeField(
auto_now_add=True)
$ ./manage.py migrate --list
minifier
(*) 0001_initial
(*) 0002_auto__add_field_minifiedurl_submitt
( ) 0003_auto__add_field_minifiedurl_created
( ) 0004_datetime_to_created
( ) 0005_auto__del_field_minifiedurl_datetim
Data migration - basic example
$ ./manage.py migrate
- Migrating forwards to 0005_auto__del_
field_minifiedurl_datetime.
> minifier:0003_auto__add_field_minifiedurl_
created
> minifier:0004_datetime_to_created
> minifier:0005_auto__del_field_
minifiedurl_datetime
Data migration - basic example
$ ./manage.py migrate
- Migrating forwards to 0005_auto__del_
field_minifiedurl_datetime.
> minifier:0003_auto__add_field_minifiedurl_
created
> minifier:0004_datetime_to_created
> minifier:0005_auto__del_field_
minifiedurl_datetime
Data migration - basic example
South’s frozen ORM is pretty nifty.
It will expose the model at an historical point-in-time.
Danger.
Many parts of the Django ORM still function.
Our Model
class MinifiedURL(models.Model):
submitter = models.ForeignKey(
'auth.user')
created = models.DateTimeField(
auto_now_add=True)
updated = models.DateTimeField(
auto_now=True)
url = models.CharField(
max_length=100)
Our Model
class MinifiedURL(models.Model):
submitter = models.ForeignKey(
'auth.user')
created = models.DateTimeField(
auto_now_add=True)
updated = models.DateTimeField(
auto_now=True)
url = models.CharField(
max_length=100)
+ domain = models.CharField(
+ max_length=30)
class Migration(DataMigration):
def forwards(self, orm):
+ model = orm.MinifiedURL
+
+ for minified_url in model.objects.all():
+ minified_url.domain = (
+ minified_url.url.split('/')[2]
+ )
+ minified_url.save()
Data migration
$ git commit -am "Migrate 'domain' from 'url'
field."
$ git push
$ ./deploy-to-production.sh
Done!
Fast iteration!
Before and After
pk updated (before)
---|-----------------|
566|2013-03-01 09:01 |
567|2012-01-22 17:34 |
568|2012-12-31 19:11 |
569|2013-04-10 10:02 |
...
Before and After
pk updated (before) updated (after)
---|-----------------|-----------------|
566|2013-03-01 09:01 |2013-09-04 14:01 |
567|2012-01-22 17:34 |2013-09-04 14:01 |
568|2012-12-31 19:11 |2013-09-04 14:01 |
569|2013-04-10 10:02 |2013-09-04 14:01 |
... ...
Oh no! Why did we lose datetime
information?
Our Model
class MinifiedURL(models.Model):
submitter = models.ForeignKey(
'auth.user')
created = models.DateTimeField(
auto_now_add=True)
updated = models.DateTimeField(
auto_now=True)
url = models.CharField(
max_length=100)
The South ORM wraps over the Django
ORM, which applies rules such as
auto_now=True and
auto_now_add=True.
Especially nasty because no exception
raised or warning given even with this
kind of data loss.
Lesson #2
Always confirm your migrations do what you expect --
and nothing more.
Workaround
+ opts = model._meta
+ field = opts.get_field_by_name('updated')[0]
+ old_auto_now = field.auto_now
+ field.auto_now = False
for minified_url in model.objects.all():
minified_url.domain = (
minified_url.url.split('/')[2]
)
minified_url.save()
+ field.auto_now = old_auto_now
Peter Bengtsson, http://www.peterbe.com/plog/migration-south-auto_now_add
Before and After
pk updated (before) updated (after)
---|-----------------|-----------------|
566|2013-03-01 09:01 |2013-03-01 09:01 |
567|2012-01-22 17:34 |2012-01-22 17:34 |
568|2012-12-31 19:11 |2012-12-31 19:11 |
569|2013-04-10 10:02 |2013-04-10 10:02 |
... ...
Oh no! Why did all our users suddenly
get emailed?
@receiver(post_save)
def email_user_on_save(sender, **kwargs):
"""
Not sure why I'm doing this here,
but it seems like a good place!
REFACTOR LATER TBD FYI!!
"""
if sender.__name__ == "MinifiedURL":
email(kwargs['instance'].submitter,
"Congratulations on changing "
"your url!",
)
Whoops, forgot about this.
The South ORM wraps over the Django
ORM, so it sends post_save signals.
However, the metaclass magic usually
takes care of avoiding problems.
@receiver(post_save)
def email_user_on_save(sender, **kwargs):
"""
Not sure why I'm doing this here,
but it seems like a good place!
REFACTOR LATER TBD FYI!!
"""
if sender.__name__ == "MinifiedURL":
email(kwargs['instance'].submitter,
"Congratulations on changing "
"your url!",
)
So this...
@receiver(post_save)
def email_user_on_save(sender, **kwargs):
"""
Not sure why I'm doing this here,
but it seems like a good place!
REFACTOR LATER TBD FYI!!
"""
- if sender.__name__ == "MinifiedURL":
+ if sender == MinifiedURL:
email(kwargs['instance'].submitter,
"Congratulations on changing "
"your url!",
)
...should be this.
ipdb> print repr(sender)
<class 'minifier.models.MinifiedURL'>
ipdb> print repr(MinifiedURL)
<class 'minifier.models.MinifiedURL'>
ipdb> print MinifiedURL == sender
False
ipdb> print id(MinifiedURL)
140455961329984
ipdb> print id(sender)
140455961864000
Metaclass magic
Lesson #3
Always check data migrations for unintended consequences.
class MinifiedURL(models.Model):
created = models.DateTimeField(
auto_now_add=True)
updated = models.DateTimeField(
auto_now=True)
url = models.CharField(
max_length=100, db_index=True)
Our Model
class MinifiedURL(models.Model):
created = models.DateTimeField(
auto_now_add=True)
updated = models.DateTimeField(
auto_now=True)
- url = models.CharField(
- max_length=100, db_index=True)
+ url = models.CharField(
+ max_length=1000, db_index=True)
Our Model
$ ./manage.py schemamigration minifier 
--auto
~ Changed field url on minifier.MinifiedURL
Created 0010_auto__chg_field_minifiedurl_ url.
py. You can now apply this migration with: .
/manage.py migrate minifier
Create the schema migration.
Seems fine...
$ ./manage.py migrate
Running migrations for minifier:
- Migrating forwards to
0010_auto__chg_field_minifiedurl_url.
> minifier:
0010_auto__chg_field_minifiedurl_url
- Loading initial data for minifier.
Installed 0 object(s) from 0 fixture(s)
“Works fine on development?”
“Ship it!”
Production vs. Development
Beware of differences in configuration.
From a Django blog
7. Local vs. Production Environments
Django comes with sqlite, a simple flat-file database that
doesn't need any configuration. This makes prototyping
fast and easy right out of the box.
However, once you've moved your project into a
production environment, odds are you'll have to use a
more robust database like Postgresql or MySQL. This
means that you're going to have two separate
environments: production and development.
http://net.tutsplus.com/tutorials/other/10-django-troublespots-for-beginners/
$ git commit -am "Add some breathing
space to url fields."
$ git push
$ ./deploy-to-production.sh
Done!
Fast iteration!
Migration Failed
Running migrations for minifier:
- Migrating forwards to
0010_auto__chg_field_minifiedurl.
> minifier:0010_auto__chg_field_minifiedurl
! Error found during real run of migration!
Aborting.
Error in migration: minifier:
0010_auto__chg_field_minifiedurl
Warning: Specified key was too long; max key
length is 250 bytes
class MinifiedURL(models.Model):
created = models.DateTimeField(
auto_now_add=True)
updated = models.DateTimeField(
auto_now=True)
url = models.CharField(
max_length=1000, db_index=True)
Our Model
Always pay attention to the limitations of your DBMS.
Lesson #4
Schema-altering commands (DDL commands) cause a phantom
auto-commit.
Major limitation of MySQL
With InnoDB, when a client executes a
DDL change, the server executes an
implicit commit even if the normal auto-
commit behavior is turned off.
DDL transaction on Postgres
psql=# DROP TABLE IF EXISTS foo;
NOTICE: table "foo" does not exist
psql=# BEGIN;
psql=# CREATE TABLE foo (bar int);
psql=# INSERT INTO foo VALUES (1);
psql=# ROLLBACK; # rolls back two cmds
psql=# SELECT * FROM foo;
ERROR: relation "foo" does not exist
No DDL transaction on MySQL
mysql> drop table if exists foo;
mysql> begin;
mysql> create table foo (bar int)
type=InnoDB;
mysql> insert into foo values (1);
mysql> rollback; # Table 'foo' exists!
mysql> select * from foo;
0 rows in set (0.00 sec)
South uses DDL transactions if they are
available.
Pay attention to your DBMS
FATAL ERROR - The following SQL query failed:
ALTER TABLE `minifier_minifiedurl` ADD CONSTRAINT
`minifier_minifiedurl_url_263b28b6c6b349a8_uniq` UNIQUE (`name`)
The error was: (1062, "Duplicate entry 'http://cnn.com' for key
'minifier_minifiedurl_url_263b28b6c6b349a8_uniq'")
! Error found during real run of migration! Aborting.
! Since you have a database that does not support running
! schema-altering statements in transactions, we have had
! to leave it in an interim state between migrations.
! You *might* be able to recover with:
= ALTER TABLE `minifier_minifiedurl` DROP COLUMN `url` CASCADE; []
- no dry run output for alter_column() due to dynamic DDL, sorry
! The South developers regret this has happened, and would
! like to gently persuade you to consider a slightly
! easier-to-deal-with DBMS (one that supports DDL transactions)
! NOTE: The error which caused the migration to fail is further up.
1. Always read migrations that are generated
with --auto.
2. Always confirm your migrations do what
you expect.
3. Always check data migrations for
unintended consequences.
4. Always pay attention to the limitations of
your DBMS.
Lessons Recap
Encouragement
● Tools are not the problem. Tools are
why we are in this business.
● Knowledge is power. Know what
South is doing.
● Know what Django is doing for that
matter.
● David Cho
● Hadi Arbabi
● Mike Harris
Special Thanks
http://www.scrollmotion.com/careers

More Related Content

What's hot

VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法Kenji Tanaka
 
Less ismorewithcoffeescript webdirectionsfeb2012
Less ismorewithcoffeescript webdirectionsfeb2012Less ismorewithcoffeescript webdirectionsfeb2012
Less ismorewithcoffeescript webdirectionsfeb2012Jo Cranford
 
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...Databricks
 
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...Matthew Tovbin
 
Before there was Hoop Dreams, there was McDonald's: Strange and Beautiful
Before there was Hoop Dreams, there was McDonald's: Strange and BeautifulBefore there was Hoop Dreams, there was McDonald's: Strange and Beautiful
Before there was Hoop Dreams, there was McDonald's: Strange and Beautifulchicagonewsonlineradio
 
jQuery for Beginners
jQuery for Beginners jQuery for Beginners
jQuery for Beginners NAILBITER
 
jQuery & 10,000 Global Functions: Working with Legacy JavaScript
jQuery & 10,000 Global Functions: Working with Legacy JavaScriptjQuery & 10,000 Global Functions: Working with Legacy JavaScript
jQuery & 10,000 Global Functions: Working with Legacy JavaScriptGuy Royse
 
SVCC 2013 D3.js Presentation (10/05/2013)
SVCC 2013 D3.js Presentation (10/05/2013)SVCC 2013 D3.js Presentation (10/05/2013)
SVCC 2013 D3.js Presentation (10/05/2013)Oswald Campesato
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Robert DeLuca
 
前端MVC 豆瓣说
前端MVC 豆瓣说前端MVC 豆瓣说
前端MVC 豆瓣说Ting Lv
 
The rise and fall of a techno DJ, plus more new reviews and notable screenings
The rise and fall of a techno DJ, plus more new reviews and notable screeningsThe rise and fall of a techno DJ, plus more new reviews and notable screenings
The rise and fall of a techno DJ, plus more new reviews and notable screeningschicagonewsyesterday
 
fme Alfresco Day 06-2013 - alfresco.js and share
fme Alfresco Day 06-2013 - alfresco.js and sharefme Alfresco Day 06-2013 - alfresco.js and share
fme Alfresco Day 06-2013 - alfresco.js and shareAlfresco by fme AG
 
JavaScript Unit Testing with Jasmine
JavaScript Unit Testing with JasmineJavaScript Unit Testing with Jasmine
JavaScript Unit Testing with JasmineRaimonds Simanovskis
 
Why (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is AwesomeWhy (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is AwesomeJo Cranford
 
Mad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screeningsMad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screeningschicagonewsonlineradio
 
Standford 2015 week6
Standford 2015 week6Standford 2015 week6
Standford 2015 week6彼得潘 Pan
 
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridasFrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridasLoiane Groner
 

What's hot (20)

VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
 
mobl
moblmobl
mobl
 
WebXR if X = how?
WebXR if X = how?WebXR if X = how?
WebXR if X = how?
 
Less ismorewithcoffeescript webdirectionsfeb2012
Less ismorewithcoffeescript webdirectionsfeb2012Less ismorewithcoffeescript webdirectionsfeb2012
Less ismorewithcoffeescript webdirectionsfeb2012
 
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
The Rule of 10,000 Spark Jobs: Learning From Exceptions and Serializing Your ...
 
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
The Rule of 10,000 Spark Jobs - Learning from Exceptions and Serializing Your...
 
Before there was Hoop Dreams, there was McDonald's: Strange and Beautiful
Before there was Hoop Dreams, there was McDonald's: Strange and BeautifulBefore there was Hoop Dreams, there was McDonald's: Strange and Beautiful
Before there was Hoop Dreams, there was McDonald's: Strange and Beautiful
 
jQuery for Beginners
jQuery for Beginners jQuery for Beginners
jQuery for Beginners
 
jQuery & 10,000 Global Functions: Working with Legacy JavaScript
jQuery & 10,000 Global Functions: Working with Legacy JavaScriptjQuery & 10,000 Global Functions: Working with Legacy JavaScript
jQuery & 10,000 Global Functions: Working with Legacy JavaScript
 
SVCC 2013 D3.js Presentation (10/05/2013)
SVCC 2013 D3.js Presentation (10/05/2013)SVCC 2013 D3.js Presentation (10/05/2013)
SVCC 2013 D3.js Presentation (10/05/2013)
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
 
前端MVC 豆瓣说
前端MVC 豆瓣说前端MVC 豆瓣说
前端MVC 豆瓣说
 
The rise and fall of a techno DJ, plus more new reviews and notable screenings
The rise and fall of a techno DJ, plus more new reviews and notable screeningsThe rise and fall of a techno DJ, plus more new reviews and notable screenings
The rise and fall of a techno DJ, plus more new reviews and notable screenings
 
fme Alfresco Day 06-2013 - alfresco.js and share
fme Alfresco Day 06-2013 - alfresco.js and sharefme Alfresco Day 06-2013 - alfresco.js and share
fme Alfresco Day 06-2013 - alfresco.js and share
 
JavaScript Unit Testing with Jasmine
JavaScript Unit Testing with JasmineJavaScript Unit Testing with Jasmine
JavaScript Unit Testing with Jasmine
 
Why (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is AwesomeWhy (I think) CoffeeScript Is Awesome
Why (I think) CoffeeScript Is Awesome
 
Mad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screeningsMad Max is back, plus the rest of our new reviews and notable screenings
Mad Max is back, plus the rest of our new reviews and notable screenings
 
Backbone intro
Backbone introBackbone intro
Backbone intro
 
Standford 2015 week6
Standford 2015 week6Standford 2015 week6
Standford 2015 week6
 
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridasFrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
FrontInBahia 2014: 10 dicas de desempenho para apps mobile híbridas
 

Viewers also liked

Task 4 Final Images Landscape Review Work Sheet
Task 4 Final Images Landscape Review Work SheetTask 4 Final Images Landscape Review Work Sheet
Task 4 Final Images Landscape Review Work Sheetryansharman
 
Task 4 Final Images Portrait Review Work Sheet
Task 4 Final Images Portrait Review Work SheetTask 4 Final Images Portrait Review Work Sheet
Task 4 Final Images Portrait Review Work Sheetryansharman
 
Thebestwriting services.com
Thebestwriting services.comThebestwriting services.com
Thebestwriting services.combestwriting
 
235286655 actividades-para-reforzar-en-vacaciones
235286655 actividades-para-reforzar-en-vacaciones235286655 actividades-para-reforzar-en-vacaciones
235286655 actividades-para-reforzar-en-vacacionesCynthia Pardo
 
A Related Matter: Optimizing your webapp by using django-debug-toolbar, selec...
A Related Matter: Optimizing your webapp by using django-debug-toolbar, selec...A Related Matter: Optimizing your webapp by using django-debug-toolbar, selec...
A Related Matter: Optimizing your webapp by using django-debug-toolbar, selec...Christopher Adams
 
How to be popular....
How to be popular....How to be popular....
How to be popular....Ankit Goel
 
Idea Pitch Powerpoint - FMP
Idea Pitch Powerpoint - FMP Idea Pitch Powerpoint - FMP
Idea Pitch Powerpoint - FMP ryansharman
 
Servicios medicos 2017
Servicios medicos 2017Servicios medicos 2017
Servicios medicos 2017Felipe Cofré
 
Corporate Governance and Social Responsibility
Corporate Governance and Social ResponsibilityCorporate Governance and Social Responsibility
Corporate Governance and Social ResponsibilityHoly Angel University
 
FINDING THE INITIAL INVESTMENT LESSON
FINDING THE INITIAL INVESTMENT LESSONFINDING THE INITIAL INVESTMENT LESSON
FINDING THE INITIAL INVESTMENT LESSONHoly Angel University
 
Chapter 4 Marketing Research and Sales Forecasting
Chapter 4 Marketing Research and Sales ForecastingChapter 4 Marketing Research and Sales Forecasting
Chapter 4 Marketing Research and Sales ForecastingHoly Angel University
 
Geogrid-As a soil reinforcement
Geogrid-As a soil reinforcementGeogrid-As a soil reinforcement
Geogrid-As a soil reinforcementssp1505
 

Viewers also liked (18)

Task 4 Final Images Landscape Review Work Sheet
Task 4 Final Images Landscape Review Work SheetTask 4 Final Images Landscape Review Work Sheet
Task 4 Final Images Landscape Review Work Sheet
 
Task 4 Final Images Portrait Review Work Sheet
Task 4 Final Images Portrait Review Work SheetTask 4 Final Images Portrait Review Work Sheet
Task 4 Final Images Portrait Review Work Sheet
 
Thebestwriting services.com
Thebestwriting services.comThebestwriting services.com
Thebestwriting services.com
 
qwerty.com
qwerty.comqwerty.com
qwerty.com
 
235286655 actividades-para-reforzar-en-vacaciones
235286655 actividades-para-reforzar-en-vacaciones235286655 actividades-para-reforzar-en-vacaciones
235286655 actividades-para-reforzar-en-vacaciones
 
A Related Matter: Optimizing your webapp by using django-debug-toolbar, selec...
A Related Matter: Optimizing your webapp by using django-debug-toolbar, selec...A Related Matter: Optimizing your webapp by using django-debug-toolbar, selec...
A Related Matter: Optimizing your webapp by using django-debug-toolbar, selec...
 
Expo tipos anatomia
Expo tipos anatomiaExpo tipos anatomia
Expo tipos anatomia
 
Primeras lecturas de 5 años.
Primeras lecturas de 5 años.Primeras lecturas de 5 años.
Primeras lecturas de 5 años.
 
How to be popular....
How to be popular....How to be popular....
How to be popular....
 
Idea Pitch Powerpoint - FMP
Idea Pitch Powerpoint - FMP Idea Pitch Powerpoint - FMP
Idea Pitch Powerpoint - FMP
 
Lesson 3- Corporate Governance
Lesson 3- Corporate GovernanceLesson 3- Corporate Governance
Lesson 3- Corporate Governance
 
Servicios medicos 2017
Servicios medicos 2017Servicios medicos 2017
Servicios medicos 2017
 
LESSON 3- TOTAL QUALITY MANAGEMENT
LESSON 3- TOTAL QUALITY MANAGEMENTLESSON 3- TOTAL QUALITY MANAGEMENT
LESSON 3- TOTAL QUALITY MANAGEMENT
 
Corporate Governance and Social Responsibility
Corporate Governance and Social ResponsibilityCorporate Governance and Social Responsibility
Corporate Governance and Social Responsibility
 
FINDING THE INITIAL INVESTMENT LESSON
FINDING THE INITIAL INVESTMENT LESSONFINDING THE INITIAL INVESTMENT LESSON
FINDING THE INITIAL INVESTMENT LESSON
 
Chapter 4 Marketing Research and Sales Forecasting
Chapter 4 Marketing Research and Sales ForecastingChapter 4 Marketing Research and Sales Forecasting
Chapter 4 Marketing Research and Sales Forecasting
 
Chapter 3- Consumer Behavior
Chapter 3- Consumer BehaviorChapter 3- Consumer Behavior
Chapter 3- Consumer Behavior
 
Geogrid-As a soil reinforcement
Geogrid-As a soil reinforcementGeogrid-As a soil reinforcement
Geogrid-As a soil reinforcement
 

Similar to Epic South Disasters

Optimization in django orm
Optimization in django ormOptimization in django orm
Optimization in django ormDenys Levchenko
 
PerlApp2Postgresql (2)
PerlApp2Postgresql (2)PerlApp2Postgresql (2)
PerlApp2Postgresql (2)Jerome Eteve
 
The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31Mahmoud Samir Fayed
 
Future of Web Apps: Google Gears
Future of Web Apps: Google GearsFuture of Web Apps: Google Gears
Future of Web Apps: Google Gearsdion
 
Strategies for refactoring and migrating a big old project to be multilingual...
Strategies for refactoring and migrating a big old project to be multilingual...Strategies for refactoring and migrating a big old project to be multilingual...
Strategies for refactoring and migrating a big old project to be multilingual...benjaoming
 
GHC Participant Training
GHC Participant TrainingGHC Participant Training
GHC Participant TrainingAidIQ
 
The Best (and Worst) of Django
The Best (and Worst) of DjangoThe Best (and Worst) of Django
The Best (and Worst) of DjangoJacob Kaplan-Moss
 
The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196Mahmoud Samir Fayed
 
The Ring programming language version 1.9 book - Part 54 of 210
The Ring programming language version 1.9 book - Part 54 of 210The Ring programming language version 1.9 book - Part 54 of 210
The Ring programming language version 1.9 book - Part 54 of 210Mahmoud Samir Fayed
 
Beyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeBeyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeWim Godden
 
Data Migrations in the App Engine Datastore
Data Migrations in the App Engine DatastoreData Migrations in the App Engine Datastore
Data Migrations in the App Engine DatastoreRyan Morlok
 
The Ring programming language version 1.8 book - Part 50 of 202
The Ring programming language version 1.8 book - Part 50 of 202The Ring programming language version 1.8 book - Part 50 of 202
The Ring programming language version 1.8 book - Part 50 of 202Mahmoud Samir Fayed
 
Crowdsourcing with Django
Crowdsourcing with DjangoCrowdsourcing with Django
Crowdsourcing with DjangoSimon Willison
 
Gae Meets Django
Gae Meets DjangoGae Meets Django
Gae Meets Djangofool2nd
 
Backbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVCBackbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVCpootsbook
 
Lo nuevo de Django 1.7 y 1.8
Lo nuevo de Django 1.7 y 1.8Lo nuevo de Django 1.7 y 1.8
Lo nuevo de Django 1.7 y 1.8pedroburonv
 

Similar to Epic South Disasters (20)

Optimization in django orm
Optimization in django ormOptimization in django orm
Optimization in django orm
 
PerlApp2Postgresql (2)
PerlApp2Postgresql (2)PerlApp2Postgresql (2)
PerlApp2Postgresql (2)
 
Django tricks (2)
Django tricks (2)Django tricks (2)
Django tricks (2)
 
The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31The Ring programming language version 1.5 book - Part 8 of 31
The Ring programming language version 1.5 book - Part 8 of 31
 
Future of Web Apps: Google Gears
Future of Web Apps: Google GearsFuture of Web Apps: Google Gears
Future of Web Apps: Google Gears
 
Strategies for refactoring and migrating a big old project to be multilingual...
Strategies for refactoring and migrating a big old project to be multilingual...Strategies for refactoring and migrating a big old project to be multilingual...
Strategies for refactoring and migrating a big old project to be multilingual...
 
GHC Participant Training
GHC Participant TrainingGHC Participant Training
GHC Participant Training
 
The Best (and Worst) of Django
The Best (and Worst) of DjangoThe Best (and Worst) of Django
The Best (and Worst) of Django
 
JavaScript Refactoring
JavaScript RefactoringJavaScript Refactoring
JavaScript Refactoring
 
The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196The Ring programming language version 1.7 book - Part 48 of 196
The Ring programming language version 1.7 book - Part 48 of 196
 
The Ring programming language version 1.9 book - Part 54 of 210
The Ring programming language version 1.9 book - Part 54 of 210The Ring programming language version 1.9 book - Part 54 of 210
The Ring programming language version 1.9 book - Part 54 of 210
 
Beyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the codeBeyond PHP - It's not (just) about the code
Beyond PHP - It's not (just) about the code
 
Data Migrations in the App Engine Datastore
Data Migrations in the App Engine DatastoreData Migrations in the App Engine Datastore
Data Migrations in the App Engine Datastore
 
The Ring programming language version 1.8 book - Part 50 of 202
The Ring programming language version 1.8 book - Part 50 of 202The Ring programming language version 1.8 book - Part 50 of 202
The Ring programming language version 1.8 book - Part 50 of 202
 
Crowdsourcing with Django
Crowdsourcing with DjangoCrowdsourcing with Django
Crowdsourcing with Django
 
Gae Meets Django
Gae Meets DjangoGae Meets Django
Gae Meets Django
 
Backbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVCBackbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVC
 
Backbone Basics with Examples
Backbone Basics with ExamplesBackbone Basics with Examples
Backbone Basics with Examples
 
Django Pro ORM
Django Pro ORMDjango Pro ORM
Django Pro ORM
 
Lo nuevo de Django 1.7 y 1.8
Lo nuevo de Django 1.7 y 1.8Lo nuevo de Django 1.7 y 1.8
Lo nuevo de Django 1.7 y 1.8
 

Recently uploaded

Design Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxDesign Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxFIDO Alliance
 
Long journey of Ruby Standard library at RubyKaigi 2024
Long journey of Ruby Standard library at RubyKaigi 2024Long journey of Ruby Standard library at RubyKaigi 2024
Long journey of Ruby Standard library at RubyKaigi 2024Hiroshi SHIBATA
 
How we scaled to 80K users by doing nothing!.pdf
How we scaled to 80K users by doing nothing!.pdfHow we scaled to 80K users by doing nothing!.pdf
How we scaled to 80K users by doing nothing!.pdfSrushith Repakula
 
JavaScript Usage Statistics 2024 - The Ultimate Guide
JavaScript Usage Statistics 2024 - The Ultimate GuideJavaScript Usage Statistics 2024 - The Ultimate Guide
JavaScript Usage Statistics 2024 - The Ultimate GuidePixlogix Infotech
 
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdfMuhammad Subhan
 
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...panagenda
 
Event-Driven Architecture Masterclass: Challenges in Stream Processing
Event-Driven Architecture Masterclass: Challenges in Stream ProcessingEvent-Driven Architecture Masterclass: Challenges in Stream Processing
Event-Driven Architecture Masterclass: Challenges in Stream ProcessingScyllaDB
 
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPTiSEO AI
 
AI mind or machine power point presentation
AI mind or machine power point presentationAI mind or machine power point presentation
AI mind or machine power point presentationyogeshlabana357357
 
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...FIDO Alliance
 
State of the Smart Building Startup Landscape 2024!
State of the Smart Building Startup Landscape 2024!State of the Smart Building Startup Landscape 2024!
State of the Smart Building Startup Landscape 2024!Memoori
 
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)Paige Cruz
 
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptxHarnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptxFIDO Alliance
 
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on ThanabotsContinuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on ThanabotsLeah Henrickson
 
WebAssembly is Key to Better LLM Performance
WebAssembly is Key to Better LLM PerformanceWebAssembly is Key to Better LLM Performance
WebAssembly is Key to Better LLM PerformanceSamy Fodil
 
ADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptxADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptxFIDO Alliance
 
ERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage IntacctERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage IntacctBrainSell Technologies
 
The Metaverse: Are We There Yet?
The  Metaverse:    Are   We  There  Yet?The  Metaverse:    Are   We  There  Yet?
The Metaverse: Are We There Yet?Mark Billinghurst
 
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...TrustArc
 
Microsoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - QuestionnaireMicrosoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - QuestionnaireExakis Nelite
 

Recently uploaded (20)

Design Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptxDesign Guidelines for Passkeys 2024.pptx
Design Guidelines for Passkeys 2024.pptx
 
Long journey of Ruby Standard library at RubyKaigi 2024
Long journey of Ruby Standard library at RubyKaigi 2024Long journey of Ruby Standard library at RubyKaigi 2024
Long journey of Ruby Standard library at RubyKaigi 2024
 
How we scaled to 80K users by doing nothing!.pdf
How we scaled to 80K users by doing nothing!.pdfHow we scaled to 80K users by doing nothing!.pdf
How we scaled to 80K users by doing nothing!.pdf
 
JavaScript Usage Statistics 2024 - The Ultimate Guide
JavaScript Usage Statistics 2024 - The Ultimate GuideJavaScript Usage Statistics 2024 - The Ultimate Guide
JavaScript Usage Statistics 2024 - The Ultimate Guide
 
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
“Iamnobody89757” Understanding the Mysterious of Digital Identity.pdf
 
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
Easier, Faster, and More Powerful – Alles Neu macht der Mai -Wir durchleuchte...
 
Event-Driven Architecture Masterclass: Challenges in Stream Processing
Event-Driven Architecture Masterclass: Challenges in Stream ProcessingEvent-Driven Architecture Masterclass: Challenges in Stream Processing
Event-Driven Architecture Masterclass: Challenges in Stream Processing
 
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
1111 ChatGPT Prompts PDF Free Download - Prompts for ChatGPT
 
AI mind or machine power point presentation
AI mind or machine power point presentationAI mind or machine power point presentation
AI mind or machine power point presentation
 
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...Hyatt driving innovation and exceptional customer experiences with FIDO passw...
Hyatt driving innovation and exceptional customer experiences with FIDO passw...
 
State of the Smart Building Startup Landscape 2024!
State of the Smart Building Startup Landscape 2024!State of the Smart Building Startup Landscape 2024!
State of the Smart Building Startup Landscape 2024!
 
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)
Observability Concepts EVERY Developer Should Know (DevOpsDays Seattle)
 
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptxHarnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
Harnessing Passkeys in the Battle Against AI-Powered Cyber Threats.pptx
 
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on ThanabotsContinuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
Continuing Bonds Through AI: A Hermeneutic Reflection on Thanabots
 
WebAssembly is Key to Better LLM Performance
WebAssembly is Key to Better LLM PerformanceWebAssembly is Key to Better LLM Performance
WebAssembly is Key to Better LLM Performance
 
ADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptxADP Passwordless Journey Case Study.pptx
ADP Passwordless Journey Case Study.pptx
 
ERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage IntacctERP Contender Series: Acumatica vs. Sage Intacct
ERP Contender Series: Acumatica vs. Sage Intacct
 
The Metaverse: Are We There Yet?
The  Metaverse:    Are   We  There  Yet?The  Metaverse:    Are   We  There  Yet?
The Metaverse: Are We There Yet?
 
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
TrustArc Webinar - Unified Trust Center for Privacy, Security, Compliance, an...
 
Microsoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - QuestionnaireMicrosoft CSP Briefing Pre-Engagement - Questionnaire
Microsoft CSP Briefing Pre-Engagement - Questionnaire
 

Epic South Disasters

  • 1. Epic South Disasters Or: Why You Need to Actually Pay Attention to Your DBMS http://todaysmeet.com/epic-south-disasters Christopher Adams Engineering Lead, Scrollmotion @adamsc64 http://todaysmeet.com/epic-south-disasters https://github.com/adamsc64/epic-south-disasters
  • 2. What is South? South fills a need in the Django ecosystem.
  • 3. The Django ORM A tool to write SQL for you
  • 4. “You can still write SQL if needed.” You don’t have to write SQL any more?
  • 5. In other words, it's your responsibility. Problem: Django does not change a table once it’s created with syncdb.
  • 6. So "if needed" is really "when needed." Most web apps that change often are going to need schema migrations.
  • 7. Django 1.5 documentation The recommended way to migrate schemas: “If you have made changes to a model and wish to alter the database tables to match, use the sql command to display the new SQL structure and compare that to your existing table schema to work out the changes.” https://docs.djangoproject.com/en/1.5/ref/django-admin/#syncdb
  • 9. BEGIN; CREATE TABLE "foo_foo_bars" ( "id" serial NOT NULL PRIMARY KEY, - "foo_id" integer NOT NULL, "bar_id" integer NOT NULL REFERENCES "baz_bar" ("id") DEFERRABLE INITIALLY DEFERRED, + "foo_id" integer NOT NULL, UNIQUE ("foo_id", "bar_id") ) ; CREATE TABLE "foo_foo_zazzs" ( "id" serial NOT NULL PRIMARY KEY, "foo_id" integer NOT NULL, + "user_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, +) -; - "bang_id" integer NOT NULL REFERENCES "baz_bang" ("id") DEFERRABLE INITIALLY DEFERRED, - UNIQUE ("foo_id", "bang_id") -) -; -CREATE TABLE "foo_foo" ( - "user_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, + "properties" text NOT NULL, - "id" serial NOT NULL PRIMARY KEY, "name" varchar(100) NOT NULL, "description" text NOT NULL, + "user_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, "created_by_id" integer REFERENCES "auth_user" ("id") DEFERRABLE INITIALLY DEFERRED, "updated_at" timestamp with time zone NOT NULL, "bing" text NOT NULL, + "properties" text NOT NULL, - "lft" integer CHECK ("lft" >= 0) NOT NULL, - "rght" integer CHECK ("rght" >= 0) NOT NULL, - "tree_id" integer CHECK ("tree_id" >= 0) NOT NULL, - "level" integer CHECK ("level" >= 0) NOT NULL ) ; ALTER TABLE "foo_foo_bars" ADD CONSTRAINT "foo_id_refs_id_1401a163" FOREIGN KEY ("foo_id") REFERENCES "foo_foo" ("id") DEFERRABLE INITIALLY DEFERRED; ALTER TABLE "foo_foo_zazzs" ADD CONSTRAINT "foo_id_refs_id_ca2b0e5" FOREIGN KEY ("foo_id") REFERENCES "foo_foo" ("id") DEFERRABLE INITIALLY DEFERRED; +ALTER TABLE "foo_foo" ADD CONSTRAINT "parent_id_refs_id_1eb29019" FOREIGN KEY ("parent_id") REFERENCES "foo_foo" ("id") DEFERRABLE INITIALLY DEFERRED; SQL diffs are no fun
  • 11. South Solves Some Problems for us.
  • 12. Problems South solves: 1. Automates writing schema migrations for us. 2. In Python (no SQL). 3. Broadly “Don’t Repeat Yourself” (DRY). 4. Migrations are applied in order. 5. Version control migration code. 6. Shared migrations, dev and production. 7. Fast iteration.
  • 13. Levels of abstraction ● great because they take us away from the messy details ● risky because they take us away from the messy details ● can obscure what’s going on
  • 14.
  • 15. How to Use South (very quick 101)
  • 16. Our Model +++ b/minifier/models.py class MinifiedURL(models.Model): url = models.CharField( max_length=100) datetime = models.DateTimeField( auto_now_add=True)
  • 17. Initial Migration $ ./manage.py schemamigration minifier -- initial Creating migrations directory at '/... /minifier/migrations'... Creating __init__.py in '/... /minifier/migrations'... + Added model minifier.MinifiedURL Created 0001_initial.py. You can now apply this migration with: ./manage.py migrate minifier
  • 18. Initial Migration $ ls -l minifier/migrations/ total 8 -rw-r--r-- 1 chris staff 1188 Aug 30 11:40 0001_initial.py -rw-r--r-- 1 chris staff 0 Aug 30 11:40 __init__.py
  • 19. Initial Migration $ ./manage.py syncdb Syncing... Creating tables ... Creating table south_migrationhistory Synced: > django.contrib.auth > south Not synced (use migrations): - minifier (use ./manage.py migrate to migrate these)
  • 20. Initial Migration $ ./manage.py migrate Running migrations for minifier: - Migrating forwards to 0001_initial. > minifier:0001_initial - Loading initial data for minifier. Installed 0 object(s) from 0 fixture(s)
  • 21. Ok, let’s add a field.
  • 22. Adding a Field class MinifiedURL(models.Model): + submitter = models.ForeignKey( + 'auth.user', null=True) url = models.CharField( max_length=100) datetime = models.DateTimeField( auto_now_add=True)
  • 23. $ ./manage.py schemamigration minifier --auto + Added field submitter on minifier.MinifiedURL Created 0002_auto__add_field_minifiedurl_submitt er.py. You can now apply this migration with: ./manage. py migrate minifier Adding a Field
  • 24. class Migration(SchemaMigration): def forwards(self, orm): # Adding field 'MinifiedURL.submitter' db.add_column(u'minifier_minifiedurl', 'submitter', ... ) def backwards(self, orm): # Deleting field 'MinifiedURL.submitter' db.delete_column(u'minifier_minifiedurl', 'submitter_id' ) Adding a Field
  • 25. Adding a Field $ ./manage.py migrate minifier 0002 - Soft matched migration 0002 to 0002_auto__add_field_minifiedurl_submitter. Running migrations for minifier: - Migrating forwards to 0002_auto__add_field_minifiedurl_submitter. > minifier: 0002_auto__add_field_minifiedurl_submitter - Loading initial data for minifier. Installed 0 object(s) from 0 fixture(s)
  • 26. $ ./manage.py dbshell psql-# d+ minifier_minifiedurl; Table "public.minifier_minifiedurl" Column | Type --------------+------------------------- id | integer url | character varying(100) submitter_id | integer datetime | timestamp with time zone It worked!
  • 27. More details: follow the South Tutorial http://south.readthedocs.org/en/latest/tutorial/
  • 28. ● Many people approach a new tool with a broad set of expectations as to what they think it will do for them. ● This may have little correlation with what the project actually has implemented. Expectations
  • 30. Our Model class MinifiedURL(models.Model): submitter = models.ForeignKey( 'auth.user', null=True) url = models.CharField( max_length=100) datetime = models.DateTimeField( auto_now_add=True)
  • 31. Our Model class MinifiedURL(models.Model): submitter = models.ForeignKey( 'auth.user', null=True) url = models.CharField( max_length=100) + created = models.DateTimeField( auto_now_add=True)
  • 32. $ vim minifier/models.py $ ./manage.py schemamigration minifier --auto $ git commit -am "Rename field." $ git push $ ./deploy-to-production.sh Done! Fast iteration!
  • 33.
  • 34. Fast iteration! +++ b/minifier/migrations/0003_auto_del_field. py # Deleting field 'MinifiedURL.datetime' db.delete_column(u'minifier_minifiedurl', 'datetime') # Adding field 'MinifiedURL.created' db.add_column(u'minifier_minifiedurl', 'created', ... )
  • 35. Lesson #1 Always read migrations that are generated with --auto.
  • 36. So how do we do this? class MinifiedURL(models.Model): submitter = models.ForeignKey( 'auth.user', null=True) url = models.CharField( max_length=100) + created = models.DateTimeField( auto_now_add=True)
  • 37. Data migration - basic example 1. schemamigration - Create the new field. 2. datamigration - Copy the data to the new field from the old field. 3. schemamigration - Delete the old field.
  • 38. Data migration - basic example class MinifiedURL(models.Model): submitter = models.ForeignKey( 'auth.user', null=True) url = models.CharField( max_length=100) datetime = models.DateTimeField( auto_now_add=True) + created = models.DateTimeField( auto_now_add=True)
  • 39. Data migration - basic example $ ./manage.py schemamigration minifier --auto Created 0003_auto__add_field_minifiedurl_crea ted.py. $ ./manage.py datamigration minifier datetime_to_created Created 0004_datetime_to_created.py. $ vim minifier/migrations/0004_datetime_to_ created.py
  • 40. # Note: Don't use "from # appname.models import ModelName". # Use orm.ModelName to refer to # models in this application... Data migration - basic example
  • 41. Data migration - basic example class Migration(DataMigration): def forwards(self, orm): + for minified_url in orm.MinifiedURL.objects.all(): + minified_url.created = minified_url.datetime + minified_url.save() def backwards(self, orm): + for minified_url in orm.MinifiedURL.objects.all(): + minified_url.datetime = minified_url.created + minified_url.save()
  • 42. Data migration - basic example class MinifiedURL(models.Model): submitter = models.ForeignKey( 'auth.user', null=True) url = models.CharField( max_length=100) - datetime = models.DateTimeField( auto_now_add=True) created = models.DateTimeField( auto_now_add=True)
  • 43. $ ./manage.py migrate --list minifier (*) 0001_initial (*) 0002_auto__add_field_minifiedurl_submitt ( ) 0003_auto__add_field_minifiedurl_created ( ) 0004_datetime_to_created ( ) 0005_auto__del_field_minifiedurl_datetim Data migration - basic example
  • 44. $ ./manage.py migrate - Migrating forwards to 0005_auto__del_ field_minifiedurl_datetime. > minifier:0003_auto__add_field_minifiedurl_ created > minifier:0004_datetime_to_created > minifier:0005_auto__del_field_ minifiedurl_datetime Data migration - basic example
  • 45. $ ./manage.py migrate - Migrating forwards to 0005_auto__del_ field_minifiedurl_datetime. > minifier:0003_auto__add_field_minifiedurl_ created > minifier:0004_datetime_to_created > minifier:0005_auto__del_field_ minifiedurl_datetime Data migration - basic example
  • 46. South’s frozen ORM is pretty nifty. It will expose the model at an historical point-in-time.
  • 47. Danger. Many parts of the Django ORM still function.
  • 48. Our Model class MinifiedURL(models.Model): submitter = models.ForeignKey( 'auth.user') created = models.DateTimeField( auto_now_add=True) updated = models.DateTimeField( auto_now=True) url = models.CharField( max_length=100)
  • 49. Our Model class MinifiedURL(models.Model): submitter = models.ForeignKey( 'auth.user') created = models.DateTimeField( auto_now_add=True) updated = models.DateTimeField( auto_now=True) url = models.CharField( max_length=100) + domain = models.CharField( + max_length=30)
  • 50. class Migration(DataMigration): def forwards(self, orm): + model = orm.MinifiedURL + + for minified_url in model.objects.all(): + minified_url.domain = ( + minified_url.url.split('/')[2] + ) + minified_url.save() Data migration
  • 51. $ git commit -am "Migrate 'domain' from 'url' field." $ git push $ ./deploy-to-production.sh Done! Fast iteration!
  • 52.
  • 53. Before and After pk updated (before) ---|-----------------| 566|2013-03-01 09:01 | 567|2012-01-22 17:34 | 568|2012-12-31 19:11 | 569|2013-04-10 10:02 | ...
  • 54. Before and After pk updated (before) updated (after) ---|-----------------|-----------------| 566|2013-03-01 09:01 |2013-09-04 14:01 | 567|2012-01-22 17:34 |2013-09-04 14:01 | 568|2012-12-31 19:11 |2013-09-04 14:01 | 569|2013-04-10 10:02 |2013-09-04 14:01 | ... ...
  • 55. Oh no! Why did we lose datetime information?
  • 56. Our Model class MinifiedURL(models.Model): submitter = models.ForeignKey( 'auth.user') created = models.DateTimeField( auto_now_add=True) updated = models.DateTimeField( auto_now=True) url = models.CharField( max_length=100)
  • 57. The South ORM wraps over the Django ORM, which applies rules such as auto_now=True and auto_now_add=True.
  • 58. Especially nasty because no exception raised or warning given even with this kind of data loss.
  • 59. Lesson #2 Always confirm your migrations do what you expect -- and nothing more.
  • 60. Workaround + opts = model._meta + field = opts.get_field_by_name('updated')[0] + old_auto_now = field.auto_now + field.auto_now = False for minified_url in model.objects.all(): minified_url.domain = ( minified_url.url.split('/')[2] ) minified_url.save() + field.auto_now = old_auto_now Peter Bengtsson, http://www.peterbe.com/plog/migration-south-auto_now_add
  • 61. Before and After pk updated (before) updated (after) ---|-----------------|-----------------| 566|2013-03-01 09:01 |2013-03-01 09:01 | 567|2012-01-22 17:34 |2012-01-22 17:34 | 568|2012-12-31 19:11 |2012-12-31 19:11 | 569|2013-04-10 10:02 |2013-04-10 10:02 | ... ...
  • 62. Oh no! Why did all our users suddenly get emailed?
  • 63. @receiver(post_save) def email_user_on_save(sender, **kwargs): """ Not sure why I'm doing this here, but it seems like a good place! REFACTOR LATER TBD FYI!! """ if sender.__name__ == "MinifiedURL": email(kwargs['instance'].submitter, "Congratulations on changing " "your url!", ) Whoops, forgot about this.
  • 64. The South ORM wraps over the Django ORM, so it sends post_save signals.
  • 65. However, the metaclass magic usually takes care of avoiding problems.
  • 66. @receiver(post_save) def email_user_on_save(sender, **kwargs): """ Not sure why I'm doing this here, but it seems like a good place! REFACTOR LATER TBD FYI!! """ if sender.__name__ == "MinifiedURL": email(kwargs['instance'].submitter, "Congratulations on changing " "your url!", ) So this...
  • 67. @receiver(post_save) def email_user_on_save(sender, **kwargs): """ Not sure why I'm doing this here, but it seems like a good place! REFACTOR LATER TBD FYI!! """ - if sender.__name__ == "MinifiedURL": + if sender == MinifiedURL: email(kwargs['instance'].submitter, "Congratulations on changing " "your url!", ) ...should be this.
  • 68. ipdb> print repr(sender) <class 'minifier.models.MinifiedURL'> ipdb> print repr(MinifiedURL) <class 'minifier.models.MinifiedURL'> ipdb> print MinifiedURL == sender False ipdb> print id(MinifiedURL) 140455961329984 ipdb> print id(sender) 140455961864000 Metaclass magic
  • 69. Lesson #3 Always check data migrations for unintended consequences.
  • 70. class MinifiedURL(models.Model): created = models.DateTimeField( auto_now_add=True) updated = models.DateTimeField( auto_now=True) url = models.CharField( max_length=100, db_index=True) Our Model
  • 71. class MinifiedURL(models.Model): created = models.DateTimeField( auto_now_add=True) updated = models.DateTimeField( auto_now=True) - url = models.CharField( - max_length=100, db_index=True) + url = models.CharField( + max_length=1000, db_index=True) Our Model
  • 72. $ ./manage.py schemamigration minifier --auto ~ Changed field url on minifier.MinifiedURL Created 0010_auto__chg_field_minifiedurl_ url. py. You can now apply this migration with: . /manage.py migrate minifier Create the schema migration.
  • 73. Seems fine... $ ./manage.py migrate Running migrations for minifier: - Migrating forwards to 0010_auto__chg_field_minifiedurl_url. > minifier: 0010_auto__chg_field_minifiedurl_url - Loading initial data for minifier. Installed 0 object(s) from 0 fixture(s) “Works fine on development?” “Ship it!”
  • 74. Production vs. Development Beware of differences in configuration.
  • 75. From a Django blog 7. Local vs. Production Environments Django comes with sqlite, a simple flat-file database that doesn't need any configuration. This makes prototyping fast and easy right out of the box. However, once you've moved your project into a production environment, odds are you'll have to use a more robust database like Postgresql or MySQL. This means that you're going to have two separate environments: production and development. http://net.tutsplus.com/tutorials/other/10-django-troublespots-for-beginners/
  • 76. $ git commit -am "Add some breathing space to url fields." $ git push $ ./deploy-to-production.sh Done! Fast iteration!
  • 77. Migration Failed Running migrations for minifier: - Migrating forwards to 0010_auto__chg_field_minifiedurl. > minifier:0010_auto__chg_field_minifiedurl ! Error found during real run of migration! Aborting. Error in migration: minifier: 0010_auto__chg_field_minifiedurl Warning: Specified key was too long; max key length is 250 bytes
  • 78. class MinifiedURL(models.Model): created = models.DateTimeField( auto_now_add=True) updated = models.DateTimeField( auto_now=True) url = models.CharField( max_length=1000, db_index=True) Our Model
  • 79. Always pay attention to the limitations of your DBMS. Lesson #4
  • 80. Schema-altering commands (DDL commands) cause a phantom auto-commit. Major limitation of MySQL
  • 81. With InnoDB, when a client executes a DDL change, the server executes an implicit commit even if the normal auto- commit behavior is turned off.
  • 82. DDL transaction on Postgres psql=# DROP TABLE IF EXISTS foo; NOTICE: table "foo" does not exist psql=# BEGIN; psql=# CREATE TABLE foo (bar int); psql=# INSERT INTO foo VALUES (1); psql=# ROLLBACK; # rolls back two cmds psql=# SELECT * FROM foo; ERROR: relation "foo" does not exist
  • 83. No DDL transaction on MySQL mysql> drop table if exists foo; mysql> begin; mysql> create table foo (bar int) type=InnoDB; mysql> insert into foo values (1); mysql> rollback; # Table 'foo' exists! mysql> select * from foo; 0 rows in set (0.00 sec)
  • 84. South uses DDL transactions if they are available.
  • 85. Pay attention to your DBMS FATAL ERROR - The following SQL query failed: ALTER TABLE `minifier_minifiedurl` ADD CONSTRAINT `minifier_minifiedurl_url_263b28b6c6b349a8_uniq` UNIQUE (`name`) The error was: (1062, "Duplicate entry 'http://cnn.com' for key 'minifier_minifiedurl_url_263b28b6c6b349a8_uniq'") ! Error found during real run of migration! Aborting. ! Since you have a database that does not support running ! schema-altering statements in transactions, we have had ! to leave it in an interim state between migrations. ! You *might* be able to recover with: = ALTER TABLE `minifier_minifiedurl` DROP COLUMN `url` CASCADE; [] - no dry run output for alter_column() due to dynamic DDL, sorry ! The South developers regret this has happened, and would ! like to gently persuade you to consider a slightly ! easier-to-deal-with DBMS (one that supports DDL transactions) ! NOTE: The error which caused the migration to fail is further up.
  • 86.
  • 87. 1. Always read migrations that are generated with --auto. 2. Always confirm your migrations do what you expect. 3. Always check data migrations for unintended consequences. 4. Always pay attention to the limitations of your DBMS. Lessons Recap
  • 88. Encouragement ● Tools are not the problem. Tools are why we are in this business. ● Knowledge is power. Know what South is doing. ● Know what Django is doing for that matter.
  • 89.
  • 90.
  • 91. ● David Cho ● Hadi Arbabi ● Mike Harris Special Thanks