MCP

вторник, 27 февраля 2018 г.

Хранение данных в памяти

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

Для начала скажу, что сам я, пока так и не реализовал эту идею в полной мере, были продукты, где это активно использовалось, но в основном, продукты, за которые я ответственен работают достаточно быстро и так, так что как-то и не требуется усложнение и смена парадигмы. Но, когда-нибудь, обязательно попробую!

Почему не стоит полагаться на кеш SQL-сервера?

Потому что он построен именно на модели кеша для данных, и в него можно попадать или не попадать. Конечно, в MS SQL есть OLTP таблицы, которые хранятся в памяти, но они больше для очень активных данных, и вообще это на уровне SQL, причём MS SQL. А без них — используются стандартные алгоритмы для поиска, оптимизированные для данных, находящихся на диске, как результат, необходимый кеш для очень 100% попадания в память многократно превышает размер реальных данных.

Я же предлагаю забить на это, и максимально хранить все данные из базы в памяти (загружать их из базы на старте, или ленивым образом).

Что это даёт?


  • Резко упрощается внешняя логика пользователя. Во многих случаях можно отказаться от джойнов, если нет дополнительной фильтрации. Например, классический джойн с данных с пользователем (например, вывести автора данных) можно заменить на выборку данных и доставание всех пользователей по ID. Это очёнь дешёво
  • Можно использовать очень тупые алгоритмы, и это всё равно будет очень быстро. Фуллскан 10000 записей почти незаметная вещь, а если надо будет ещё быстрее — всегда можно будет прикрутить "индекс" в виде словарика
  • Можно избавиться от отдельного кеша для тяжёлых данных, ибо и так всё в памяти
  • Тупо быстрее из-за отсутствия запросов к внешнему SQL-серверу
  • Легко хранить данные, которые тяжело забирать из SQL (объект с зависимыми детишками, слабо-структурированные документы, JSON/XML поля)
  • При этом, если приложение написано с использованием LINQ, то местами можно вообще не заметить разницы между базой и памятью (если грамотно спланировать архитектуру приложения)

 А в память влезет?

А вот это как раз главный вопрос, который всех останавливает и которого все боятся. И из-за этого, всё так и останавливается на уровне идей. Но давайте прикинем необходимое количество памяти.
  • База данных в 20ГБ содержит где-то 20ГБ данных (логично, да?), в памяти это будет занимать примерно столько же. Найти подходящий сервер — не так уж и сложно. Естественно, базы в 100МБ вообще влезут в память без проблем
  • Очень часто в "больших" базах большой объём занимают всякие полумёртвые данные — журналы, результаты импорта, файлы, подписанные данные, акксесс логи... Эти данные нужны очень редко, их можно не хранить в памяти, тем самым кардинально снизив объём "реальных" данных
  • Многие данные нужны только в небольшом количестве. Например, у вас есть 10000 пользователей в системе, но активны только 1000, тут можно использовать какой-нить LRU кеш и не держать в памяти все объекты, а только активные. Опять же, очень сокращает необходимый объём памяти
  • Ну и для реально огромных баз данных можно уж держать в памяти только специально выделенные объекты (например, справочники). Хотя, с такими объёмами у вас будет проблем побольше чем просто держать в памяти

Как реализовать?

Поскольку я ещё не делал это в полном виде, то могу только предположить следующие варианты:
  • Собственный кеш класса, ратающего с сущностью (e.g. UserManager), он сам решает, что и как кешировать. Проблемы в куче аналогичного кода в разных классах и сложность с инвалидацией. Плюсы: в каждом конкретном случае можно использовать самые эффективные варианты
  • Мемоизация и автомемоизация методов. Плюсы: очень упрощается код, минусы: сложно инвалидировать и оптимально использовать данные. 
  • Обёртка над ORM (или использование встроенных средств типа Second Level Cache), которая сделает всё сама. Проблемы: сложно в реализации и конфигурировании. Плюсы: полная прозрачность в использовании со стороны кода

Краткий итог

Данное агрессивное кеширование и разработка с учётом того, что оно есть, может кардинально поменять работу с данными в вашем приложении. Код будет проще, тупее и понятнее, но при этом весьма быстро работать, даже если выполняет сложные операции с данными.При этом сама база данных останется и в ней всегда можно покопаться, поделать аналитику, но при этом не сильно думать об её оптимизации (индексах, статистике и прочем тюнинге), это всё будет на уровне приложения, которое знает, что оно хочет от данных и как оно будет их использовать.

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

  1. Если ты даш своему DB серверу те самые 20GB - то все и так будет в памяти. Ему не нужно будет выгружать ничего и освобождать память.
    Если ты попробуешь написать свой собственный кеш, то возникнут проблемы:
    а) инвалидации
    б) компрессии, 20 compressed GB on Disk is not the same as uncompressed data in memory. Либо самому нужно писать подходящии compress алгоритмы.

    В общем, ты просто напишешь свой собственный кеш и столкнешься со всеми проблемами написания этого самого кеш.

    ОтветитьУдалить
    Ответы
    1. Нет, не будет всё в памяти. Во всяком случае у MS SQL (ему гораздо больше нужно для того, чтобы всё в памяти было) и у Postgre (он как-то очень странно работает с кешированием)
      С инвалидацией проблемы есть, со сжатием — SQL базы данных обычно не сжимают данные на диске, если явно не указать (проседает перфоманс сильно), и 20Gb я имел в виду именно несжатых данных.

      Удалить
  2. Ну так верно, и тебе будет гораздо больше, чтобы в памяти было. Ты же не будешь делать table scan на каждый запрос, тебе нужны будут индексы. Это как раз следующее, что большое должно будет храниться после самих данных.

    По поводу сжатия данных, на данный момент самое медленное это как раз IO, поэтому проще прочитать меньше с диска и использовать какой-нибудь snappy, чем не сжимать. То, что некоторые базы пока не умеют сжимать - это только показатель того, что скорее всего не сделали еще, и не могут сделать в связи с историческими причинами (см https://www.citusdata.com/blog/2013/04/30/zfs-compression/ - "we demonstrated that ZFS and compression actually improves performance when queries are IO bound"). Посмотри на ту же MongoDB они перешли с MMAP на WiredTiger со сжатием и что получилось.

    И на самом деле - твоя идея это не бред, это как раз тренд, создание in-memory databases, которые держат данные на диски только для reliability. Просто выбери подходящую, см https://en.wikipedia.org/wiki/List_of_in-memory_databases и как раз Microsoft SQL Server with Hekaton (OLTP) это один из вариантов. Не очень понимаю, почему ты его откидываешь. Потому что тебе придется денормализировать данные? Ну тогда тебе точно дорога в NoSQL ;) Welcome Redis/MongoDB/etc.

    ОтветитьУдалить
    Ответы
    1. Не, SQL именно что хранит кеш для стораджа, а не для данных, они как раз OLTP таблицы сделали, чтобы они в памяти идеально торчали. Они даже на презентации цифры приводили по скорости и памяти (числа уже не помню, но значимые были).

      Сжатие повышает скорость линейного чтения, но начисто гробит IO, т.е. оно подходит для баз, которые в основном держат в памяти, иначе будет очень плохо (очень тяжёлые чтения и поиск по соответствующим полям). MS SQL умеет сжимать по таблицам, если явно сказать.
      Собственно, про сжатие я долго могу говорить, у меня архиватор свой есть, я все эти грабли прошёл неоднократно :)

      Монга - отдельный разговор, с их BSON'ом и желанием всё держать в памяти — это хорошее решение было.

      По поводу In-Memory Db, думаю, но есть глобальная разница между внутренним кешем или своим правильным кешем и неким абстрактным. Банальный TCP round-trip до сервера очень долгий относительно выдёргивания из локальной памяти приложения. Но зато даёт возможность горизонтального масштабирования.
      OLTP таблицы MS SQL не использую т.к. он платный и до недавнего времени не было под Linux, что ограничивало применение. За его стоимость проще ресурсов было подбросить :)

      Может быть в подходящем проекте попробую NoSQL, пока просто в моих и так всё быстро, особого смысла оптимизировать нет.

      Удалить
    2. То что ты называешь кеш стораджа это отчасти и есть те самые данные. Это backend DB. И на самом деле OLTP выигрывает именно из-за другой реализации transactions (или в терминах concurrency control). Hekaton (см https://www.microsoft.com/en-us/research/blog/hekaton-breaks-through/) это был Microsoft Research проект, который и вырос в OLTP, где основная идея была реализовать Multi Version Concurrency Control.

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

      Про платность понятно. Реализовать MVCC совсем не просто, поэтому есть буквально несколько нормальных реализаций, и, понятное дело, все хотят на этом заработать.

      Вообще, очень рекомендую посмотреть курсы лекций от CMU Database Group https://www.youtube.com/channel/UCHnBsf2rH-K7pn09rb3qvkA - выбери любой, например, сейчас идет запись Advanced Database Systems (Spring 2018). Лектор очень классный и необычный. Вообще, я думаю, если бы кто-нибудь разобрался в этом курсе и смог бы на его основе преподавать в ЯрГУ - было бы очень круто.

      Удалить
    3. Пока только про сжатие. Тут ещё нюансы в том, что условный блок в 8Кб (размер страницы в MSSQL) при сжатии превратится в условные 4764 байт, соответственно, нельзя просто прочитать 100-ый блок, надо держать их адреса на диске, при этом, если мы чуть изменим данные и они станут 5000 байт, нам придётся писать в другое место, тут будет дырка. Заткнуть эти дырки в целом можно полным перестроением базы.
      Ну и сжатие по блокам приводит к проблемам, что сжимать мелкие объёмы очень неэффективно, а большие приводят к тому, что при изменении условного бита надо перезаписать весь блок целиком. Тут весьма хорошо проседает перфоманс.
      Если мы используем аналог Eventual Consistency, то нам чуть легче, если нет — то куча геморроя.

      Удалить
    4. Согласен, это не так все просто. И когда я говорю про компрессию, я имею ввиду не только компрессию на базе словарей, но и column compression.
      И все примерно так, как ты говоришь - да, в БД на диске постоянно образуются дырки. На сколько я понимаю VACUUM операция в PostgreSQL как раз с этим и борется. LSM это другая технология, которая выбирается большинством БД, которая никогда не меняет файлы, а только делать merge нескольких в один больше файл. Backend БД это очень интересная тема. Опять же, рекомендую посмотреть тот набор лекций.
      Кстати, погуглил - SQL Server 2017 тоже поддерживает Data Compression https://docs.microsoft.com/en-us/sql/relational-databases/data-compression/data-compression, и то же самое "In addition to saving space, data compression can help improve performance of I/O intensive workloads because the data is stored in fewer pages and queries need to read fewer pages from disk."

      Удалить