Эффективная работа с глобальными счетчиками
Оригинал статьи – http://blog.appenginefan.com/2008/06/efficient-global-counters.html
Одним из примеров, как не нужно работать с глобальными счетчиками – тот наивный подход, который я использовал в прошлом (сохранять счетчик в объекте и обновлять его в транзакции) является бутылочным горлышком, который реально уменьшает производительность приложения. Вместо этого рекомендуется использовать механизм по разделению счетчика на несколько составляющих, относящихся к разным группам объектов. Примерный код может выглядеть так:
from google.appengine.ext import db
import random
SHARDS_PER_COUNTER = 20
class CounterShard(db.Model):
name = db.StringProperty(required=True)
count = db.IntegerProperty(default=0)
def GetCount(nameOfCounter):
result = 0
for shard in CounterShard.gql('WHERE name=:1', nameOfCounter):
result += shard.count
return result
def ChangeCount(nameOfCounter, delta):
shard_id = '/%s/%s' % (
nameOfCounter, random.randint(1, SHARDS_PER_COUNTER))
def update():
shard = CounterShard.get_by_key_name(shard_id)
if shard:
shard.count += delta
else:
shard = CounterShard(
key_name=shard_id, name=nameOfCounter, count=delta)
shard.put()
db.run_in_transaction(update)
Очень интересное решение, но можно сделать еще лучше с помощью использования интерфейса Memcache API, путем сохранения значения счетчика в памяти и обновления его при необходимости. Следующий пример показывает, как можно установить период устаревания значения счетчика на одну минуту:
def GetCount(nameOfCounter):
memcache_id = '/CounterShard/%s' % nameOfCounter
result = memcache.get(memcache_id)
if not (result == None):
return result
result = 0
for shard in CounterShard.gql('WHERE name=:1', nameOfCounter):
result += shard.count
memcache.set(memcache_id, result, 60)
return result
[...] Замечание по производительности: Приведенный выше пример показывает неэффективную работу с счетчиками, однако он указан только для объяснения принципов работы. На практике же необходимо использовать разделение счетчиков. [...]