Когда мы давали общее описание архитектуры нашего сервиса на нашем англоязычном техноблоге, у читателей, имеющих опыт работы с другими большими сервисами, самыми частыми вопросами были:
Оба этих вопроса закономерны и интересны. Сегодня мы ответим на первый, а второй прибережем для отдельного поста.
При правильном применении современный механизм хранения данных в ассоциативных массивах (key-value) может обеспечить значительную производительность и масштабируемость по сравнению с единичным экземпляром SQL-сервера. Однако есть несколько причин, по которым мы все же решили размещать все данные вашего аккаунта в MySQL.
Для начала, ACID-свойства транзакционных баз данных, таких как InnoDB с MySQL, имеют важное значение для нашего приложения и модели синхронизации.
Вот небольшой фрагмент таблиц базы данных для хранения блокнотов и заметок в серверной базе данных:
Если вы создаете блокнот под названием “Рецепты” на своем Windows-клиенте Evernote, а затем сразу же копируете в этот блокнот рецепт любимой запеканки, ваш клиент при следующей синхронизации сделает следующее:
Каждый из этих высокоуровневых запросов к API осуществляется посредством единственной SQL-транзакции, которая гарантирует, что клиент может полностью доверять любому ответу сервера.
ACID-совместимая база данных обеспечивает такие преимущества как:
Атомарность. Если вызов API проходит успешно, то 100% изменений будут выполнены, а если вызов API не удается, то не будет сделано ни одно из них. Это значит, что если у нас не получается поместить четвертое изображение в вашу заметку, то в вашем аккаунте не останется наполовину сформированная заметка, которая, к тому же, будет высчитана из оставшегося ежемесячного объема для загрузки данных.
Непротиворечивость. В конце любого вызова API аккаунт находится в полностью рабочем и внутренне непротиворечивом состоянии. У каждой заметки есть свой блокнот, и ни одна из них не находится в подвешенном состоянии. База данных не даст нам удалить блокнот, в котором еще есть заметки, благодаря ограничению FOREIGN KEY.
Гарантированность. Когда сервер сообщает, что блокнот был создан, клиент может считать, что блокнот у него уже есть и учитывать это в дальнейших операциях (таких как вызов на создание заметки). Это изменение гарантированно, и клиент знает, что оно консистентно отражает состояние сервиса в любое время.
Принцип гарантированности играет самую важную роль для нашего протокола синхронизации. Если клиентское приложение не было бы уверено, что изменения, сделанные сервисом, гарантированно произошли, протокол синхронизации стал бы гораздо более сложным и менее эффективным. Каждому синхронизируемому клиенту пришлось бы постоянно проверять соответствие каждого серверного объекта текущей ситуации. Подобная реализация абсолютного контроля над непротиворечивостью для аккаунта с 20 тысячами заметок, 40 тысячами файлов ресурсов и 10 тысячами меток была бы крайне дорогой, если бы изменения не предполагали гарантированность.
Преимущества ACID в случае с транзакционной базой данных делают очень сложным масштабирование данных за пределы одного сервера. Кластеризация базы данных и репликация с несколькими мастер-серверами — дело весьма темное, а ассоциативные хранилища данных обеспечивают значительно более простой подход к масштабированию единого хранилища на несколько серверов.
К счастью, в Evernote сейчас нет необходимости решать эту проблему. Хотя у нас на серверах уже около миллиарда заметок и почти 2 миллиарда файлов ресурсов, это, на самом деле, не единый большой набор данных — это 20 миллионов отдельных наборов, по одному на пользователя.
Такая раздробленность означает, что у нас нет проблемы хранения большого единого объема данных, мы имеем дело с хранением множества изолированных наборов данных среднего объема, что аккуратно ложится в нашу серверную архитектуру шардов.
Мы, тем временем, стараемся держать руку на пульсе современных технологий в области хранения данных для будущих проектов, которые не требуют строгой транзакционности ACID и предусматривают горизонтальную масштабируемость. Например, наша система отчетности и аналитики уже переросла возможности MySQL-платформы и требует замены на что-то более большое, быстрое и интересное.
Однако мы вполне удовлетворены имеющимся разбитым на шарды MySQL-хранилищем пользовательских данных, хоть это и не так круто по мнению некоторых ребят.
- Почему ваши структурированные данные хранятся в базах данных с SQL вместо того, чтобы использовать NoSQL-решения?
- Почему вы используете собственное аппаратное обеспечение вместо того, чтобы воспользоваться услугами облачного хостинга?
Оба этих вопроса закономерны и интересны. Сегодня мы ответим на первый, а второй прибережем для отдельного поста.
При правильном применении современный механизм хранения данных в ассоциативных массивах (key-value) может обеспечить значительную производительность и масштабируемость по сравнению с единичным экземпляром SQL-сервера. Однако есть несколько причин, по которым мы все же решили размещать все данные вашего аккаунта в MySQL.
Преимущества SQL
Для начала, ACID-свойства транзакционных баз данных, таких как InnoDB с MySQL, имеют важное значение для нашего приложения и модели синхронизации.
Вот небольшой фрагмент таблиц базы данных для хранения блокнотов и заметок в серверной базе данных:
CREATE TABLE notebooks (
id int UNSIGNED NOT NULL PRIMARY KEY,
guid binary(16) NOT NULL,
user_id int UNSIGNED NOT NULL,
name varchar(100) COLLATE utf8_bin NOT NULL,
...
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE notes (
id int UNSIGNED NOT NULL PRIMARY KEY,
guid binary(16) NOT NULL,
user_id int UNSIGNED NOT NULL,
notebook_id int UNSIGNED NOT NULL,
title varchar(255) NOT NULL,
...
FOREIGN KEY (notebook_id) REFERENCES notebooks(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Если вы создаете блокнот под названием “Рецепты” на своем Windows-клиенте Evernote, а затем сразу же копируете в этот блокнот рецепт любимой запеканки, ваш клиент при следующей синхронизации сделает следующее:
- Вызовет NoteStore.createNotebook(), чтобы запросить создание блокнота на сервере. Тот вернет ему идентификатор (GUID) созданного блокнота.
- Вызовет NoteStore.createNote(), чтобы запросить создание заметки на сервере с уже заданным GUID блокнота.
Каждый из этих высокоуровневых запросов к API осуществляется посредством единственной SQL-транзакции, которая гарантирует, что клиент может полностью доверять любому ответу сервера.
ACID-совместимая база данных обеспечивает такие преимущества как:
Атомарность. Если вызов API проходит успешно, то 100% изменений будут выполнены, а если вызов API не удается, то не будет сделано ни одно из них. Это значит, что если у нас не получается поместить четвертое изображение в вашу заметку, то в вашем аккаунте не останется наполовину сформированная заметка, которая, к тому же, будет высчитана из оставшегося ежемесячного объема для загрузки данных.
Непротиворечивость. В конце любого вызова API аккаунт находится в полностью рабочем и внутренне непротиворечивом состоянии. У каждой заметки есть свой блокнот, и ни одна из них не находится в подвешенном состоянии. База данных не даст нам удалить блокнот, в котором еще есть заметки, благодаря ограничению FOREIGN KEY.
Гарантированность. Когда сервер сообщает, что блокнот был создан, клиент может считать, что блокнот у него уже есть и учитывать это в дальнейших операциях (таких как вызов на создание заметки). Это изменение гарантированно, и клиент знает, что оно консистентно отражает состояние сервиса в любое время.
Принцип гарантированности играет самую важную роль для нашего протокола синхронизации. Если клиентское приложение не было бы уверено, что изменения, сделанные сервисом, гарантированно произошли, протокол синхронизации стал бы гораздо более сложным и менее эффективным. Каждому синхронизируемому клиенту пришлось бы постоянно проверять соответствие каждого серверного объекта текущей ситуации. Подобная реализация абсолютного контроля над непротиворечивостью для аккаунта с 20 тысячами заметок, 40 тысячами файлов ресурсов и 10 тысячами меток была бы крайне дорогой, если бы изменения не предполагали гарантированность.
Как насчет большого объема данных?
Преимущества ACID в случае с транзакционной базой данных делают очень сложным масштабирование данных за пределы одного сервера. Кластеризация базы данных и репликация с несколькими мастер-серверами — дело весьма темное, а ассоциативные хранилища данных обеспечивают значительно более простой подход к масштабированию единого хранилища на несколько серверов.
К счастью, в Evernote сейчас нет необходимости решать эту проблему. Хотя у нас на серверах уже около миллиарда заметок и почти 2 миллиарда файлов ресурсов, это, на самом деле, не единый большой набор данных — это 20 миллионов отдельных наборов, по одному на пользователя.
Такая раздробленность означает, что у нас нет проблемы хранения большого единого объема данных, мы имеем дело с хранением множества изолированных наборов данных среднего объема, что аккуратно ложится в нашу серверную архитектуру шардов.
Возможно, в будущем…
Мы, тем временем, стараемся держать руку на пульсе современных технологий в области хранения данных для будущих проектов, которые не требуют строгой транзакционности ACID и предусматривают горизонтальную масштабируемость. Например, наша система отчетности и аналитики уже переросла возможности MySQL-платформы и требует замены на что-то более большое, быстрое и интересное.
Однако мы вполне удовлетворены имеющимся разбитым на шарды MySQL-хранилищем пользовательских данных, хоть это и не так круто по мнению некоторых ребят.