SlideShare a Scribd company logo
WRITING APPS THE GOOGLE-Y WAY Pamela Fox, YOW! Australia 2010
Who am I? twitter.com/pamelafox [email_address] pamelafox.org you get the idea...
Who am I? USC Google Amazon, Flickr, Maps Google Maps API Google Wave API Spreadsheets, Blogger, Youtube, Picasa, Gadgets,  App Engine
Who am I? Java pYthon
What is App Engine? “ Google App Engine enables you to build and host web apps on the same systems that power Google applications.” http://code.google.com/appengine
What is a “web app”?
Static vs. Dynamic
Anonymous vs. Users
Intranet vs. Internet ~2 billion Hundreds - Thousands
What is a “web app”?
Some Google web apps
Some Google App Engine web apps www.gifttag.com www.buddypoke.com
Google apps on App Engine panoramio.com pubsubhubbub.appspot.com
How does App Engine work? You upload application code & resources to Google. Google serves your application from scalable infrastructure. You pay for only the resources that Google used in serving the application.
Example app: YOW!*
App Engine architecture user or task
App Engine architecture
App Engine architecture LIMIT!
The tricky bits LIMIT!
Datastore Entity Properties Key Entity Entity Entity Entity Path Kind Name/ID
Example: Speaker Entities Key Path Kind ID First Name Last Name Speaker1 - Speaker 1 Rod Johnson Key Path Kind ID First Name Last Name Middle Name Suffix Speaker1 - Speaker 2 Guy Steele L Jr.
Modeling Speaker Entities class Speaker(db.model): firstname = db.StringProperty(required=True) lastname = db.StringProperty(required=True) middlename = db.StringProperty() namesuffix = db.StringProperty() website = db.StringProperty() keynote = db.BooleanProperty(default=False)
Saving Speaker Entities ron = Speaker(firstname="Ron", lastname="Johnson") guy = Speaker(firstname="Guy", lastname="Steele", middlename="L", namesuffix="Jr.") ron.put() guy.put()
Updating Speaker Entities ron = Speaker.get_by_id(1) guy = Speaker.get_by_id(2) ron.website = "http://www.ronjohnson.com" ron.keynote = True guy.website = "http://www.guysteele.com" guy.keynote = True db.put(ron, guy) LIMIT!
How Updates Happen commit journal apply entities apply indexes A B
Queries & Indexes Query Index Index Index Index Query Query Query Query Query
Queries & Indexes SELECT * from Speaker ORDER BY lastname LIMIT! key lastname Speaker3 Fox Speaker4 Hohpe Speaker1 Johnson Speaker2 Steele
Queries & Indexes SELECT * from Speaker ORDER by middlename key middlename Speaker2 L
Queries & Indexes SELECT * from Speaker WHERE keynote = True key keynote Speaker1 True Speaker2 True Speaker3 False Speaker4 False
Queries & Indexes SELECT * from Speaker WHERE keynote = False key keynote Speaker1 True Speaker2 True Speaker3 False Speaker4 False
Queries allspeakers = Speaker.all().order('lastname) for speaker in allspeakers: print speaker.firstname + '' + speaker.lastname + '' +    speaker.website keynotespeakers = Speaker.all().filter('keynote = ', True) notspecialspeakers = Speaker.all().filter('keynote = ', False) LIMIT!
Custom Indexes speakers = Speaker.all().order('lastname') .order('keynote') SELECT * from Speaker ORDER BY lastname, keynote key lastname keynote Speaker3 Fox false Speaker4 Hohpe false Speaker1 Johnson true Speaker2 Steele true
Custom Indexes speakers = Speaker.all().order('lastname') .filter('keynote =', True) SELECT * from Speaker WHERE lastname > 'Johnson'  and keynote = true key lastname keynote Speaker3 Fox false Speaker4 Hohpe false Speaker1 Johnson true Speaker2 Steele true
Impossible Indexes SELECT * from Speaker WHERE lastname < 'Steele'  and firstname > 'Gregory' ...not in subsequent rows! key lastname firstname Speaker3 Fox Pamela Speaker4 Hohpe Gregory Speaker1 Johnson Ron Speaker2 Steele Guy
Impossible Indexes SELECT * from Speaker WHERE lastname > 'Fox' ORDER BY firstname  ...not in the correct order! key lastname firstname Speaker3 Fox Pamela Speaker4 Hohpe Gregory Speaker1 Johnson Ron Speaker2 Steele Guy
Queries with Offset speakers = Speaker.all().fetch(2, 2) SELECT * from Speaker LIMIT 2 OFFSET 2 1 2 ...slow! LIMIT! key lastname Speaker3 Fox Speaker4 Hohpe Speaker1 Johnson Speaker2 Steele
Queries with Cursors query = db.Query(Speaker) speakers = q.fetch(1000) cursor = q.cursor() memcache.set('speaker_cursor', cursor) ... last_cursor = memcache.get('speaker_cursor') q.with_cursor(last_cursor) speakers = q.fetch(1000)
More Properties class Talk(db.Model): title = db.StringProperty(required=True) abstract = db.TextProperty(required=True) speaker = db.ReferenceProperty(Speaker) tags = db.StringListProperty() pamela = Speaker.all().filter('firstname = ', 'Pamela').get() talk = Talk('Writing Apps the Googley Way', 'Bla bla bla', pamela, ['App Engine', 'Python']) talk.put() talk = Talk('Wonders of the Onesie', 'Bluh bluh bluh', pamela, ['Pajamas', 'Onesies']) talk.put()
Back-References pamela = Speaker.all().filter('firstname = ', 'Pamela').get() for talk in pamela.talk_set: print talk.title SELECT * from Talk WHERE speaker = Speaker3 key speaker Talk6 Speaker2 Talk1 Speaker3 Talk2 Speaker3 Talk5 Speaker4
Searching List Properties talks = Talk.all().filter('tags = ', 'python').fetch(10) SELECT * from Talk WHERE tags = 'Python' LIMIT! key lastname Talk1 App Engine Talk2 Pajamas Talk1 Python Talk2 Onesies
Entity Groups pamela = Speaker.all().filter('firstname = ', 'Pamela').get() talk1 = Talk('Writing Apps the Googley Way', 'Bla bla bla', pamela, ['App Engine', 'Python'], parent=pamela) talk2 = Talk('Wonders of the Onesie', 'Bluh bluh bluh', pamela, ['Pajamas', 'Onesies'], parent=pamela) db.put(talk1, talk2) def update_talks(): talk1.title = 'Writing Apps the Microsoft Way' talk2.title = 'Wonders of the Windows' db.put(talk1, talk2) db.run_in_transaction(update_talks)
Common Features
Counters 1 2 3 4 5 people have done something.
RageTube: Global Stats
RageTube: Global Stats SongStat yaycount viewcount title artist Key Path Kind Name (song) naycount mehcount
RageTube: Global Stats viewcount viewcount viewcount datastore memcache
RageTube: Global Stats class Song(db.Model): viewcount = db.IntegerProperty(default=0) title = db.StringProperty() artist = db.StringProperty() def get_viewcount(self): viewcount = self.viewcount cached_viewcount = memcache.get('viewcount-' + self.key().name(), self.key().kind()) if cached_viewcount: viewcount += cached_viewcount return viewcount @classmethod def flush_viewcount(cls, name): song = cls.get_by_key_name(name) value = memcache.get('viewcount-' + name, cls.kind()) memcache.decr('viewcount-' + name, value, cls.kind()) song.viewcount += value song.put() @classmethod def incr_viewcount(cls, name, interval=5, value=1): memcache.incr('viewcount-' + name, value, cls.kind()) interval_num = get_interval_number(datetime.now(), interval) task_name = '-'.join([cls.kind(), name.replace(' ', '-'),  'viewcount', str(interval), str(interval_num)]) deferred.defer(cls.flush_viewcount, name, _name=task_name) LIMIT!
Ratings Rated by 500 users.
App Gallery: Ratings
App Gallery: Ratings Comment Application total_ratings sum_ratings avg_rating rated_index comment_count rating
App Gallery: Ratings def UpdateAppCommentData(self, rating, operation): def UpdateCommentData(self, rating, operation): self.comment_count += 1 * operation self.sum_ratings += rating * operation self.total_ratings += 1 * operation self.avg_rating = int(round(self.sum_ratings / self.total_ratings)) self.rated_index = '%d:%d:%d' %  (self.avg_rating, self.total_ratings, self.index) self.put() db.run_in_transaction(UpdateCommentData, self, rating, operation) app.UpdateAppCommentData(rating, db_models.Comment.ADD) comment = db_models.Comment() comment.application = app comment.rating = rating comment.put() query.order('-avg_rating').order('-rated_index')
Geospatial Queries
City-Go-Round: Agencies citygoround.org https://github.com/walkscore/City-Go-Round
City-Go-Round: Geo Queries Agency GeoModel location (GeoPt) location_geocells (StringListProperty)
City-Go-Round: Geo Queries def fetch_agencies_near(lat, long, bbox_side_in_miles): query = Agency.all() bbox = bbox_centered_at(lat, long, bbox_side_in_miles) return Agency.bounding_box_fetch(query, bbox, max_results = 50) def bounding_box_fetch(query, bbox, max_results=1000,): results = [] query_geocells = geocell.best_bbox_search_cells(bbox) for entity in query.filter('location_geocells IN', query_geocells): if len(results) == max_results: break if (entity.location.lat >= bbox.south and entity.location.lat <= bbox.north and entity.location.lon >= bbox.west and entity.location.lon <= bbox.east): results.append(entity) return results
City-Go-Round: Apps citygoround.org
City-Go-Round: Geo Queries TransitAppLocation GeoModel location (GeoPt) location_geocells (StringListProperty) TransitApp
City-Go-Round: Geo Queries class TransitAppLocation(GeoModel): transit_app = db.ReferenceProperty(TransitApp) def fetch_transit_app_locations_near(lat, longi): query = TransitAppLocation.all() bbox = bbox_centered_at(lat, long, bbox_side_in_miles) return TransitAppLocation.bounding_box_fetch(query, bounding_box, max_results = 500) def fetch_transit_apps_near(lat, long): transit_app_locations = TransitAppLocation.fetch_transit_app_locations_near(lat, long)] transit_apps = [transit_app_location.transit_app  for transit_app_location in transit_app_locations] return transit_apps
Full Text Search pizza Search Thingy It's like pizza, but in the cloud. Other Thingy This will make you smell as delicious as pizza.
Disclosed.ca: Search
Disclosed.ca: Search Contract agency_name vendor_name description comments uri
Disclosed.ca: Search from search.core import SearchIndexProperty, porter_stemmer class Contract(db.Model): uri = db.StringProperty(required=True) agency_name = db.StringProperty(required=True) vendor_name = db.StringProperty(required=True) description = db.StringProperty() comments = db.TextProperty() search_index = SearchIndexProperty(('agency_name', 'vendor_name', 'description', 'comments'), indexer=porter_stemmer) results = Contract.search_index.search(sheep').fetch(20)
Disclosed.ca: Search Contract agency_name vendor_name description comments uri search_index (StringListProperty) SearchIndex
Disclosed.ca: Search SELECT FROM ContractSearch WHERE search_index = &quot;sheep&quot; key search_index ContractSearch1 charter ContractSearch1 june ContractSearch1 sheep ContractSearch2 sheep ContractSearch1 wood
More Learning http://ae-book.appspot.com http://code.google.com/appengine http://blog.notdot.net/
AppEngine: Now & Later &quot;Run your web apps on Google's infrastructure. Easy to build, easy to maintain, easy to scale.&quot; Roadmap: Background processes > 30s MapReduce Bulk Import/Export Channel API App Engine for Business: SQL Other stuff..
Thanks for coming! *I am never very good at conclusions, so this slide is a subtle notice to all of you that I am done talking now.

More Related Content

What's hot (20)

PPT
Php Basic Security
mussawir20
 
PPT
Writing Friendly libraries for CodeIgniter
CodeIgniter Conference
 
PDF
WIRED and the WP REST API
kvignos
 
ODP
Creating Themes
DaisyOlsen
 
ODP
Ruby on discuz
Mu-Fan Teng
 
PPT
Rails 101
The Active Network
 
PPT
Dealing with Legacy Perl Code - Peter Scott
O'Reilly Media
 
PPT
Introduction to python scrapping
n|u - The Open Security Community
 
PPT
Power Theming
drkdn
 
PPTX
Reno-Tahoe WordCamp 2011 - WordPress End User Security - Dre Armeda
Dre Armeda
 
ODP
ABC of Perl programming
Bo Hua Yang
 
PDF
Moving from Django Apps to Services
Craig Kerstiens
 
PDF
Elasticsearch – mye mer enn søk! [JavaZone 2013]
foundsearch
 
PDF
Web Scraping is BS
John D
 
PDF
Finding things on the web with BOSS
Christian Heilmann
 
PPSX
Symfony2 meets propel 1.5
Francois Zaninotto
 
PDF
How does get template part works in twenty ten theme
mohd rozani abd ghani
 
PPT
Google Wave 20/20: Product, Protocol, Platform
Pamela Fox
 
PPTX
Introduction to PHP Lecture 1
Ajay Khatri
 
Php Basic Security
mussawir20
 
Writing Friendly libraries for CodeIgniter
CodeIgniter Conference
 
WIRED and the WP REST API
kvignos
 
Creating Themes
DaisyOlsen
 
Ruby on discuz
Mu-Fan Teng
 
Dealing with Legacy Perl Code - Peter Scott
O'Reilly Media
 
Introduction to python scrapping
n|u - The Open Security Community
 
Power Theming
drkdn
 
Reno-Tahoe WordCamp 2011 - WordPress End User Security - Dre Armeda
Dre Armeda
 
ABC of Perl programming
Bo Hua Yang
 
Moving from Django Apps to Services
Craig Kerstiens
 
Elasticsearch – mye mer enn søk! [JavaZone 2013]
foundsearch
 
Web Scraping is BS
John D
 
Finding things on the web with BOSS
Christian Heilmann
 
Symfony2 meets propel 1.5
Francois Zaninotto
 
How does get template part works in twenty ten theme
mohd rozani abd ghani
 
Google Wave 20/20: Product, Protocol, Platform
Pamela Fox
 
Introduction to PHP Lecture 1
Ajay Khatri
 

Similar to Writing Apps the Google-y Way (20)

PPT
Python - Getting to the Essence - Points.com - Dave Park
pointstechgeeks
 
ODP
The bones of a nice Python script
saniac
 
PPT
Django
webuploader
 
PPT
Fantom and Tales
kaushik_sathupadi
 
PPT
Object Orientation vs. Functional Programming in Python
Python Ireland
 
PPT
Javascript Primer
Adam Hepton
 
ODP
Decorators in Python
Ben James
 
PPT
JavaScript Needn't Hurt!
Thomas Kjeldahl Nilsson
 
PPT
Demystifying Maven
Mike Desjardins
 
PDF
Having Fun Programming!
Aaron Patterson
 
PPTX
Custom Signals for Uncoupled Design
ecomsmith
 
ODP
Testing Jboss Seam Bottom Up
Dan Hinojosa
 
PPT
Plone For Developers - World Plone Day, 2009
Core Software Group
 
ODP
SlideShare Instant
Saket Choudhary
 
PPT
SlideShare Instant
Saket Choudhary
 
PPT
The Kotlin Programming Language
intelliyole
 
PPT
Declarative Development Using Annotations In PHP
stubbles
 
PPT
Declarative Development Using Annotations In PHP
Stephan Schmidt
 
PPT
Apache Ant
hussulinux
 
PPT
Go OO! - Real-life Design Patterns in PHP 5
Stephan Schmidt
 
Python - Getting to the Essence - Points.com - Dave Park
pointstechgeeks
 
The bones of a nice Python script
saniac
 
Django
webuploader
 
Fantom and Tales
kaushik_sathupadi
 
Object Orientation vs. Functional Programming in Python
Python Ireland
 
Javascript Primer
Adam Hepton
 
Decorators in Python
Ben James
 
JavaScript Needn't Hurt!
Thomas Kjeldahl Nilsson
 
Demystifying Maven
Mike Desjardins
 
Having Fun Programming!
Aaron Patterson
 
Custom Signals for Uncoupled Design
ecomsmith
 
Testing Jboss Seam Bottom Up
Dan Hinojosa
 
Plone For Developers - World Plone Day, 2009
Core Software Group
 
SlideShare Instant
Saket Choudhary
 
SlideShare Instant
Saket Choudhary
 
The Kotlin Programming Language
intelliyole
 
Declarative Development Using Annotations In PHP
stubbles
 
Declarative Development Using Annotations In PHP
Stephan Schmidt
 
Apache Ant
hussulinux
 
Go OO! - Real-life Design Patterns in PHP 5
Stephan Schmidt
 
Ad

More from Pamela Fox (20)

PDF
Teaching Programming Online
Pamela Fox
 
PDF
Engineering culture
Pamela Fox
 
KEY
Django Admin: Widgetry & Witchery
Pamela Fox
 
PDF
A Year of Hermit Hacking
Pamela Fox
 
PDF
The Developer Experience
Pamela Fox
 
PDF
Making JavaScript Libraries More Approachable
Pamela Fox
 
PPT
How I became a born again vegetable-tarian
Pamela Fox
 
KEY
The Developer Experience
Pamela Fox
 
PPT
No, Really, I'm Shy
Pamela Fox
 
PPT
The Wonders of the "Onesie"
Pamela Fox
 
PPT
I’M A Barbie Girl In A CS World
Pamela Fox
 
PPT
Collaborative Mapping with Google Wave
Pamela Fox
 
PPT
Google Products: Deep Dive on Google Maps
Pamela Fox
 
PPT
Google Products & Google Maps
Pamela Fox
 
PPT
A World of Words
Pamela Fox
 
PPT
Web APIs & Google APIs
Pamela Fox
 
PPT
Growing up Geek: My Dad, the Computer Scientist
Pamela Fox
 
PPT
Living in the Cloud: Hosting Data & Apps Using the Google Infrastructure
Pamela Fox
 
PPT
Client Killed the Server Star
Pamela Fox
 
PPT
Flex vs. HTML5 for RIAS
Pamela Fox
 
Teaching Programming Online
Pamela Fox
 
Engineering culture
Pamela Fox
 
Django Admin: Widgetry & Witchery
Pamela Fox
 
A Year of Hermit Hacking
Pamela Fox
 
The Developer Experience
Pamela Fox
 
Making JavaScript Libraries More Approachable
Pamela Fox
 
How I became a born again vegetable-tarian
Pamela Fox
 
The Developer Experience
Pamela Fox
 
No, Really, I'm Shy
Pamela Fox
 
The Wonders of the "Onesie"
Pamela Fox
 
I’M A Barbie Girl In A CS World
Pamela Fox
 
Collaborative Mapping with Google Wave
Pamela Fox
 
Google Products: Deep Dive on Google Maps
Pamela Fox
 
Google Products & Google Maps
Pamela Fox
 
A World of Words
Pamela Fox
 
Web APIs & Google APIs
Pamela Fox
 
Growing up Geek: My Dad, the Computer Scientist
Pamela Fox
 
Living in the Cloud: Hosting Data & Apps Using the Google Infrastructure
Pamela Fox
 
Client Killed the Server Star
Pamela Fox
 
Flex vs. HTML5 for RIAS
Pamela Fox
 
Ad

Recently uploaded (20)

PPTX
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
PPTX
Using Google Data Studio (Looker Studio) to Create Effective and Easy Data Re...
Orage Technologies
 
PDF
TrustArc Webinar - Navigating Data Privacy in LATAM: Laws, Trends, and Compli...
TrustArc
 
PDF
OFFOFFBOX™ – A New Era for African Film | Startup Presentation
ambaicciwalkerbrian
 
PDF
Market Insight : ETH Dominance Returns
CIFDAQ
 
PDF
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
PDF
Brief History of Internet - Early Days of Internet
sutharharshit158
 
PPTX
Farrell_Programming Logic and Design slides_10e_ch02_PowerPoint.pptx
bashnahara11
 
PDF
How ETL Control Logic Keeps Your Pipelines Safe and Reliable.pdf
Stryv Solutions Pvt. Ltd.
 
PPTX
The Future of AI & Machine Learning.pptx
pritsen4700
 
PPTX
Agile Chennai 18-19 July 2025 | Workshop - Enhancing Agile Collaboration with...
AgileNetwork
 
PPTX
Applied-Statistics-Mastering-Data-Driven-Decisions.pptx
parmaryashparmaryash
 
PPTX
Simple and concise overview about Quantum computing..pptx
mughal641
 
PDF
Build with AI and GDG Cloud Bydgoszcz- ADK .pdf
jaroslawgajewski1
 
PDF
Researching The Best Chat SDK Providers in 2025
Ray Fields
 
PDF
Tea4chat - another LLM Project by Kerem Atam
a0m0rajab1
 
PPTX
AI Code Generation Risks (Ramkumar Dilli, CIO, Myridius)
Priyanka Aash
 
PPTX
PCU Keynote at IEEE World Congress on Services 250710.pptx
Ramesh Jain
 
PPTX
Agentic AI in Healthcare Driving the Next Wave of Digital Transformation
danielle hunter
 
PDF
State-Dependent Conformal Perception Bounds for Neuro-Symbolic Verification
Ivan Ruchkin
 
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
Using Google Data Studio (Looker Studio) to Create Effective and Easy Data Re...
Orage Technologies
 
TrustArc Webinar - Navigating Data Privacy in LATAM: Laws, Trends, and Compli...
TrustArc
 
OFFOFFBOX™ – A New Era for African Film | Startup Presentation
ambaicciwalkerbrian
 
Market Insight : ETH Dominance Returns
CIFDAQ
 
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
Brief History of Internet - Early Days of Internet
sutharharshit158
 
Farrell_Programming Logic and Design slides_10e_ch02_PowerPoint.pptx
bashnahara11
 
How ETL Control Logic Keeps Your Pipelines Safe and Reliable.pdf
Stryv Solutions Pvt. Ltd.
 
The Future of AI & Machine Learning.pptx
pritsen4700
 
Agile Chennai 18-19 July 2025 | Workshop - Enhancing Agile Collaboration with...
AgileNetwork
 
Applied-Statistics-Mastering-Data-Driven-Decisions.pptx
parmaryashparmaryash
 
Simple and concise overview about Quantum computing..pptx
mughal641
 
Build with AI and GDG Cloud Bydgoszcz- ADK .pdf
jaroslawgajewski1
 
Researching The Best Chat SDK Providers in 2025
Ray Fields
 
Tea4chat - another LLM Project by Kerem Atam
a0m0rajab1
 
AI Code Generation Risks (Ramkumar Dilli, CIO, Myridius)
Priyanka Aash
 
PCU Keynote at IEEE World Congress on Services 250710.pptx
Ramesh Jain
 
Agentic AI in Healthcare Driving the Next Wave of Digital Transformation
danielle hunter
 
State-Dependent Conformal Perception Bounds for Neuro-Symbolic Verification
Ivan Ruchkin
 

Writing Apps the Google-y Way

  • 1. WRITING APPS THE GOOGLE-Y WAY Pamela Fox, YOW! Australia 2010
  • 2. Who am I? twitter.com/pamelafox [email_address] pamelafox.org you get the idea...
  • 3. Who am I? USC Google Amazon, Flickr, Maps Google Maps API Google Wave API Spreadsheets, Blogger, Youtube, Picasa, Gadgets, App Engine
  • 4. Who am I? Java pYthon
  • 5. What is App Engine? “ Google App Engine enables you to build and host web apps on the same systems that power Google applications.” http://code.google.com/appengine
  • 6. What is a “web app”?
  • 9. Intranet vs. Internet ~2 billion Hundreds - Thousands
  • 10. What is a “web app”?
  • 12. Some Google App Engine web apps www.gifttag.com www.buddypoke.com
  • 13. Google apps on App Engine panoramio.com pubsubhubbub.appspot.com
  • 14. How does App Engine work? You upload application code & resources to Google. Google serves your application from scalable infrastructure. You pay for only the resources that Google used in serving the application.
  • 16. App Engine architecture user or task
  • 19. The tricky bits LIMIT!
  • 20. Datastore Entity Properties Key Entity Entity Entity Entity Path Kind Name/ID
  • 21. Example: Speaker Entities Key Path Kind ID First Name Last Name Speaker1 - Speaker 1 Rod Johnson Key Path Kind ID First Name Last Name Middle Name Suffix Speaker1 - Speaker 2 Guy Steele L Jr.
  • 22. Modeling Speaker Entities class Speaker(db.model): firstname = db.StringProperty(required=True) lastname = db.StringProperty(required=True) middlename = db.StringProperty() namesuffix = db.StringProperty() website = db.StringProperty() keynote = db.BooleanProperty(default=False)
  • 23. Saving Speaker Entities ron = Speaker(firstname=&quot;Ron&quot;, lastname=&quot;Johnson&quot;) guy = Speaker(firstname=&quot;Guy&quot;, lastname=&quot;Steele&quot;, middlename=&quot;L&quot;, namesuffix=&quot;Jr.&quot;) ron.put() guy.put()
  • 24. Updating Speaker Entities ron = Speaker.get_by_id(1) guy = Speaker.get_by_id(2) ron.website = &quot;http://www.ronjohnson.com&quot; ron.keynote = True guy.website = &quot;http://www.guysteele.com&quot; guy.keynote = True db.put(ron, guy) LIMIT!
  • 25. How Updates Happen commit journal apply entities apply indexes A B
  • 26. Queries & Indexes Query Index Index Index Index Query Query Query Query Query
  • 27. Queries & Indexes SELECT * from Speaker ORDER BY lastname LIMIT! key lastname Speaker3 Fox Speaker4 Hohpe Speaker1 Johnson Speaker2 Steele
  • 28. Queries & Indexes SELECT * from Speaker ORDER by middlename key middlename Speaker2 L
  • 29. Queries & Indexes SELECT * from Speaker WHERE keynote = True key keynote Speaker1 True Speaker2 True Speaker3 False Speaker4 False
  • 30. Queries & Indexes SELECT * from Speaker WHERE keynote = False key keynote Speaker1 True Speaker2 True Speaker3 False Speaker4 False
  • 31. Queries allspeakers = Speaker.all().order('lastname) for speaker in allspeakers: print speaker.firstname + '' + speaker.lastname + '' + speaker.website keynotespeakers = Speaker.all().filter('keynote = ', True) notspecialspeakers = Speaker.all().filter('keynote = ', False) LIMIT!
  • 32. Custom Indexes speakers = Speaker.all().order('lastname') .order('keynote') SELECT * from Speaker ORDER BY lastname, keynote key lastname keynote Speaker3 Fox false Speaker4 Hohpe false Speaker1 Johnson true Speaker2 Steele true
  • 33. Custom Indexes speakers = Speaker.all().order('lastname') .filter('keynote =', True) SELECT * from Speaker WHERE lastname > 'Johnson' and keynote = true key lastname keynote Speaker3 Fox false Speaker4 Hohpe false Speaker1 Johnson true Speaker2 Steele true
  • 34. Impossible Indexes SELECT * from Speaker WHERE lastname < 'Steele' and firstname > 'Gregory' ...not in subsequent rows! key lastname firstname Speaker3 Fox Pamela Speaker4 Hohpe Gregory Speaker1 Johnson Ron Speaker2 Steele Guy
  • 35. Impossible Indexes SELECT * from Speaker WHERE lastname > 'Fox' ORDER BY firstname ...not in the correct order! key lastname firstname Speaker3 Fox Pamela Speaker4 Hohpe Gregory Speaker1 Johnson Ron Speaker2 Steele Guy
  • 36. Queries with Offset speakers = Speaker.all().fetch(2, 2) SELECT * from Speaker LIMIT 2 OFFSET 2 1 2 ...slow! LIMIT! key lastname Speaker3 Fox Speaker4 Hohpe Speaker1 Johnson Speaker2 Steele
  • 37. Queries with Cursors query = db.Query(Speaker) speakers = q.fetch(1000) cursor = q.cursor() memcache.set('speaker_cursor', cursor) ... last_cursor = memcache.get('speaker_cursor') q.with_cursor(last_cursor) speakers = q.fetch(1000)
  • 38. More Properties class Talk(db.Model): title = db.StringProperty(required=True) abstract = db.TextProperty(required=True) speaker = db.ReferenceProperty(Speaker) tags = db.StringListProperty() pamela = Speaker.all().filter('firstname = ', 'Pamela').get() talk = Talk('Writing Apps the Googley Way', 'Bla bla bla', pamela, ['App Engine', 'Python']) talk.put() talk = Talk('Wonders of the Onesie', 'Bluh bluh bluh', pamela, ['Pajamas', 'Onesies']) talk.put()
  • 39. Back-References pamela = Speaker.all().filter('firstname = ', 'Pamela').get() for talk in pamela.talk_set: print talk.title SELECT * from Talk WHERE speaker = Speaker3 key speaker Talk6 Speaker2 Talk1 Speaker3 Talk2 Speaker3 Talk5 Speaker4
  • 40. Searching List Properties talks = Talk.all().filter('tags = ', 'python').fetch(10) SELECT * from Talk WHERE tags = 'Python' LIMIT! key lastname Talk1 App Engine Talk2 Pajamas Talk1 Python Talk2 Onesies
  • 41. Entity Groups pamela = Speaker.all().filter('firstname = ', 'Pamela').get() talk1 = Talk('Writing Apps the Googley Way', 'Bla bla bla', pamela, ['App Engine', 'Python'], parent=pamela) talk2 = Talk('Wonders of the Onesie', 'Bluh bluh bluh', pamela, ['Pajamas', 'Onesies'], parent=pamela) db.put(talk1, talk2) def update_talks(): talk1.title = 'Writing Apps the Microsoft Way' talk2.title = 'Wonders of the Windows' db.put(talk1, talk2) db.run_in_transaction(update_talks)
  • 43. Counters 1 2 3 4 5 people have done something.
  • 45. RageTube: Global Stats SongStat yaycount viewcount title artist Key Path Kind Name (song) naycount mehcount
  • 46. RageTube: Global Stats viewcount viewcount viewcount datastore memcache
  • 47. RageTube: Global Stats class Song(db.Model): viewcount = db.IntegerProperty(default=0) title = db.StringProperty() artist = db.StringProperty() def get_viewcount(self): viewcount = self.viewcount cached_viewcount = memcache.get('viewcount-' + self.key().name(), self.key().kind()) if cached_viewcount: viewcount += cached_viewcount return viewcount @classmethod def flush_viewcount(cls, name): song = cls.get_by_key_name(name) value = memcache.get('viewcount-' + name, cls.kind()) memcache.decr('viewcount-' + name, value, cls.kind()) song.viewcount += value song.put() @classmethod def incr_viewcount(cls, name, interval=5, value=1): memcache.incr('viewcount-' + name, value, cls.kind()) interval_num = get_interval_number(datetime.now(), interval) task_name = '-'.join([cls.kind(), name.replace(' ', '-'), 'viewcount', str(interval), str(interval_num)]) deferred.defer(cls.flush_viewcount, name, _name=task_name) LIMIT!
  • 48. Ratings Rated by 500 users.
  • 50. App Gallery: Ratings Comment Application total_ratings sum_ratings avg_rating rated_index comment_count rating
  • 51. App Gallery: Ratings def UpdateAppCommentData(self, rating, operation): def UpdateCommentData(self, rating, operation): self.comment_count += 1 * operation self.sum_ratings += rating * operation self.total_ratings += 1 * operation self.avg_rating = int(round(self.sum_ratings / self.total_ratings)) self.rated_index = '%d:%d:%d' % (self.avg_rating, self.total_ratings, self.index) self.put() db.run_in_transaction(UpdateCommentData, self, rating, operation) app.UpdateAppCommentData(rating, db_models.Comment.ADD) comment = db_models.Comment() comment.application = app comment.rating = rating comment.put() query.order('-avg_rating').order('-rated_index')
  • 53. City-Go-Round: Agencies citygoround.org https://github.com/walkscore/City-Go-Round
  • 54. City-Go-Round: Geo Queries Agency GeoModel location (GeoPt) location_geocells (StringListProperty)
  • 55. City-Go-Round: Geo Queries def fetch_agencies_near(lat, long, bbox_side_in_miles): query = Agency.all() bbox = bbox_centered_at(lat, long, bbox_side_in_miles) return Agency.bounding_box_fetch(query, bbox, max_results = 50) def bounding_box_fetch(query, bbox, max_results=1000,): results = [] query_geocells = geocell.best_bbox_search_cells(bbox) for entity in query.filter('location_geocells IN', query_geocells): if len(results) == max_results: break if (entity.location.lat >= bbox.south and entity.location.lat <= bbox.north and entity.location.lon >= bbox.west and entity.location.lon <= bbox.east): results.append(entity) return results
  • 57. City-Go-Round: Geo Queries TransitAppLocation GeoModel location (GeoPt) location_geocells (StringListProperty) TransitApp
  • 58. City-Go-Round: Geo Queries class TransitAppLocation(GeoModel): transit_app = db.ReferenceProperty(TransitApp) def fetch_transit_app_locations_near(lat, longi): query = TransitAppLocation.all() bbox = bbox_centered_at(lat, long, bbox_side_in_miles) return TransitAppLocation.bounding_box_fetch(query, bounding_box, max_results = 500) def fetch_transit_apps_near(lat, long): transit_app_locations = TransitAppLocation.fetch_transit_app_locations_near(lat, long)] transit_apps = [transit_app_location.transit_app for transit_app_location in transit_app_locations] return transit_apps
  • 59. Full Text Search pizza Search Thingy It's like pizza, but in the cloud. Other Thingy This will make you smell as delicious as pizza.
  • 61. Disclosed.ca: Search Contract agency_name vendor_name description comments uri
  • 62. Disclosed.ca: Search from search.core import SearchIndexProperty, porter_stemmer class Contract(db.Model): uri = db.StringProperty(required=True) agency_name = db.StringProperty(required=True) vendor_name = db.StringProperty(required=True) description = db.StringProperty() comments = db.TextProperty() search_index = SearchIndexProperty(('agency_name', 'vendor_name', 'description', 'comments'), indexer=porter_stemmer) results = Contract.search_index.search(sheep').fetch(20)
  • 63. Disclosed.ca: Search Contract agency_name vendor_name description comments uri search_index (StringListProperty) SearchIndex
  • 64. Disclosed.ca: Search SELECT FROM ContractSearch WHERE search_index = &quot;sheep&quot; key search_index ContractSearch1 charter ContractSearch1 june ContractSearch1 sheep ContractSearch2 sheep ContractSearch1 wood
  • 65. More Learning http://ae-book.appspot.com http://code.google.com/appengine http://blog.notdot.net/
  • 66. AppEngine: Now & Later &quot;Run your web apps on Google's infrastructure. Easy to build, easy to maintain, easy to scale.&quot; Roadmap: Background processes > 30s MapReduce Bulk Import/Export Channel API App Engine for Business: SQL Other stuff..
  • 67. Thanks for coming! *I am never very good at conclusions, so this slide is a subtle notice to all of you that I am done talking now.

Editor's Notes

  • #16: *This is not actually an App Engine site. But for the sake of demonstration and having something to talk about that you guys are familiar with, let’s use it as an example It has data, like each speaker and their talks, and it has users that want to register for the conference.
  • #17: App Engine gets request from client OR from cron Figures out what app its mapping to Decides if request corresponds to static or dynamic content If static: Serves file from static servers. Cache, faster, latency. Otherwise: Selects a server that will respond the fastest Fires up app, sends the request, gets the response, gives response to user
  • #18: Runs either Python interpreter or JVM 6 Doesn’t retain state (like global variables) Can read its own files, can’t write any files or read other app’s files Can’t access networking facilities or hardware Doesn’t expose OS details
  • #19: Run multiple apps from same hardware Limit clock time, CPU usage, memory usage of apps Gives each app 30 seconds to respond to each  Enforces isolation
  • #21: Similar to an object store / object database. A datastore is made up of entities, and each entity has a kind, a key, and properties. The key is unique for each entity, is set on creation and never changed. It provides a fast way to retrive that entity. The kind is used mostly when querying the datastore, as most queries only returns results of a particular kind. The properties can vary for each entity of a kind – the underlying datastore is schemaless. You’ll often use the API to enforce a schema, for better application logic, however. Properties are optional; you don’t have to have any at all.
  • #22: We could actually have more properties on Guy to store the additional parts of his name. App Engine would have no problems with this.
  • #23: But we usually like to enforce schema in code, like so. When we save them to the datastore using put(), the datastore auto assigns a key. We could also set the keys ourself, but we have to make sure they are unique.
  • #24: But we usually like to enforce schema in code, like so. When we save them to the datastore using put(), the datastore auto assigns a key. We could also set the keys ourself, but we have to make sure they are unique.
  • #25: We know the key_name of the entity, as we specified it when we created it. That isn&apos;t the same as the full key, however. Here we do a batch put() to save in time. Subject to limit in size/number of entities.
  • #26: This is a transaction! By default, only one entity is in a transaction at a time. We&apos;ll see how later how to have multiple in a transaction.
  • #27: In App Engine, every query must be answered by an existing index or it will return an error. So it must know ahead of time the types of questions that you will ask. App Engine doesn&apos;t has a weak query engine compared to other DBs. Those other DBs don&apos;t perform at web speeds with large amounts of data spread across multiple machines, however.  Let&apos;s look at some example queries and indexes.
  • #28: When performing query, finds the index, finds the first matching row, returns entities until first not-matching row.
  • #29: Filtering Or Sorting On a Property Requires That the Property Exists A query filter condition or sort order for a property also implies a condition that the entity have a value for the property. A datastore entity is not required to have a value for a property that other entities of the same kind have. A filter on a property can only match an entity with a value for the property. Entities without a value for a property used in a filter or sort order are omitted from the index built for the query.
  • #30: When performing query, finds the index, finds the first matching row, returns entities until first not-matching row.
  • #31: This index can also answer this query.
  • #32: App Engine will always return either the full entities or the keys only, but never partial entities. The size of the result set is subject to a limit, so you need to be careful not to put too much information in one entity. You can spread info across multiple entities for an object if necessary.
  • #33: - Needs custom ones because building every possible index would take huge amount of space/time, and an app won&apos;t use most of them, and more indexes means slower entity updates. - These queries require custom indexes: query with multiple sort orders, query with inequality filter on a property
  • #34: Needs custom ones because building every possible index would take huge amount of space/time, and an app won&apos;t use most of them, and more indexes means slower entity updates. - These queries require custom indexes: queries with multiple sort orders queries with a sort order on keys in descending order queries with one or more inequality filters on a property and one or more equality filters over other properties queries with inequality filters and ancestor filters
  • #35: Inequality Filters Are Allowed On One Property Only A query may only use inequality filters (&lt;, &lt;=, &gt;=, &gt;, !=) on one property across all of its filters. This makes geo queries difficult as they typically compare lat and lon in same query.
  • #36: Properties In Inequality Filters Must Be Sorted Before Other Sort Orders If a query has both a filter with an inequality comparison and one or more sort orders, the query must include a sort order for the property used in the inequality, and the sort order must appear  before  sort orders on other properties. This query is  not  valid, because it uses an inequality filter and does not order by the filtered property: SELECT * FROM Person WHERE birth_year &gt;= :min_year ORDER BY last_name # ERROR Similarly, this query is not valid because it does not order by the filtered property before ordering by other properties: SELECT * FROM Person WHERE birth_year &gt;= :min_year ORDER BY last_name, birth_year # ERROR This query is valid: SELECT * FROM Person WHERE birth_year &gt;= :min_year ORDER BY birth_year, last_name To get all results that match an inequality filter, a query scans the index table for the first matching row, then returns all consecutive results until it finds a row that doesn&apos;t match. For the consecutive rows to represent the complete result set, the rows must be ordered by the inequality filter before other sort orders.
  • #37: You can specify an offset, but it is slow. It will still have to go to the first result, then count until it gets the one you want. Limit of 1000 as offset. For large datastore sets, this is not a good way to paginate.
  • #38: Query cursors allow an app to perform a query and retrieve a batch of results, then fetch additional results for the same query in a subsequent web request without the overhead of a query offset. After the app fetches some results for a query, it can ask for an encoded string that represents the location in the result set after the last result fetched (the &amp;quot;cursor&amp;quot;). The app can use the cursor to fetch additional results starting from that point at a later time. A cursor is a base64-encoded string that represents the next starting position of a query after a fetch operation. The app can store the cursor in the datastore or memcache, or in a task queue task payload. A future request handler can perform the same query and include the cursor with the query to tell the datastore to start returning results from the location represented by the cursor. A cursor can only be used by the app that performed the original query, and can only be used to continue the same query.
  • #39: Query cursors allow an app to perform a query and retrieve a batch of results, then fetch additional results for the same query in a subsequent web request without the overhead of a query offset. After the app fetches some results for a query, it can ask for an encoded string that represents the location in the result set after the last result fetched (the &amp;quot;cursor&amp;quot;). The app can use the cursor to fetch additional results starting from that point at a later time. A cursor is a base64-encoded string that represents the next starting position of a query after a fetch operation. The app can store the cursor in the datastore or memcache, or in a task queue task payload. A future request handler can perform the same query and include the cursor with the query to tell the datastore to start returning results from the location represented by the cursor. A cursor can only be used by the app that performed the original query, and can only be used to continue the same query.
  • #40: Query cursors allow an app to perform a query and retrieve a batch of results, then fetch additional results for the same query in a subsequent web request without the overhead of a query offset. After the app fetches some results for a query, it can ask for an encoded string that represents the location in the result set after the last result fetched (the &amp;quot;cursor&amp;quot;). The app can use the cursor to fetch additional results starting from that point at a later time. A cursor is a base64-encoded string that represents the next starting position of a query after a fetch operation. The app can store the cursor in the datastore or memcache, or in a task queue task payload. A future request handler can perform the same query and include the cursor with the query to tell the datastore to start returning results from the location represented by the cursor. A cursor can only be used by the app that performed the original query, and can only be used to continue the same query.
  • #41: Query cursors allow an app to perform a query and retrieve a batch of results, then fetch additional results for the same query in a subsequent web request without the overhead of a query offset. After the app fetches some results for a query, it can ask for an encoded string that represents the location in the result set after the last result fetched (the &amp;quot;cursor&amp;quot;). The app can use the cursor to fetch additional results starting from that point at a later time. A cursor is a base64-encoded string that represents the next starting position of a query after a fetch operation. The app can store the cursor in the datastore or memcache, or in a task queue task payload. A future request handler can perform the same query and include the cursor with the query to tell the datastore to start returning results from the location represented by the cursor. A cursor can only be used by the app that performed the original query, and can only be used to continue the same query.
  • #47: http://blog.notdot.net/2010/04/High-concurrency-counters-without-sharding We could do sharding, but that&apos;s a lot of work, and it takes time to add up the counter.
  • #49: ..and search
  • #50: http://code.google.com/appengine/docs/python/datastore/transactions.html http://www.google.com/codesearch/p?hl=en#e-58ZvqBkF0/trunk/samples-gallery/projectgallery.py&amp;q=rated_index%20lang:py%20appengine&amp;sa=N&amp;cd=2&amp;ct=rc http://google-wave-resources.googlecode.com/svn/trunk/samples-gallery/db_models.py
  • #67: App Engine&apos;s motto is that its easy to build, easy to maintain, and easy to scale. I think you may find that parts of your app are harder to build on App Engine than on other platforms, because you&apos;re not used to doing things the scalable App Engine way, and you&apos;ll have to do some rethinking. But, once you do start thinking that way, and perhaps also experiment with other scalable platforms, you should find it easier and easier. App Engine is also continuing to come out with features to enable developers to do more in their webapps, and also to make webapps for different audiences. So, try it out, see how you like it, and keep it in mind for your next project.