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

推荐订阅源

H
Help Net Security
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
Google DeepMind News
Google DeepMind News
Apple Machine Learning Research
Apple Machine Learning Research
有赞技术团队
有赞技术团队
Y
Y Combinator Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
爱范儿
爱范儿
L
LangChain Blog
IT之家
IT之家
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog
Hugging Face - Blog
Hugging Face - Blog
G
Google Developers Blog
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
宝玉的分享
宝玉的分享
博客园 - 三生石上(FineUI控件)
D
DataBreaches.Net
Recent Announcements
Recent Announcements
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

Google App Engine

给还没能在 Windows 登录 Antigravity 的同学多一条路试试 Google Map 评价不了商铺 关于 GCP 机器多网卡、多公网 IP 配置,急急急. 小网站搭在 GAE + CloudFlare 上 GAE 3 月底还没绑定账单,实例就会被停用 貌似不挂代理也能進谷歌云 节点名是不是应该改成 GCP 了? Google Calendar 开发 1 月 19 日深圳活动预告 如何利用 Google Analytics 搭建 (Google )最个性化转化漏斗 谷歌云 GCP 还是送 300 刀么? Google cloud 外网 IP 问题 google 账号找回! Google 文本转语音的轮子 谁搞过? V2EX 源码必须用 Google APP Engine 吗,我下了源码想用户自己小区内平台上,怎么能手机访问? 谁有 Google Play 开发者帐号违规的经验? [请教] 上传应该到 google play,状态为发布成功,但是设备目录一片灰,无法下载 你们服务器都是选的哪个地区,速度如何? GAE 骨灰实例是不是已经凉透了 你们有没有收到过这样的邮件? GAE 部署出错(求助!) 谷歌 cdn, google cloud cdn 设置 https 的访问请求。 Google Cloud Platform 自定义防火墙无效,只有 443, 80, 22 等几个端口能用。 Google App Engine Standard 增加 Java 8 beta 支持 有人的 G Suite 地区设置在台湾的吗?请教个问题 Google cloud 外网 ip 问题 Google Cloud 是不是被强了?一大早的连不上 GOOGLE COLUD 用 HTTP 负载均衡的 CDN SESSION 丢失问题 Google VM 推荐下,台湾节点速度很好,就是流量小贵。 为什么有人反向代理 google 搜索,却不能反向 gmail? Google Apps 正式更名为 G Suite
最近将自己的一个小 app 迁移到了 GAE,说说感想和经验。
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分词,然后推送消息...已经超免费配额了,大家随便看看就好,我不是职业写代码的,也就不敢开源出来献丑了。

坑和填坑

免费的GAE配额,以及一些坑

  • 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友们少走弯路。

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