惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Know Your Adversary
Know Your Adversary
小众软件
小众软件
L
LangChain Blog
月光博客
月光博客
博客园 - Franky
Microsoft Azure Blog
Microsoft Azure Blog
Y
Y Combinator Blog
有赞技术团队
有赞技术团队
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
MongoDB | Blog
MongoDB | Blog
Recorded Future
Recorded Future
V
Visual Studio Blog
TaoSecurity Blog
TaoSecurity Blog
S
Schneier on Security
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
D
DataBreaches.Net
L
LINUX DO - 热门话题
C
Check Point Blog
F
Fortinet All Blogs
Hugging Face - Blog
Hugging Face - Blog
The Hacker News
The Hacker News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Microsoft Security Blog
Microsoft Security Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
V
V2EX
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
P
Proofpoint News Feed
L
Lohrmann on Cybersecurity
博客园 - 司徒正美
T
Threatpost
P
Palo Alto Networks Blog
A
About on SuperTechFans
Spread Privacy
Spread Privacy
Engineering at Meta
Engineering at Meta
N
News | PayPal Newsroom
T
Tailwind CSS Blog
The Last Watchdog
The Last Watchdog
Blog — PlanetScale
Blog — PlanetScale
A
Arctic Wolf
量子位
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 聂微东
Google Online Security Blog
Google Online Security Blog
Google DeepMind News
Google DeepMind News
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Vulnerabilities – Threatpost
H
Hacker News: Front Page

Google App Engine

给还没能在 Windows 登录 Antigravity 的同学多一条路试试 - V2EX Google Map 评价不了商铺 - V2EX 关于 GCP 机器多网卡、多公网 IP 配置,急急急. - V2EX 小网站搭在 GAE + CloudFlare 上 - V2EX GAE 3 月底还没绑定账单,实例就会被停用 - V2EX 貌似不挂代理也能進谷歌云 - V2EX 节点名是不是应该改成 GCP 了? - V2EX Google Calendar 开发 - V2EX 1 月 19 日深圳活动预告 如何利用 Google Analytics 搭建 (Google )最个性化转化漏斗 - V2EX 谷歌云 GCP 还是送 300 刀么? - V2EX Google cloud 外网 IP 问题 google 账号找回! - V2EX Google 文本转语音的轮子 谁搞过? - V2EX V2EX 源码必须用 Google APP Engine 吗,我下了源码想用户自己小区内平台上,怎么能手机访问? - V2EX 谁有 Google Play 开发者帐号违规的经验? - V2EX [请教] 上传应该到 google play,状态为发布成功,但是设备目录一片灰,无法下载 - V2EX 你们服务器都是选的哪个地区,速度如何? - V2EX GAE 骨灰实例是不是已经凉透了 - V2EX 你们有没有收到过这样的邮件? - V2EX GAE 部署出错(求助!) - V2EX 谷歌 cdn, google cloud cdn 设置 https 的访问请求。 - V2EX Google Cloud Platform 自定义防火墙无效,只有 443, 80, 22 等几个端口能用。 - V2EX Google App Engine Standard 增加 Java 8 beta 支持 - V2EX 有人的 G Suite 地区设置在台湾的吗?请教个问题 - V2EX Google cloud 外网 ip 问题 Google Cloud 是不是被强了?一大早的连不上 - V2EX GOOGLE COLUD 用 HTTP 负载均衡的 CDN SESSION 丢失问题 - V2EX Google VM 推荐下,台湾节点速度很好,就是流量小贵。 - V2EX 为什么有人反向代理 google 搜索,却不能反向 gmail? - V2EX Google Apps 正式更名为 G Suite - V2EX 江苏电信,已可自由访问 gmail 和 google now - V2EX 在 GAE 上部署更新时改动被无视了怎么破? - V2EX https://appengine.google.com 现在还能免费申请吗? - V2EX GAE 有没有用 PHP 开发一些小程序的(另附 PHP 探针一枚以及替代 MYSQL 的方案) - V2EX 如何测试通过代理访问的延时? - V2EX 请问现在想找支持 gae 的开源程序应该去哪里呢? - V2EX 不同的 app 消息推送的服务各自有什么优劣? - V2EX 如何彻底卸载 chrome? - V2EX google cloud 的 sql 不同地区怎么 ping 起来都是 0.2ms - V2EX Google App Engine 无法实现官方 python 教程 helloworld 的 deploy,希望大家帮忙 - V2EX Google 与 AppScale 合作贡献代码给 App Engine 开源实现 - V2EX V2EX 今天 GAE 突然变得很不稳定 - V2EX V2EX GAE 还能用吗?我没法提交代码了 - V2EX App Engine 支持 PHP 5.5 运行时,添加 curl 支持 - V2EX V2EX V2EX
最近将自己的一个小 app 迁移到了 GAE,说说感想和经验。 - V2EX
geeklian · 2015-06-21 · via Google App Engine

先说说感想

心里只有一万个WTF,一万个草泥马。

GAE的Datastore收费方法简直令人恶心,一个App的开发过程中,想着怎么优化write/read Op,浪费的时间和精力,你定可以转换为很多创新点子...特别是对于一个不存在code review的个人App来说。

如果app自用且足够小,放到GAE上追求个稳定,还是可以接受的,但理由也仅限如此了...

当初冲动的原因

本来是一个py3+Django写的小程序,跑在我的Linode JP VPS上。在优化扶墙的过程中,想将debian8换成debian7,然后想想为了未来省事,感觉把几个app移到gae上去吧,以后折腾vps也无所谓了。

软件成品在此,实现也很简单,每五分钟抓一些feed,然后jieba分词,然后推送消息...已经超免费配额了,大家随便看看就好,我不是职业写代码的,也就不敢开源出来献丑了。

坑和填坑

  • CPU:600 Mhz, 内存:128 MB, 28个执行小时。

    • 占用过多的cpu和内存,会导致你的程序跑不满24小时就额度用尽。
    • 当CPU占用高时,GAE会再起新Instance来应对新的请求,多个Instance多倍扣CPU时间。
    • 当内存超限时,app可能直接被stop,不会死掉。
    • 这对于jieba分词简直是灾难,在不修改代码的情况下,jieba初始化时就内存超限了,并且初始化词库需要高达12秒。
  • 5万次数据库读作业,5万次写作业。

    • 大坑!这里要特别注意索引,可能倍翻你的配额用量。
  • 不支持Python3:处理中文,要花费大量时间在utf-8上。

  • 不支持tempfile:很多库需要改造。

  • 免费配额不支持socket:无法使用外部数据库。收费配额皆不可socket listen。

相比之下,其他的配额对应用的影响微不足道:Memcache是免费的。UrlFetch除非抓来的数据不做任何处理,Mail除非用来滥发邮件。

NDB配额优化

GAE的数据库额度存在3个关键:

  1. 激活账单后,Small Op目前是免费的不限量的,keys_only=True可以随便用。
  2. get()和get_multi()查询会被自动memcache。
  3. indexed会倍增write Op

提取单条数据,使用get_by_key_name(),而不是fetch(1) / first()

user = User.query(User.username = "tom").first()

替换为

user = User.get_by_key_name("tom")

原方法会消耗1 Fetch Op + 1 Query Op = 2 read Op,修改后,会产生1 Small Op + 1 read Op,而且这个read Op会被自动memcache。

提取多条数据时,使用keys_only + get_multi()

比如一个表有,我想一次取出N条数据时,常规ORM的写法:

feeds = Feed.query().fetch(N)

每次查询,都会消耗1+N Read Op,为了优化额度,可以修改成:

q = Feed.query()
feeds = ndb.get_multi(q.fetch(N,keys_only=True))

首次查询,消耗1 Small Op + N Read Op,但是在重复查询是,则只消耗1 Small Op + m*N Read Op,m是memcache未命中的概率,理想情况是0。

至于性能,可以参看这里,大概75%缓存命中是性能的分界线。

Memcache hit ratio: 100% (everything was in cache)

  Query for entities:              3755 ms
  Query/memcache/ndb:              3239 ms
    Keys-only query:       834 ms
    Memcache.get_multi:   2387 ms
    ndb.get_mutli:           0 ms

Memcache hit ratio: 75%

  Query for entities:              3847 ms
  Query/memcache/ndb:              3928 ms
    Keys-only query:       859 ms
    Memcache.get_multi:   1564 ms
    ndb.get_mutli:        1491 ms

Memcache hit ratio: 50%

  Query for entities:              3507 ms
  Query/memcache/ndb:              5170 ms
    Keys-only query:       825 ms
    Memcache.get_multi:   1061 ms
    ndb.get_mutli:        3168 ms

Memcache hit ratio: 25%

  Query for entities:              3799 ms
  Query/memcache/ndb:              6335 ms
    Keys-only query:       835 ms
    Memcache.get_multi:    486 ms
    ndb.get_mutli:        4875 ms

Memcache hit ratio: 0% (no memcache hits)

  Query for entities:              3828 ms
  Query/memcache/ndb:              8866 ms
    Keys-only query:       836 ms
    Memcache.get_multi:     13 ms
    ndb.get_mutli:        8012 ms

尽可能的禁用索引。

  • 为所有不需要的被query()和order()的字段,使用indexed=False

  • 当你插入一条数据时,每个索引字段都会产生write Op,特别当操作对象是ListProperty,会根据list的数量,倍数消耗写配额。

  • 对于一些查询,有些和实际逻辑需求相左,但能大幅节约Op的手段。。

    class EntryCollect(ndb.Model):
        apublished = ndb.DateTimeProperty()
        need_collect_word = ndb.BooleanProperty(default=True, indexed=False)
        key_word = ndb.StringProperty(repeated=True, indexed=False)
    

对于原先是in(List)的查询:

keys = EntryCollect.query().order(-EntryCollect.published)
entrys = ndb.get_multi(keys.fetch(PER_PAGE*2, keys_only=True))
new_entry = []
for entry in entrys:
    if keyword.decode('utf-8') in entry.key_word:
        new_entry.append(entry)

对于原先是list.IN(other_list)的查询:

keys = EntryCollect.query().order(-EntryCollect.published)
entrys = ndb.get_multi(keys.fetch(PER_PAGE*2, keys_only=True))
top_entry = []
for entry in entrys:
    if set(other_list).intersection(set(entry.key_word)):
        top_entry.append(entry)

对于原先是Boolean的字段:

keys = EntryCollect.query().order(-EntryCollect.published)
entrys = ndb.get_multi(kesy.fetch(CONT*2, keys_only=True))
for entry in entrys:
    if entry.need_collect_word:
        # do something

projected()的利弊权衡

  • 使用projected()的字段,必须被indexed。
  • 使用projected()的查询,算一次small Op。

这里就有个权衡,如果read Op紧张,write Op富裕,那么就可以使用projected()。

绞尽脑汁使用Memcache

  • Memcache是免费的! Memcache是免费的! 这个必须说两遍,Query太贵了。
  • Query.get()会自动被缓存。
  • 将查询的参数作为key,取md5,查询的结果用json存储起来。

    json_data = memcache.get('{}:XXXXXXX'.format(md5sum))
    if json_data is None:
        # do something....
        json_data = json.dumps(data)
        memcache.add('{}:Analyse'.format(md5sum), json_data, MEMCACHE_TIMEOUT)
    

TextProperty 和 StringProperty的区别

  • 在管理后台,你无法添加TextProperty的字段,StringProperty可以。
  • TextProperty无法生成索引,StringProperty可以。
  • StringProperty的最大长度是 1500 bytes。

拆分App

一个App拆分成多个App,是最简单的,倍翻利用app engine的方法。

应用间通信,使用什么格式最效率?

根据我自己的测试结果:

marshal取胜,而且处理utf-8更简便一些,但切记marshal不能用于两个不同版本的python之间序列化数据,不适用于开放的api。

如果使用json,要随时注意编码:

form_fields = {
        "something": self.request.get("something", default_value="").encode("utf-8"),
    }
    form_data = urllib.urlencode(form_fields)
    result = urlfetch.fetch(url=SOME_URL,
                            payload=form_data,
                            method=urlfetch.POST,
                            follow_redirects=False, 
                            headers={'Content-Type': 'application/x-www-form-urlencoded'})
    self.response.headers['Content-Type'] = 'application/json'
    self.response.out.write(result.content)

节省各种配额

  • 在一个Instance内,不管cpu占有率高低,cpu time都一样计费。
  • 删除数据库也占用write Op,没用的资源尽早删除。
  • 绞尽脑汁优化内存和cpu使用。

节省CPU配额:使用asynchronous urlfetch

为节约网络延迟而浪费的cputime,使用异步urlfetch就十分重要。 官方手册在这里,例如:在抓取多个feed时:

q = Feed.query()
results = ndb.get_multi(q.fetch(keys_only=True))

rpcs = []
for f in results:
    rpc = urlfetch.create_rpc()
    urlfetch.make_fetch_call(rpc, f.url)
    rpcs.append(rpc)

for rpc in rpcs:
    rpc.wait()
    result = rpc.get_result()
    d = feedparser.parse(result.content)
    for e in d['entries']:
    # do something....

节省CPU配额:需要初始化的资源,在本地进行序列化,GAE上直接读取序列化的资源。

以jieba词库为例:默认情况,jieba每次初始化,都会将本地词库dict.txt进行readline操作,生成字典,这个过程在GAE默认的CPU上需要将近6秒。先将这个字典在本地使用marshal.dump,在GAE中在load,初始化阶段则只消耗1.x秒。

try:
        with open(cache_file, 'rb') as cf:
            object_a, object_b = marshal.load(cf)
    except :
        for line in open(dict, 'rb').read().decode('utf-8').splitlines():
            # do something....
        with open(cache_file, 'wb') as cf:
            marshal.dump((object_a, object_b), cf)

节省CPU配额:不使用memcache,如何缓存一个页面

能省则省,虽然memcache免费的,但还想省掉cpu怎么办?

self.response.headers['Cache-Control'] = 'public, max-age:300' 
self.response.headers['Pragma'] = 'Public'

资源优化:删掉过时的数据

节约数据库存储空间最简单的方法,就是删掉过时的数据,而对于ndb,不存在Object.query().del() 这样的方法,需要使用:

earliest = datetime.datetime.now() - datetime.timedelta(days=10)
keys = EntryCollect.query(EntryCollect.published <= earliest).fetch(keys_only=True)
ndb.delete_multi(keys)

资源优化:使用robots.txt

减少搜索引擎对app的负载,不失为一个办法,一个个位数pv的app,被bot拖到配额超限真的好23333...

后记

然后?然后就没有然后了...
我用一个周末django写的app,用了2个周末迁移到gae上,跟配额,特别是Datastore write/read Op奋斗了2个星期,经验写出来,希望同样蛋痛的V友们少走弯路。

本人不是职业程序员,金融从业者,希望少拍代码砖=.=