воскресенье, 8 августа 2010 г.

Простая реализация хранения данных, на примере технологий FriendFeed

Можно проводить много времени в чтении о различных технологиях хранения данных, пытаться сравнивать, выбирать, что лучше. Но, наверное, самый правильный путь - это попробовать каждую из технологий. Установить, настроить, протестировать на реальных задачах. Но что делать, если технология не находится в свободном доступе, она закрыта или используется внутри компании, и все, что мы о ней знаем, что она существует. Примеры: BigTable (Google), SimpleDB (Amazon), FluidDB (fluidinfo).

Конечно же это право разработчиков, и они поступают так, как считают будет правильнее и лучше для них. В любом случае, я очень благодарен им хотя бы за то, что они выложили в свободный доступ описание своих решений:

- Google BigTable, BigTable: A Distributed Storage System for Structured Data
- Amazon Dynamo, Dynamo: Amazon’s Highly Available Key-Value Store
- FriendFeed, как они используют MySQL для schema-less баз данных. Оригинал статьи. Ее перевод на Хабре.

Я остановлюсь на последнем решении, как наиболее простом и лучше описанном, не требующем какого-либо специального окружения. Для его воплощения в жизнь подойдет любой компьютер на базе Windows, Linux, MacOS с установленным python и MySQL.

Хочу сразу заметить, что приведенный ниже пример несколько упрощен от оригинала. Основная идея - это разобраться, как это работает, а не повторить один в один реализацию, описанную в статье. Следовательно, я допустил ряд вольностей со своей стороны по упрощению схемы хранения данных, изменению полей. В данном посте не рассматриваются вопросы индексации данных, а также вопросы масштабирования. Очень надеюсь, что данный пост станет первым в серии постов, посвященных применению MySQL для хранения key-value баз данных. Как говорится, от простого к более сложному. В любом случае, ссылки на оригиналы статей вы можете найти выше и вернуться к ним в случае необходимости.

Небольшая вводная часть. Хранилище данных позволяет хранить наборы свойств объекта/сущности, которые представляют собой либо JSON объекты, либо Python словари. Единственным обязательным полем является поле id - 16 байтовый UUID.

Объекты хранятся в таблице
CREATE TABLE entity (
__key__ INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
id BINARY(16) NOT NULL,
body MEDIUMBLOB,
UNIQUE KEY (id));
Поле __key__ используется для выборки данных, о котором я расскажу несколько позже. Про id уже упоминалось - это 16 байтовый уникальный id (uuid). Для его формирования используется стандартная функция mysql uuid() с единственным отличием, что расположение полей uuid изменено для упрощения индексации, выполнена реверсия полей:

стандартный вывод uuid(): 694518e3-a2a3-11df-957f-d4c66194c009
для id используется: d4c66194c009957f11dfa2a3694518e3

О причине данного решения я уже вскользь упоминал в предыдущем посте MySQL: как добавить микро/миллисекунды в current_timestmap. В MySQL для генерации uuid используется функция uuid версии 1, т.е. комбинация MAC адреса и значение 100 наносекундных интервалов с 00:00:00.00 15 Октября 1582 года. В стандартном выводе uuid() первые поля используются для хранения интервалов времени, а последнее для MAC адреса. Следовательно, первые поля чаще изменяются и их имеет смысл переставить в конец последовательности.

Поле body используется для хранения свойств объекта. Перед сохранением выполняется сериализация данных cPickle и сжатие zlib.

Исходный код можно найти на sources-ownport

Предположу, что база данных MySQL уже создана и перейду сразу к работе с данными.
ds = Datastore('mysql://entitydb:entitydb@localhost/entitydb')
if not ds.is_table_exist('entity01'):
    ds.create_entity('entity01')

Код, представленный выше, показывает, каким образом можно подключиться к базе данных. Формат URI для подключения к базе данных: mysql://<username>:<password>@<host or IP address>[:port]/<database>

Объект Datastore использует три метода для работы с данными: get, put, delete.

Метод get() используется для выборки объекта по его id или выборки набора данных.

Если id объекта известно, то выборка осуществляется get(<entity>, <id>).

Если необходимо сделать выборку для всех объектов из базы данных, можно воспользоваться полем __key__. Например:

data = ds.get('entity01')
while len(data) > 0:
    print data
    data = ds.get('entity01', __key__=data[-1][0]+1)

Метод put() используется для добавления нового или обновления данных существующего объекта.

Добавление нового объекта
entity = {
    'title': 'Test entity',
    'description': 'Test entity description',
}
ds.put('entity01', entity)

Для обновления данных существующего объекта достаточно указать его id

ds.put('entity01', entity, id)

Метод delete() используется для удаления объекта.

ds.delete('entity01', id)

Конечно же данное решение еще достаточно сырое, чтобы сразу использовать его в продуктиве. Есть не решенные вопросы:
- индексация данных
- полнотекстовый поиск
- разрешение конфликтов в случае одновременного сохранения данных
- версионность данных

Но как площадка для экспериментов на мой взгляд очень интересна.

1 комментариев:

  1. Пример реализации NoSQL With MySQL in Ruby http://jamesgolick.com/2009/12/16/introducing-friendly-nosql-with-mysql-in-ruby.html

    ОтветитьУдалить