Google Code предлагается на следующих языках: English – Español – 日本語 – 한국어 – Português – Pусский – 中文(简体) – 中文(繁體)
При разработке эффективного приложения на Google App Engine нужно учитывать частоту обновления объектов. Несмотря на то что хранилище данных App Engine масштабируется для поддержки огромного числа объектов, следует иметь в виду, что обновить объект или группу из них можно не чаще пяти раз в секунду. Это лишь оценка. Фактическая частота обновления зависит от нескольких атрибутов объекта, включая количество его свойств, его размер и количество индексов, которые нужно обновить. Несмотря на то что частота обновления объекта и группы из них ограничена, App Engine позволяет успешно обрабатывать многочисленные параллельные запросы, распределенные среди отдельных объектов. Таким образом, в целом удается достичь гораздо более частого обновления, что мы и рассмотрим в этой статье с использованием сегментирования.
Вопрос состоит в том, а что если объект нужно обновлять чаще, чем пять раз в секунду? Например, нужно посчитать количество голосов в опросе, количество комментариев и т. д. Вот простой пример:
class Counter(db.Model):
count = db.IntergerProperty()
Если бы один объект являлся счетчиком и обновление осуществлялось бы слишком часто, возник бы конфликт, поскольку бы начали накапливаться сериализованные записи и истекало бы время ожидания. Решение этой проблемы может показаться немного алогичным, если вы привыкли использовать реляционные базы данных. Оно построено на том факте, что чтение из хранилища данных App Engine осуществляется очень быстро и требует очень мало ресурсов, поскольку последние считанные и обновленные данные кэшируются в памяти. Уменьшить вероятность конфликта можно, создав сегментированный счетчик – разделив счетчик максимум на N разных счетчиков. Чтобы увеличить счетчик, нужно выбрать случайным образом сегмент и увеличить соответствующий счетчик. Чтобы узнать значение счетчика, нужно считать значения всех счетчиков сегментов и сложить их. Чем больше сегментов, тем выше производительность для увеличения счетчика. Эта техника работает не только для счетчиков. Она может пригодиться для определения объектов в приложении с наибольшим количеством записей и нахождения эффективных способов их сегментирования.
Вот очень простая реализация сегментированного счетчика:
from google.appengine.ext import db
import random
class SimpleCounterShard(db.Model):
"""Shards for the counter"""
count = db.IntegerProperty(required=True, default=0)
NUM_SHARDS = 20
def get_count():
"""Retrieve the value for a given sharded counter."""
total = 0
for counter in SimpleCounterShard.all():
total += counter.count
return total
def increment():
"""Increment the value for a given sharded counter."""
def txn():
index = random.randint(0, NUM_SHARDS - 1)
shard_name = "shard" + str(index)
counter = SimpleCounterShard.get_by_key_name(shard_name)
if counter is None:
counter = SimpleCounterShard(key_name=shard_name)
counter.count += 1
counter.put()
db.run_in_transaction(txn)
В методе get_count() мы просто просматриваем все сегменты (с помощью метода CounterShard.all()) и складываем значения их счетчиков. В методе increment() нужно в одной транзакции считать значение счетчика, увеличить счетчик сегмента, выбранного случайным образом, и записать его значение.
Обратите внимание, мы создаем сегменты максимально просто – при первом увеличении их счетчиков. Такое создание сегментов позволяет при необходимости увеличить их количество (но не уменьшить) впоследствии. Значение NUM_SHARDS можно увеличить до 20. Результаты выполнения метода get_count() не изменятся, поскольку запрос выбирает только сегменты, добавленные в хранилище данных, а метод increment() создает нужные сегменты.
Этот пример хорош для ознакомления. Однако, более общий счетчик позволит моментально создавать именованные счетчики, динамически увеличивать количество сегментов и использовать кэш памяти для ускорения считывания сегментов. Именно это реализовано в коде примера, который Бретт Слаткин предоставил в своем выступлении на Google I/O. Здесь представлен этот код вместе с функцией, которая увеличивает количество сегментов для определенного счетчика:
from google.appengine.api import memcache
from google.appengine.ext import db
import random
class GeneralCounterShardConfig(db.Model):
"""Tracks the number of shards for each named counter."""
name = db.StringProperty(required=True)
num_shards = db.IntegerProperty(required=True, default=20)
class GeneralCounterShard(db.Model):
"""Shards for each named counter"""
name = db.StringProperty(required=True)
count = db.IntegerProperty(required=True, default=0)
def get_count(name):
"""Retrieve the value for a given sharded counter.
Parameters:
name - The name of the counter
"""
total = memcache.get(name)
if total is None:
total = 0
for counter in GeneralCounterShard.all().filter('name = ', name):
total += counter.count
memcache.add(name, str(total), 60)
return total
def increment(name):
"""Increment the value for a given sharded counter.
Parameters:
name - The name of the counter
"""
config = GeneralCounterShardConfig.get_or_insert(name, name=name)
def txn():
index = random.randint(0, config.num_shards - 1)
shard_name = name + str(index)
counter = GeneralCounterShard.get_by_key_name(shard_name)
if counter is None:
counter = GeneralCounterShard(key_name=shard_name, name=name)
counter.count += 1
counter.put()
db.run_in_transaction(txn)
memcache.incr(name)
def increase_shards(name, num):
"""Increase the number of shards for a given sharded counter.
Will never decrease the number of shards.
Parameters:
name - The name of the counter
num - How many shards to use
"""
config = GeneralCounterShardConfig.get_or_insert(name, name=name)
def txn():
if config.num_shards < num:
config.num_shards = num
config.put()
db.run_in_transaction(txn)
Исходный код этих счетчиков доступен в Примерах Google App Engine в виде примера sharded-counter. В веб-интерфейсе особо не на что смотреть, поэтому рекомендуется использовать интерфейс администрирования, чтобы проверить модели данных после нескольких увеличений счетчиков.
Сегментирование является одной из основных техник создания масштабируемого приложения. Надеюсь, что приведенные здесь примеры дали вам понятие о том, где следует применять эту технику в приложении. Код в этой статье предоставлен по лицензии Apache 2, поэтому его можно спокойно использовать для разработки своих решений.
Посмотрите выступление Бретта Слаткина на Google I/O Создание масштабируемых веб-приложений с помощью Google App Engine.