Introducing 

Prezi AI.

Your new presentation assistant.

Refine, enhance, and tailor your content, source relevant images, and edit visuals quicker than ever before.

Loading…
Transcript

Google App Engine Anti Patterns

2010-02-12

Takashi Matsuo

App Engine Ja Night #5

松尾 貴史

@tmatsuo

サイオステクノロジー株式会社

株式会社キャンディット

Google App Engine API Expert

Tokyo GTUG Organizer

Kay's daddy

Kay でも gtx

class Tag(db.Model):

name = db.StringProperty()

count= db.IntegerProperty()

class Post(db.Model):

tags = db.ListProperty(db.Key)

body = db.TextProperty(required=True)

created = db.DateTimeProperty(auto_now_add=True)

Post.tags stores keys of Tags

tags = []

for tag_name in tag_input.split():

tags.append(Tag.get_or_insert(key_name=tag_name))

post = Post(tags=[t.key() t in tags], body=body)

post.put()

t = Tag.get_by_key_name(tag_input)

if t is None:

raise Http404

posts = Post.all().filter("tags =", t.key()).fetch(200)

Redundant db operation

class Tag(db.Model):

name = db.StringProperty()

count= db.IntegerProperty()

class Post(db.Model):

tags = db.StringListProperty()

body = db.TextProperty(required=True)

created = db.DateTimeProperty(auto_now_add=True)

Post.tags stores Tags themself as string

tags = tag_input.split()

for tag_name in tags:

Tag.get_or_insert(key_name=tag_name)

post = Post(tags=tags, body=body)

post.put()

posts = Post.all().filter("tag =", tag_input).fetch(200)

Simple and fast

検索する場合は冗長に持とう

class Foo(db.Model):

prop1 = db.StringProperty()

prop2 = db.StringProperty()

prop3 = db.StringProperty()

prop4 = db.StringProperty()

prop5 = db.StringProperty()

prop6 = db.StringProperty()

class Bar(db.Model):

prop1 = db.StringProperty(indexed=False)

prop2 = db.StringProperty(indexed=False)

prop3 = db.StringProperty(indexed=False)

prop4 = db.StringProperty(indexed=False)

prop5 = db.StringProperty(indexed=False)

prop6 = db.StringProperty(indexed=False)

class Baz(db.Model):

prop1 = db.StringProperty(name="p1")

prop2 = db.StringProperty(name="p2")

prop3 = db.StringProperty(name="p3")

prop4 = db.StringProperty(name="p4")

prop5 = db.StringProperty(name="p5")

prop6 = db.StringProperty(name="p6")

@classmethod

def kind(cls):

return "z"

検索やソートしないならインデックスを作らない

q = MyModel.all().filter("contents =", "word1").\

filter("contents =", "word2").\

filter("contents =", "word3").\

order("-created")

entries = q.fetch(10)

q = MyModel.all().filter("contents =", "word1").\

filter("contents =", "word2").\

filter("contents =", "word3").\

filter("created_month =", this_month)

# TODO: This might be not enough

entries = q.fetch(1000)

# Sorting on memory

entries.sort(cmp=lambda x, y: cmp(x.created, y.created))

ソートもインデックス使うので気を付けましょう

# -*- coding: utf-8 -*-

from kay.utils import render_to_response

import hoge_utils

import fuga_utils

import moge_utils

def index(request):

return render_to_response('myapp/index.html', {'message': 'Hello'})

def hoge(request):

entries = hoge_utils.get_entries()

return render_to_response('myapp/hoge.html', {entries: entries})

def fuga(request):

entries = fuga_utils.get_entries()

return render_to_response('myapp/fuga.html', {entries: entries})

def moge(request):

entries = moge_utils.get_entries()

return render_to_response('myapp/moge.html', {entries: entries})

# -*- coding: utf-8 -*-

from kay.utils import render_to_response

def index(request):

return render_to_response('myapp/index.html', {'message': 'Hello'})

def hoge(request):

import hoge_utils

entries = hoge_utils.get_entries()

return render_to_response('myapp/hoge.html', {entries: entries})

def fuga(request):

import fuga_utils

entries = fuga_utils.get_entries()

return render_to_response('myapp/fuga.html', {entries: entries})

def moge(request):

import moge_utils

entries = moge_utils.get_entries()

return render_to_response('myapp/moge.html', {entries: entries})

一部でしか使わないモジュールは遅延ロードする

ユーザー新規登録→確認メール→クリックで完了

def create_temp_session(use_name):

import datetime

from google.appengine.api.labs import taskqueue

def txn():

salt = crypto.gen_salt()

activation_key = crypto.sha1(salt+user_name).hexdigest()

expiration_date = datetime.datetime.now() + \

datetime.timedelta(seconds=3600) # 一時間

taskqueue.add(url='/expire_registration',

registration_key=str(activation_key)),

eta=expiration_date, transactional=True)

session = TmpSession(key_name=activation_key)

db.put(session)

return session

session = db.run_in_transaction(txn)

return session

Paging に使えそう

# -*- coding: utf-8 -*-

# myapp.views

from myapp.models import MyModel

from kay.utils import render_to_response

PAGESIZE=10

def index(request, cursor=None):

q = MyModel.all()

if cursor:

q = q.with_cursor(cursor)

ms = q.fetch(PAGESIZE)

cursor = q.cursor()

if MyModel.all(cursor=cursor).get() is None:

# no more entities

cursor = None

return render_to_response('myapp/index.html',

{'ms': ms, 'next': cursor})

fetch + get しなきゃいけない?

# -*- coding: utf-8 -*-

# myapp.views

import logging

import base64

import os

from myapp.models import MyModel

from kay.utils import render_to_response

PAGESIZE=10

def index(request, cursor=None):

q = MyModel.all()

if cursor:

q = q.with_cursor(cursor)

ms = q.fetch(PAGESIZE+1)

if len(ms) == PAGESIZE+1:

raw_cursor = q._last_raw_query.GetCompiledCursor()

for p in raw_cursor.position_:

p.set_start_inclusive("true")

nextcursor = base64.urlsafe_b64encode(raw_cursor.Encode())

else:

nextcursor = None

return render_to_response('myapp/index.html',

{'ms': ms[:PAGESIZE],

'next': nextcursor})

Nick says "Be cautious"

ページングにはもう一捻り必要ぽい

RPCのタイムライン

簡単に使える

handlers:

...

...

- url: /stats.*

script: $PYTHON_LIB/google/appengine/ext/appstats/ui.py

- url: /.*

script: kay/main.py

普段のハンドラの上に書きましょう。

DjangoとかKayだとこれだけ

MIDDLEWARE_CLASSES = (

'google.appengine.ext.appstats.recording.AppStatsDjangoMiddleware',

)

webappでも大丈夫

1. Find the code that runs your application object. It most likely uses either 'util.run_wsgi_app()' (where 'util' is a submodule of the 'google.appengine.ext.webapp' package), or 'wsgiref.handlers.CGIHandler().run()'. If the former, you're ready for step 2.

Otherwise, you must insert:

from google.appengine.ext.webapp import util

at the top of the file (with the other imports) and replace the call to 'wsgiref.handlers.CGIHandler().run(app)' with a call to:

util.run_wsgi_app(app)

NOTE: If you have multiple entry points (top-level or "main" scripts), you must do this for each script.

2. Create a file named appengine_config.py in your app's root directory and add the following code to it:

def webapp_add_wsgi_middleware(app):

from appstats import recording

app = recording.appstats_wsgi_middleware(app)

return app

Thank you

SDK-1.3.1 の新機能

Transactional Task Queue

Cursor

Paging -お薦めしない

Paging

自動で消える一時セッション

Custom Admin Page

Appstats

Adminコンソールに機能追加できる

app.yaml

admin_console:

pages:

- url: /stats

name: "Stats"

クリックすると

app.yaml

settings.py

Anti Patterns

Who am I?

正規化のし過ぎ

不要なインデックス

Model - after

Tagging - after

Model - before

Tagging - before

Logic - Put

Model

Logic - Search

Model - おまけ

Greedy module loading

Exploding Index

Query - after

Query - before

Views - after

Views - before

http://code.google.com/p/kay-framework/

# -*- coding: utf-8 -*-

"""

Copyright (c) 2009 by Takashi Matsuo <tmatsuo@candit.jp>

All rights reserved.

Redistribution and use in source and binary forms, with or without

modification, are permitted provided that the following conditions are

met:

* Redistributions of source code must retain the above copyright

notice, this list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above

copyright notice, this list of conditions and the following

disclaimer in the documentation and/or other materials provided

with the distribution.

* The names of the contributors may not be used to endorse or

promote products derived from this software without specific

prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS

"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT

LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR

A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT

OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,

SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT

LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,

DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY

THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT

(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE

OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

"""

# TODO: slim3 wo pakuru

Learn more about creating dynamic, engaging presentations with Prezi