9 марта 2012 в 15:30

Архитектурный изьян CouchDB

Моя любимая тема в программировании — копаться в негативных эффектах, которые преподносят нам самые, на наш взгляд, тривиальные операции.

Один из таких вопросов — удаление записей в базе данных. Данная операция, по мнению большинства программистов, ускоряет работу с базой и делает её компактнее. Фокус состоит в том, что это неправда. И если с реляционными базами это неправда только отчасти, то с NoSQL это может быть полнейшим враньём.

Вот о такой проблеме в Apache CouchDB мы и поговорим далее.
Картинка в тему:


Как хранятся данные


Данные в любой базе хранятся по принципу файловой системы: есть карта размещения данных и файл, в котором они непосредственно размещаются. Для SQL это обычно таблица, а для NoSQL — обычно дерево.
Когда мы удаляем данные, как и в случае файловой системы, база не будет тратить время на то, чтобы пересоздать файл карты и файл данных без записи, которую мы хотим удалить. Она просто пометит запись как удалённую в карте. В этом легко убедиться, для этого создадим простую таблицу в MySQL, используя MyISAM, добавим туда одну запись, затем удалим и посмотрим статистику:
MySQL table with overhead
Чтобы оптимизировать это, нам нужно пересоздать файл карты и файл данных. Выполним:
OPTIMIZE TABLE guest;
и получим:


Интересно, что таблица в неоптимизированном варианте, как это ни странно, работает почти так же быстро, как и в оптимизированном. Происходит это потому, что сам принцип хранения реляционных данных обычно довольно прост и легко рассчитать, насколько нужно сделать seek, чтобы пропустить удалённые записи. Вышесказанное, конечно, не означает, что overhead можно игнорировать, однако достаточно написать простейший bash-скрипт, который по расписанию будет оптимизировать данные, и никакой дополнительной работы в программном коде делать не придётся.
Надо отметить, что вышесказанное не совсем подходит для InnoDB, где ньюансов ещё больше, но сегодня статья о CouchDB, а не о MySQL.


Как работает удаление в CouchDB


Возьмём простой документ:

и удалим его. Что происходит? База помечает документ как удалённый. Как она это делает? Она считывает документ, удаляет из него все поля, вставляет дополнительное свойство _deleted:true и записывает документ под новой ревизией. Пример:


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

Как это влияет на работу


В CouchDB данные хранятся в виде B+ дерева. Удалённый документ, хоть он и пустышка, остаётся частью дерева. Это значит, что эта мёртвая запись учитывается. И учитывается она не только при построении индексов, но и при обычной вставке документа, так как при вставке документа может происходить перестроение дерева, а чем больше там записей, тем этот процесс медленнее.

Контрольный в голову


И наконец, остаётся понять, насколько Erlang быстр. Вот если взять синтетические тесты, то видно, что производительность Эрланга близка к PHP. То есть B+ деревом манипулирует не самый быстрый язык.

Как это тормозит в реальности


Если WRITE у вас не самая редкая операция, то, имея в дереве несколько миллионов документов, вы неожиданно (причём именно неожиданно) можете обнаружить, что база начинает сильно тормозить. Например, вы используете CouchDB, чтобы хранить документы с низкой продолжительностью жизни (сессии, lock-файлы, очереди). Возьмём график с реального продакшена:

Из графика видно, что пики довольно-таки острые. Резкий рост пика не всегда предсказуем. Порой у нас около 2 млн. обновлений базы (около 1 млн. документов в дереве) и работает вполне сносно, но появляется ещё 100 тысяч, и производительность вылетает в трубу. А резкий спад пика происходит потому, что мы пересоздаём базу и производительность на несколько недель становится приемлемой.

Выводы


  • CouchDB хранит все документы в B+ дереве, которое периодически перестраивается. Язык Erlang не самый быстрый для этого. Не используйте CouchDB для документов с низкой продолжительностью жизни, иначе у вас будет слишком большое дерево, ибо документы из него никогда не удаляются.
  • Даже если вы не будете удалять документы, вы получите лаг на добавление новых, когда у вас будет несколько милионов записей.
  • Советую обратить внимание на статью 16 практических советов по работе с CouchDB.
  • Становится понятно, почему Дэмиен Кац, создатель CouchDB, решил сделать форк CouchBase и переписать ядро на Си. Кстати, CouchBase содержит встроенный memcached, который позволяет хранить документы с низкой продолжительностью жизни в отдельной области.
Андрей Нехайчик @gnomeby
карма
46,2
рейтинг 23,3
Похожие публикации
Самое читаемое Разработка

Комментарии (57)

  • +10
    используете CouchDB, чтобы хранить документы с низкой продолжительностью жизни (сессии, лок-файлы, очереди)

    Очень странное использование для этих целей в продакшене решения, которое никогда не претендовало на быструю запись. Redis не просто так ведь придумали, например.
    • +1
      Запись достаточно быстрая. А если хранить сессии в отдельной базе и без индексов, то вообще хорошо. Проблема с разрастающимся B+ деревом, а вот понять это до того как начнёшь манипулировать миллионами записей сложно.
      • +1
        CouchDB — не лучшее решение для очень динамических данных. Оно идеально для статистических обработок, где данные в прошлом не меняются, база не сжимается по 10 раз в неделю, но и тут обычно выносят оперативные данные в отдельную базу, а по уменьшению их актуальности реплицируют в общий котел, если не было сделано с самого начала, и удаляют её за ненадобностью. Партицирование и в RDBMS актуально, так и тут.
        Тогда коуч работать будет быстро и действительно relax.
        • +3
          Это да. И работая с CouchDB 2 года, я могу сказать, что я до сих пор не знаю, есть ли такая ситуация, где нужно использовать именно CouchDB, а не другие базы.
          • +1
            wiki
            • 0
              Там нет такой информации. Там есть информация вида: когда не нужно использовать CouchDB и кто её использует. Однако если кто-то её и используют это совсем не означает, что она оптимальна для них.
              • +5
                Wiki — та самая ситуация, где нужно использовать именно CouchDB, а не другие базы. Я смотрел вовнутрь MediaWiki, DokuWiki и MoinMoin. Поверьте — это та самая ситуация :)
                • 0
                  Вы имеете ввиду встроенное хранилище репликаций? Да это подходит для вики, пока кто-нибудь случайно не нажмёт compact. Кроме того, постоянные правки статей сделают вам большой облом, ибо пересчёт индексов для миллионов документов будет очень тормозить.
                  • 0
                    Возможно ревизий? Эти ревизии не предназначены для версионирования данных — нужно хранить либо инлайново, если нужна историческая индексация (и нам не важен размер документа), либо вложением. Сжимать базу не обязательно для потерь старых ревизий — первая же репликация убивает их в новой копии.

                    А при больших количествах обновлений обычно настраивают автообновление всех view функций, чтобы не было застоя и не тормозило.
          • 0
            It depends. За 2 года CouchDB очень сильно изменился, но как минимум две задачи он решал и решает прекрасно: обработка больших массивов статистических данных во времени, при условии, что все map/reduce функции были заранее продуманы, и самодостаточные couchapp web-приложения.
            Ждем окончательный выход на мобильные платформы — из андроид.маркета они как то быстро выпилились.

          • 0
            Например когда нужен MVCC и репликация между удалёнными нодами через ненадёжные соединения. Ваш К.О.
            Да и вобще, помимо «нужно» есть ещё «хочется» :)
            • +2
              Через ненадёжные соединения репликация в CouchDB работает крайне ненадёжно, постоянно приходится перезапускать.
  • –14
    Обязательно вставлять несвежие раковые рожи с 9gag.com, которые корнями уходят в древний АиБ-контент?
    От этого статья тут же начинает выглядеть стильно, модно, молодёжно? Хабр стал филиалчиком фейсбука?
    • +5
      Я хотел передать ощущение. Если вы пришлёте мне ссылку на другие рожи с тем же смыслом, я с удовольствием поменяю.
      • +3
        Не надо никаких рож, все поводы для «ощущений» лучше описать объективным текстом — описанием ситуации, а уж сами ощущения пускай испытывают сами читатели в зависимости от этого описания.
        • НЛО прилетело и опубликовало эту надпись здесь
    • +3
      Зачем раскрывать рот, когда ничего дельного по теме сказать не можешь?
      Не обязательно вставлять rage-faces, но, имхо, тут было всё к месту.
      Так же не обязательно с ни с того, ни с сего вставлять свои высеры нонконформизма с претензией на серьёзность.
      • –6
        То есть теперь негативное отношение к тому, что является признаком современного сетевого разложения, называется «нонкомформизм». Ок.
        > Не обязательно вставлять rage-faces, но, имхо, тут было всё к месту.
        Я мог бы процитировать пункт 4 правил или упомянуть о том, что это в первую очередь серьёзный информационный ресурс, но видимо вы этого не поймете.
        • +1
          4 пункт притянут за уши.
        • +3
          Во всём этом меня удивляет одно. Вы уверены, что эти рожи с «9gigs» или как там его, а не с форчана, где человек описывает неприятные аспекты работы его сантехники?
          • +1
            Я знаю об истоках этого мема, но между проблемой, годной статьи на хабре и комиксом по проблеме заливания филейной части тела брызгами из унитаза при дефекации мало общего. Когда-то они были контентом форчана. Теперь они являются контентом сетевых хомячков.
            То есть в принципе, кроме популярности, изменилось немногое.
            • +3
              Ок, ок. Поменял картинку, наслаждайтесь.
              • 0
                Стало хуже, имхо. Теперь они передают не те эмоции. Что за игривый язык? Что за чёрт?
              • 0
                Оптимизировал, так сказать, под аудиторию.
        • –1
          Не обращайте внимания на минусы. Вы всё правильно сказали.
  • +1
    Странно, почему они не ограничили время жизни удалённых записей. Если сделать это конфигурируемым параметром — то можно и по старой схеме работать (бесконечное время жизни), так и по-новой, удаляя их через, скажем, 10 дней. В этом случае запись может «воскреснуть», если реплики были разломаны и не синхронизировались дольше 10 дней, но, чаще всего, это разумная плата за повышение производительности.
    • 0
      Разработчики CouchDB понимают, что они живут не в идеальном мире. А в неидеальном мире программисты очень часто не дочитывают документацию. Вот и перестраховались.
      • 0
        Сделать значением по-умолчанию бесконечность, тогда не читающие документацию не пострадают.
    • +1
      В новых версиях есть compaction daemon, который при накоплении определенного буфера через заданный интервал времени в фоне сжимает базу, тем самым минимизируя нагрузку и сохраняя место. Все настраивается в конфиге.
  • –1
    Уважаемый автор, неплохо бы проверять грамматику перед постингом, интересная статья, но читать местами неприятно.
    • –1
      Шлите ошибки в личку, я проверял 2-мя утилитами, вроде всё ок. Если конечно вас не смущают буквы ё.
      • 0
        Проблема, в щедро рассыпаных, по всему тексту, запятых ;)
        • +2
          Если ужи показывать как не надо делать, то так:
          Проблема, в, щедро, рассыпанных, по, всему, тексту, за, пятых ;,)
        • 0
          >Проблема, в щедро рассыпаных, по всему тексту, запятых ;)
          Деепричастных и причастных оборотов нет, основа (подлежащее и сказуемое) однп. Запятых в этом предложении не надо вообще.
          Простите, чесслово, за оффтоп, нетрезвый.

          В комментарии полез, чтобы узнать, куда чувачка с комикса убрали и почему вместо него рогатого поставили.
          • 0
            > Деепричастных и причастных оборотов нет, основа (подлежащее и сказуемое) однп. Запятых в этом предложении не надо вообще.

            Я знаю :) Это была иллюстрация того, что происзодит в тексте :)
      • 0
        Нет, проблема, конечно, не в ё. А в тся/ться и опечатках, не думаю что какая-то утилита могла бы пропустить такое. А чем проверяли, как называются утилиты?
        • +1
          Плагин к Firefox, Libre office, Word. Вы бы пару ошибок в личку прислали, а то, что-то дальше обвинений дело не доходит.
          • 0
            Если я их Вам пришлю, то вопрос будет исчерпан, а мне хотелось бы сейчас понять какими утилитами не стоит пользоваться для исправления ошибок. А какая версия Word? Только что проверил у себя, всё отлично определилось. Не воспринимайте это как обвинения, это был не более чем совет проверять грамматику, а сейчас мне интересно вычеркнуть для себя ненадёжный в этом плане софт.
            • 0
              Упс, кажись у меня слетела проверка в ворде. Поправил немного вручную.
          • 0
            Бросьте, всё замечательно. Обвинения — фигня, знания, которыми Вы делитесь — всё.
  • 0
    Что происходит? То что база помечает документ как удалённый. Как она это делает? Она считывает документ, удаляет все поля из документа, вставляет дополнительное свойство _deleted:true и записывает документ под новой ревизией.


    И это отлично. У CouchDB это одна из основных фишек.

    Если WRITE у вас не самая редкая операция, то имея в дереве несколько миллионов документов вы неожиданно (причём именно неожиданно) можете обнаружить, что база начинает сильно тормозить. Например вы используете CouchDB, чтобы хранить документы с низкой продолжительностью жизни (сессии, лок-файлы, очереди).


    Хранить в CouchDB документы с низкой продолжительность жизни — не лучшая для него задача.
    А насчет тормозов вы имеете ввиду долгий ответ views при огромном количестве документов (милионы записей...)? Тогда stale=update_after в помощь, и будет он быстр даже на огромных колекциях.
    • 0
      >> А насчет тормозов вы имеете ввиду долгий ответ views при огромном количестве документов (милионы записей...)?
      Не только, но также вставка новых записей.

      Тогда stale=update_after в помощь, и будет он быстр даже на огромных колекциях.
      Будет быстр, но процессор всё-равно будет постоянно их пересчитывать, а это потери.
      • 0
        stale=ok не будет их перечитывать, если пересчитывание это потеря.
  • 0
    > И наконец, остаётся понять, насколько erlang быстр.

    Тут проблема не в Erlang'е, а в архитектуре CouchDB.
    • 0
      Конечно, но если это было бы на Си, проблема всплыла бы значительно позже.
      • +1
        Совсем необязательно
        • –1
          Разве есть в Си адекватные методы замера скорости? Если да, поделитесь?
          • 0
            Для любого нетривиального проекта замеры скорости проблема ;)
          • 0
            что подразумевается под адекватными методами?
  • –5
    база users-lock, дальше можно не читать. Архитектурный изъян в твоем мозге.
  • 0
    Например, вы используете CouchDB, чтобы хранить документы с низкой продолжительностью жизни (сессии, lock-файлы, очереди).

    каждый инструмент должен использоваться там, для чего он предназначен, можно и микроскопом гвозди забивать — неудобно, но это не значить? что это плохой инструмент…
    в целом статья не плохая
  • 0
    Кстати, CouchBase содержит встроенный memcached, который позволяет хранить документы с низкой продолжительностью жизни в отдельной области.

    нет ничего удивительного:
    memcached, MemBase и CouchBase — это все дети от одной фирмы
  • +1
    По ссылке — binary tree на Erlang берет 1/41 времени от PHP :D
    • 0
      Бинарное дерево, B дерево и B+ дерево — все разные.
      • +1
        Ага — настолько разные что их производительность на Erlang будет отличаться в 41 раз чтобы аргумент статье придать.
        • 0
          Помните о том, что тесты синтетические. В реальности помимо самой манипуляции деревом БД делает ещё много перестраховочных операций и тут уместнее сравнивать производительность в среднем. Кроме того, даже если рассматривать бинарное дерево, то Си всё равно быстрее в несколько раз.
  • +1
    Ага — настолько разные что их производительность на Erlang будет отличаться в 41 раз чтобы аргумент статье придать.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.