From 97e1d9b6850dd15ada332b6a224427df6c1d018c Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 12 Jan 2017 21:41:52 +0300 Subject: [PATCH 1/7] mdbx: fix mdb_cursor_last (ITS#8557). This is a port of http://www.openldap.org/devel/gitweb.cgi?p=openldap.git;a=commit;h=d84dee516fa4cca41b5234e95c2105eb4737dfb3 HYC: Optimize mdb_page_search_root(PS_LAST) when cursor is already near last position, ignoring C_EOF flag for now. LY: Fixed C_EOF check. Don't ignore it, otherwise in some cases we got a "MDB_PAGE_NOTFOUND", instead of just "MDB_NOTFOUND". Change-Id: I2edbf6b64403abfa830a2fcb84162125634a85d0 --- CHANGES | 1 + mdb.c | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index 1cd39955..f7146039 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,7 @@ MDBX LMDB 0.9.20 Release Engineering Fix mdb_load with escaped plaintext (ITS#8558) + Fix mdb_cursor_last / mdb_put interaction (ITS#8557) LMDB 0.9.19 Release (2016/12/28) Fix mdb_env_cwalk cursor init (ITS#8424) diff --git a/mdb.c b/mdb.c index 16f8b691..c130d1d8 100644 --- a/mdb.c +++ b/mdb.c @@ -5636,8 +5636,17 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags) if (flags & (MDB_PS_FIRST|MDB_PS_LAST)) { i = 0; - if (flags & MDB_PS_LAST) + if (flags & MDB_PS_LAST) { i = NUMKEYS(mp) - 1; + /* if already init'd, see if we're already in right place */ + if (mc->mc_flags & C_INITIALIZED) { + if (mc->mc_ki[mc->mc_top] == i) { + mp = mc->mc_pg[mc->mc_top]; + mc->mc_top = mc->mc_snum++; + goto ready; + } + } + } } else { int exact; node = mdb_node_search(mc, key, &exact); @@ -5663,6 +5672,7 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags) if (unlikely(rc = mdb_cursor_push(mc, mp))) return rc; +ready: if (flags & MDB_PS_MODIFY) { if (unlikely((rc = mdb_page_touch(mc)) != 0)) return rc; @@ -6405,15 +6415,14 @@ mdb_cursor_last(MDB_cursor *mc, MDB_val *key, MDB_val *data) mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED|C_EOF); if (likely(!(mc->mc_flags & C_EOF))) { - if (!(mc->mc_flags & C_INITIALIZED) || mc->mc_top) { rc = mdb_page_search(mc, NULL, MDB_PS_LAST); if (unlikely(rc != MDB_SUCCESS)) return rc; } mdb_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); - } + mc->mc_ki[mc->mc_top] = NUMKEYS(mc->mc_pg[mc->mc_top]) - 1; mc->mc_flags |= C_INITIALIZED|C_EOF; leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); From 460eb64a6fb7819e7c469ae9239a2d6c75769e54 Mon Sep 17 00:00:00 2001 From: Howard Chu Date: Wed, 11 Jan 2017 10:33:28 +0000 Subject: [PATCH 2/7] mdbx: backport - Tweak cursor_next C_EOF check. Squash of relevant commits. HYC: Allow C_EOF flag to be stale. LY: Fixed parenthesis arrors. Change-Id: I5a26498763358a08f98481cacf582c881d59393d --- mdb.c | 53 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/mdb.c b/mdb.c index c130d1d8..8cac1718 100644 --- a/mdb.c +++ b/mdb.c @@ -5641,8 +5641,8 @@ mdb_page_search_root(MDB_cursor *mc, MDB_val *key, int flags) /* if already init'd, see if we're already in right place */ if (mc->mc_flags & C_INITIALIZED) { if (mc->mc_ki[mc->mc_top] == i) { - mp = mc->mc_pg[mc->mc_top]; mc->mc_top = mc->mc_snum++; + mp = mc->mc_pg[mc->mc_top]; goto ready; } } @@ -6005,15 +6005,20 @@ mdb_cursor_next(MDB_cursor *mc, MDB_val *key, MDB_val *data, MDB_cursor_op op) MDB_node *leaf; int rc; - if ((mc->mc_flags & C_EOF) || - ((mc->mc_flags & C_DEL) && op == MDB_NEXT_DUP)) { + if ((mc->mc_flags & C_DEL) && op == MDB_NEXT_DUP) return MDB_NOTFOUND; - } + if (!(mc->mc_flags & C_INITIALIZED)) return mdb_cursor_first(mc, key, data); mp = mc->mc_pg[mc->mc_top]; + if (mc->mc_flags & C_EOF) { + if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)-1) + return MDB_NOTFOUND; + mc->mc_flags ^= C_EOF; + } + if (mc->mc_db->md_flags & MDB_DUPSORT) { leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); if (F_ISSET(leaf->mn_flags, F_DUPDATA)) { @@ -6532,9 +6537,16 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, break; } rc = MDB_SUCCESS; - if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) || - (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF)) + if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) break; + if (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF) { + MDB_cursor *mx = &mc->mc_xcursor->mx_cursor; + if (mx->mc_ki[mx->mc_top] >= NUMKEYS(mx->mc_pg[mx->mc_top])-1) { + rc = MDB_NOTFOUND; + break; + } + mx->mc_flags ^= C_EOF; + } goto fetchm; case MDB_NEXT_MULTIPLE: if (unlikely(data == NULL)) { @@ -7878,14 +7890,23 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) return EINVAL; #if MDBX_MODE_ENABLED - MDB_page *mp = mc->mc_pg[mc->mc_top]; - int nkeys = NUMKEYS(mp); - if (!nkeys || mc->mc_ki[mc->mc_top] >= nkeys) { + if (!mc->mc_snum) { *countp = 0; return MDB_NOTFOUND; - } else if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) { + } + + MDB_page *mp = mc->mc_pg[mc->mc_top]; + if (mc->mc_flags & C_EOF) { + if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)) { + *countp = 0; + return MDB_NOTFOUND; + } + mc->mc_flags ^= C_EOF; + } + + if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) { *countp = 1; - } else { + } else { MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) *countp = 1; @@ -7895,12 +7916,18 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) *countp = mc->mc_xcursor->mx_db.md_entries; } #else - if (unlikely(mc->mc_xcursor == NULL)) + if (unlikely(mc->mc_xcursor == NULL)) return MDB_INCOMPATIBLE; - if (unlikely(!mc->mc_snum || (mc->mc_flags & C_EOF))) + if (!mc->mc_snum) return MDB_NOTFOUND; + if (mc->mc_flags & C_EOF) { + if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) + return MDB_NOTFOUND; + mc->mc_flags ^= C_EOF; + } + MDB_node *leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) { *countp = 1; From 71ae2aba8dfbffc046f4f8c9c64d29636045c992 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Thu, 12 Jan 2017 22:36:52 +0300 Subject: [PATCH 3/7] mdbx: refine mdbx_cursor_eof(). Change-Id: I786c5f2eedb273f44fd2ef5065d200f63dfec84b --- mdbx.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/mdbx.c b/mdbx.c index d2b14f51..ff2ba67a 100644 --- a/mdbx.c +++ b/mdbx.c @@ -366,7 +366,17 @@ int mdbx_cursor_eof(MDB_cursor *mc) if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) return MDB_VERSION_MISMATCH; - return (mc->mc_flags & C_INITIALIZED) ? 0 : 1; + if ((mc->mc_flags & C_INITIALIZED) == 0) + return 1; + + if (mc->mc_snum == 0) + return 1; + + if ((mc->mc_flags & C_EOF) + && mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) + return 1; + + return 0; } static int mdbx_is_samedata(const MDB_val* a, const MDB_val* b) { From ad7113419bae7b5fecc7649b8cddd08e00a23a75 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Fri, 13 Jan 2017 00:53:22 +0300 Subject: [PATCH 4/7] mdbx: CHANGES for glibc bugs #21031 and #21032. Change-Id: I621a68161dc4f47ed30c3557f19e26a4e1db42a1 --- CHANGES | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES b/CHANGES index f7146039..93486855 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ MDBX Add MDB_PREV_MULTIPLE Add error MDB_PROBLEM, replace some MDB_CORRUPTED + Workarounds for glibc bugs: #21031 and 21032. LMDB 0.9.20 Release Engineering Fix mdb_load with escaped plaintext (ITS#8558) From c4142c9a351b61f0b287a8020de3c97fa894eca1 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 18 Jan 2017 16:40:56 +0300 Subject: [PATCH 5/7] mdbx: refine README. --- README.md | 399 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 232 insertions(+), 167 deletions(-) diff --git a/README.md b/README.md index 930699ca..d11e1ad7 100644 --- a/README.md +++ b/README.md @@ -6,71 +6,81 @@ Extended LMDB, aka "Расширенная LMDB". [![Build Status](https://travis-ci.org/ReOpen/libmdbx.svg?branch=devel)](https://travis-ci.org/ReOpen/libmdbx) -English version by Google [is -here](https://translate.googleusercontent.com/translate_c?act=url&depth=1&hl=ru&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/devel). + +English version by Google [is here](https://translate.googleusercontent.com/translate_c?act=url&depth=1&hl=ru&ie=UTF8&prev=_t&rurl=translate.google.com&sl=ru&tl=en&u=https://github.com/ReOpen/libmdbx/tree/devel). + ## Кратко -libmdbx - это встраиваемый key-value движок хранения со специфическим -набором возможностей, которые при правильном применении позволяют создавать -уникальные решения с чемпионской производительностью. +_libmdbx_ - это встраиваемый key-value движок хранения со +специфическим набором возможностей, которые при правильном +применении позволяют создавать уникальные решения с чемпионской +производительностью, идеально сочетаясь с технологией +[MRAM](https://en.wikipedia.org/wiki/Magnetoresistive_random-access_memory). -libmdbx является форком [Symas Lightning Memory-Mapped -Database](https://symas.com/products/lightning-memory-mapped-database/) -(известной под аббревиатурой -[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database)), с -рядом существенных доработок, которые перечислены ниже. +_libmdbx_ обновляет совместно используемый набор данных, никак +не мешая при этом параллельным операциям чтения, не применяя +атомарных операций к самим данным, и обеспечивая +согласованность при аварийной остановке в любой момент. Поэтому +_libmdbx_ позволяя строить системы с линейным масштабированием +производительности чтения/поиска по ядрам CPU и амортизационной +стоимостью любых операций Olog(N). -Изначально модификация производилась в составе исходного кода проекта -[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за год работы -внесенные изменения приобрели самостоятельную ценность. - -Осенью 2015 доработанный движок был выделен в отдельный проект, который был -[представлен на конференции Highload++ -2015](http://www.highload.ru/2015/abstracts/1831.html). +### История +_libmdbx_ является потомком "Lightning Memory-Mapped Database", +известной под аббревиатурой +[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). +Изначально доработка производилась в составе проекта +[ReOpenLDAP](https://github.com/ReOpen/ReOpenLDAP). Примерно за +год работы внесенные изменения приобрели самостоятельную +ценность. Осенью 2015 доработанный движок был выделен в +отдельный проект, который был [представлен на конференции +Highload++ 2015](http://www.highload.ru/2015/abstracts/1831.html). -## Характеристики и ключевые особенности +Характеристики и ключевые особенности +===================================== -### Общее для оригинальной LMDB и MDBX +_libmdbx_ наследует все ключевые возможности и особенности от +своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), +с устранением описанных далее проблем и архитектурных недочетов. -* Данные хранятся в упорядоченном отображении (ordered map), ключи всегда - отсортированы, поддерживается выборка диапазонов (range lookups). +### Общее для оригинальной _LMDB_ и _libmdbx_ -* Транзакции согласно [ACID](https://ru.wikipedia.org/wiki/ACID), посредством - [MVCC](https://ru.wikipedia.org/wiki/MVCC) - и [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). +1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда + отсортированы, поддерживается выборка диапазонов (range lookups). -* Чтение [без - блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F), - без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F). - Мьютексы захватываются только при старте и завершении сеанса работы с БД. +2. Данные отображается в память каждого работающего с БД процесса. + Ключам и данным обеспечивается прямой доступ без необходимости их + копирования, так как они защищены транзакцией чтения и не изменяются. -* Читатели не конкурируют между собой, чтение масштабируется линейно по ядрам CPU. +3. Транзакции согласно + [ACID](https://ru.wikipedia.org/wiki/ACID), посредством + [MVCC](https://ru.wikipedia.org/wiki/MVCC) и + [COW](https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D0%BF%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BF%D1%80%D0%B8_%D0%B7%D0%B0%D0%BF%D0%B8%D1%81%D0%B8). + Изменения строго последовательны и не блокируются чтением, + конфликты между транзакциями не возможны. -* Изменения строго последовательны и не блокируются чтением, конфликты между - транзакциями не возможны. +4. Чтение и поиск [без блокировок](https://ru.wikipedia.org/wiki/%D0%9D%D0%B5%D0%B1%D0%BB%D0%BE%D0%BA%D0%B8%D1%80%D1%83%D1%8E%D1%89%D0%B0%D1%8F_%D1%81%D0%B8%D0%BD%D1%85%D1%80%D0%BE%D0%BD%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D1%8F), + без [атомарных операций](https://ru.wikipedia.org/wiki/%D0%90%D1%82%D0%BE%D0%BC%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F). + Читатели не блокируются операциями записи и не конкурируют + между собой, чтение масштабируется линейно по ядрам CPU. -* Амортизационная стоимость любой операции Olog(N), - [WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). +5. Эффективное хранение дубликатов (ключей с несколькими + значениями), без дублирования ключей, с сортировкой значений, в + том числе целочисленных (для вторичных индексов). -* Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала - транзакций, после сбоев не требуется восстановление. +6. Эффективная поддержка ключей фиксированной длины, в том числе целочисленных. -* Не требуется компактификация или какое-либо периодическое обслуживание. +7. Амортизационная стоимость любой операции Olog(N), + [WAF](https://en.wikipedia.org/wiki/Write_amplification) и RAF также Olog(N). -* Эффективное хранение дубликатов (ключей с несколькими значениями) с - сортировкой значений. +8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала + транзакций, после сбоев не требуется восстановление. Не требуется компактификация + или какое-либо периодическое обслуживание. Поддерживается резервное копирование + "по горячему", на работающей БД без приостановки изменения данных. -* Эффективная поддержка ключей фиксированной длины (uint32_t, uint64_t). - -* Поддержка горячего резервного копирования. - -* Файл БД отображается в память кажлого процесса, который работает с БД. К - ключам и данным обеспечивается прямой доступ (без копирования), они не - меняются до завершения транзакции чтения. - -* Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё - необходимое выполняет ядро ОС. +9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё + необходимое штатно выполняет ядро ОС. ### Недостатки и Компромиссы @@ -82,9 +92,9 @@ Database](https://symas.com/products/lightning-memory-mapped-database/) 2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) обуславливает относительно большой [WAF](https://en.wikipedia.org/wiki/Write_amplification). Поэтому фиксация - изменений на диске относительно дорога и является главным ограничителем для + изменений на диске может быть дорогой и является главным ограничителем для производительности по записи. В качестве компромисса предлагается несколько - режимов ленивой и/или периодической фиксации. В том числе режим `WRITEMAP`, + режимов ленивой и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором изменения происходят только в памяти и асинхронно фиксируются на диске ядром ОС. @@ -94,173 +104,228 @@ Database](https://symas.com/products/lightning-memory-mapped-database/) Поэтому изменение данных амортизационно требует копирования Olog(N) страниц, что расходует [пропускную способность оперативной памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является основным - ограничителем производительности в режиме `WRITEMAP`. + ограничителем производительности в режиме `MAPASYNC`. -4. Проблема долгих чтений (зависших читателей), см. ниже. +4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей), + которая приводит к деградации производительности и переполнению БД. + В _libmdbx_ предложены средства для предотвращения, выхода из проблемной + ситуации и устранения её последствий. Подробности ниже. -5. Вероятность разрушения БД в режиме `WRITEMAP`, см ниже. +5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`. + В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы, + так и согласованность данных. При этом также, в качестве альтернативы, + предложен режим `UTTERLY_NOSYNC`. Подробности ниже. #### Проблема долгих чтений -Понимание проблемы требует некоторых пояснений, которые изложены ниже, но -могут быть сложны для быстрого восприятия. Поэтому, тезисно: +Понимание проблемы требует некоторых пояснений, которые +изложены ниже, но могут быть сложны для быстрого восприятия. +Поэтому, тезисно: -* Изменение данных на фоне долгой операции чтения может приводить к исчерпанию - места в БД. +* Изменение данных на фоне долгой операции чтения может + приводить к исчерпанию места в БД. -* После чего любая попытка обновить данные будет приводить к ошибке `MAP_FULL` - до завершения долгой операции чтения. +* После чего любая попытка обновить данные будет приводить к + ошибке `MAP_FULL` до завершения долгой операции чтения. -* Характерными примерами долгих чтений являются горячее резервное копирования - и отладка клиентского приложения при активной транзакции чтения. +* Характерными примерами долгих чтений являются горячее + резервное копирования и отладка клиентского приложения при + активной транзакции чтения. -* В оригинальной LMDB после этого будет наблюдаться устойчивая деградация - производительности всех механизмов обратной записи на диск (в I/O контроллере, - в гипервизоре, в ядре ОС). +* В оригинальной _LMDB_ после этого будет наблюдаться + устойчивая деградация производительности всех механизмов + обратной записи на диск (в I/O контроллере, в гипервизоре, + в ядре ОС). -* В MDBX предусмотрен механизм аварийного прерывания таких операций, а также - режим `LIFO RECLAIM` устраняющий последующую деградацию производительности. +* В _libmdbx_ предусмотрен механизм аварийного прерывания таких + операций, а также режим `LIFO RECLAIM` устраняющий последующую + деградацию производительности. -Операции чтения выполняются в контексте снимка данных (версии БД), который был -актуальным на момент старта транзакции чтения. Такой читаемый снимок -поддерживается неизменным до завершения операции. В свою очередь, это не -позволяет повторно использовать страницы БД в последующих версиях (снимках -БД). +Операции чтения выполняются в контексте снимка данных (версии +БД), который был актуальным на момент старта транзакции чтения. +Такой читаемый снимок поддерживается неизменным до завершения +операции. В свою очередь, это не позволяет повторно +использовать страницы БД в последующих версиях (снимках БД). -Другими словами, если обновление данных выполняется на фоне долгой операции -чтения, то вместо повторного использования "старых" ненужных страниц будут -выделяться новые, так как "старые" страницы составляют снимок БД, который еще +Другими словами, если обновление данных выполняется на фоне +долгой операции чтения, то вместо повторного использования +"старых" ненужных страниц будут выделяться новые, так как +"старые" страницы составляют снимок БД, который еще используется долгой операцией чтения. -В результате, при интенсивном изменении данных и достаточно длительной -операции чтения, в БД могут быть исчерпаны свободные страницы, что не позволит -создавать новые снимки/версии БД. Такая ситуация будет сохраняться до -завершения операции чтения, которая использует старый снимок данных и -препятствует повторному использованию страниц БД. +В результате, при интенсивном изменении данных и достаточно +длительной операции чтения, в БД могут быть исчерпаны свободные +страницы, что не позволит создавать новые снимки/версии БД. +Такая ситуация будет сохраняться до завершения операции чтения, +которая использует старый снимок данных и препятствует +повторному использованию страниц БД. -Однако, на этом проблемы не заканчиваются. После описанной ситуации, все -дополнительные страницы, которые были выделены пока переработка старых была -невозможна, будут участвовать в цикле выделения/освобождения до конца жизни -экземпляра БД. В оригинальной LMDB этот цикл использования страниц работает по -принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому увеличение -количества циркулирующий страниц, с точки зрения механизмов кэширования и/или -обратной записи, выглядит как увеличение рабочего набор данных. Проще говоря, -однократное попадание в ситуацию "уснувшего читателя" приводит к устойчивому -эффекту вымывания I/O кэша при всех последующих изменениях данных. +Однако, на этом проблемы не заканчиваются. После описанной +ситуации, все дополнительные страницы, которые были выделены +пока переработка старых была невозможна, будут участвовать в +цикле выделения/освобождения до конца жизни экземпляра БД. В +оригинальной _LMDB_ этот цикл использования страниц работает по +принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO). Поэтому +увеличение количества циркулирующий страниц, с точки зрения +механизмов кэширования и/или обратной записи, выглядит как +увеличение рабочего набор данных. Проще говоря, однократное +попадание в ситуацию "уснувшего читателя" приводит к +устойчивому эффекту вымывания I/O кэша при всех последующих +изменениях данных. -Для решения описанных проблемы в MDBX сделаны существенные доработки, см. -ниже. Иллюстрации к проблеме "долгих чтений" можно найти в [слайдах -презентации](http://www.slideshare.net/leoyuriev/lmdb). Там же приведен пример -количественной оценки прироста производительности за счет эффективной работы -[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO RECLAIM` в -MDBX. +Для устранения описанных проблемы в _libmdbx_ сделаны +существенные доработки, подробности ниже. Иллюстрации к +проблеме "долгих чтений" можно найти в [слайдах +презентации](http://www.slideshare.net/leoyuriev/lmdb). +Там же приведен пример количественной оценки прироста +производительности за счет эффективной работы +[BBWC](https://en.wikipedia.org/wiki/BBWC) при включении `LIFO +RECLAIM` в _libmdbx_. -## Доработки MDBX +#### Вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC` + +При работе в режиме `WRITEMAP+MAPSYNC` запись измененных +страниц выполняется ядром ОС, что имеет ряд преимуществ. Так +например, при крахе приложения, ядро ОС сохранит все изменения. + +Однако, при аварийном отключении питания или сбое в ядре ОС, на +диске будет сохранена только часть измененных страниц БД. При +этом с большой вероятностью может оказаться так, что будут +сохранены мета-страницы со ссылками на страницы с новыми +версиями данных, но не сами новые данные. В этом случае БД +будет безвозвратна разрушена, даже если до аварии производилась +полная синхронизация данных (посредством `mdb_env_sync()`). + +В _libmdbx_ эта проблема устранена, подробности ниже. + + +Доработки _libmdbx_ +=================== 1. Режим `LIFO RECLAIM`. - Для повторного использования выбираются не самые старые, а самые новые - страницы из доступных. За счет этого цикл использования страниц всегда - имеет минимальную длину и не зависит от общего числа выделенных страниц. + Для повторного использования выбираются не самые старые, а + самые новые страницы из доступных. За счет этого цикл + использования страниц всегда имеет минимальную длину и не + зависит от общего числа выделенных страниц. - В результате механизмы кэширования и обратной записи работают с - максимально возможной эффективностью. В случае использования контроллера - дисков или системы хранения с [BBWC](https://en.wikipedia.org/wiki/BBWC) - возможно многократное увеличение производительности по записи - (обновлению данных). + В результате механизмы кэширования и обратной записи работают с + максимально возможной эффективностью. В случае использования + контроллера дисков или системы хранения с + [BBWC](https://en.wikipedia.org/wiki/BBWC) возможно + многократное увеличение производительности по записи + (обновлению данных). 2. Обработчик `OOM-KICK`. - Посредством `mdbx_env_set_oomfunc()` может быть - установлен внешний обработчик (callback), который будет вызван при исчерпания - свободных страниц из-за долгой операцией чтения. Обработчику будет передан PID - и pthread_id. В свою очередь обработчик может предпринять одно из действий: + Посредством `mdbx_env_set_oomfunc()` может быть установлен + внешний обработчик (callback), который будет вызван при + исчерпания свободных страниц из-за долгой операцией чтения. + Обработчику будет передан PID и pthread_id. В свою очередь + обработчик может предпринять одно из действий: - * отправить сигнал kill (#9), если долгое чтение выполняется сторонним процессом; - * отменить или перезапустить проблемную операцию чтения, если операция - выполняется одним из потоков текущего процесса; - * подождать некоторое время, в расчете что проблемная операция чтения будет - штатно завершена; - * перервать текущую операцию изменения данных с возвратом кода ошибки. + * отправить сигнал kill (#9), если долгое чтение выполняется + сторонним процессом; -3. Гарантия сохранности БД в режиме `WRITEMAP`. + * отменить или перезапустить проблемную операцию чтения, если + операция выполняется одним из потоков текущего процесса; - При работе в режиме `WRITEMAP` запись измененных страниц выполняется ядром ОС, - что имеет ряд преимуществ. Так например, при крахе приложения, ядро ОС - сохранит все изменения. + * подождать некоторое время, в расчете что проблемная операция + чтения будет штатно завершена; - Однако, при аварийном отключении питания или сбое в ядре ОС, на диске будет - сохранена только часть измененных страниц БД. При этом с большой вероятностью - может оказаться так, что будут сохранены мета-страницы со ссылками на страницы - с новыми версиями данных, но не сами новые данные. В этом случае БД будет - безвозвратна разрушена, даже если до аварии производилась полная синхронизация - данных (посредством `mdb_env_sync()`). + * перервать текущую операцию изменения данных с возвратом кода + ошибки. - В MDBX эта проблема решена путем полной переработки пути записи данных: +3. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`. - * В режиме `WRITEMAP` MDBX не обновляет мета-страницы непосредственно, - а поддерживает их теневые копии с переносом изменений после фиксации - данных. + При работе в режиме `WRITEMAP+MAPSYNC` запись измененных + страниц выполняется ядром ОС, что имеет ряд преимуществ. Так + например, при крахе приложения, ядро ОС сохранит все изменения. - * При завершении транзакций, в зависимости от состояния - синхронности данных между диском и оперативной память, MDBX помечает - точки фиксации либо как сильные (strong), либо как слабые (weak). Так - например, в режиме `WRITEMAP` завершаемые транзакции помечаются как - слабые, а при явной синхронизации данных как сильные. + Однако, при аварийном отключении питания или сбое в ядре ОС, на + диске будет сохранена только часть измененных страниц БД. При + этом с большой вероятностью может оказаться так, что будут + сохранены мета-страницы со ссылками на страницы с новыми + версиями данных, но не сами новые данные. В этом случае БД + будет безвозвратна разрушена, даже если до аварии производилась + полная синхронизация данных (посредством `mdb_env_sync()`). - * При открытии БД - выполняется автоматический откат к последней сильной фиксации. Этим - обеспечивается гарантия сохранности БД. + В _libmdbx_ эта проблема устранена путем полной переработки + пути записи данных: - К сожалению, такая гарантия надежности не дается бесплатно. Для сохранности - данных, страницы формирующие крайний снимок с сильной фиксацией, не должны - повторно использоваться (перезаписываться) до формирования следующей сильной - точки фиксации. Таким образом, крайняя точки фиксации создает описанный выше - эффект "долгого чтения", с разницей в том, что при исчерпании свободных - страниц автоматически будет сформирована новая точка сильной фиксации. + * В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет + мета-страницы непосредственно, а поддерживает их теневые копии + с переносом изменений после фиксации данных. - В последующих версиях MDBX будут предусмотрены средства для асинхронной записи - данных на диск с формированием сильных точек фиксации. + * При завершении транзакций, в зависимости от состояния + синхронности данных между диском и оперативной память, + _libmdbx_ помечает точки фиксации либо как сильные (strong), + либо как слабые (weak). Так например, в режиме + `WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как + слабые, а при явной синхронизации данных как сильные. -4. Возможность автоматического формирования контрольных точек (сброса данных -на диск) при накоплении заданного объёма изменений, устанавливаемого функцией + * При открытии БД выполняется автоматический откат к последней + сильной фиксации. Этим обеспечивается гарантия сохранности БД. + + К сожалению, такая гарантия надежности не дается бесплатно. Для + сохранности данных, страницы формирующие крайний снимок с + сильной фиксацией, не должны повторно использоваться + (перезаписываться) до формирования следующей сильной точки + фиксации. Таким образом, крайняя точка фиксации создает + описанный выше эффект "долгого чтения". Разница же здесь в том, + что при исчерпании свободных страниц ситуация будет + автоматически исправлена, посредством записи изменений на диск + и формированием новой сильной точки фиксации. + + В последующих версиях _libmdbx_ будут предусмотрены средства + для асинхронной записи данных на диск с автоматическим + формированием сильных точек фиксации. + +4. Возможность автоматического формирования контрольных точек +(сброса данных на диск) при накоплении заданного объёма +изменений, устанавливаемого функцией `mdbx_env_set_syncbytes()`. -5. Возможность получить отставание текущей транзакции чтения от последней -версии данных в БД посредством `mdbx_txn_straggler()`. +5. Возможность получить отставание текущей транзакции чтения от +последней версии данных в БД посредством +`mdbx_txn_straggler()`. -6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для обхода -всех страниц БД. +6. Утилита mdbx_chk для проверки БД и функция +`mdbx_env_pgwalk()` для обхода всех страниц БД. -7. Управление отладкой и получение отладочных сообщений посредством -`mdbx_setup_debug()`. +7. Управление отладкой и получение отладочных сообщений +посредством `mdbx_setup_debug()`. -8. Возможность связать с каждой завершаемой транзакцией до 3 дополнительных -маркеров посредством `mdbx_canary_put()`, и прочитать их в транзакции чтения -посредством `mdbx_canary_get()`. +8. Возможность связать с каждой завершаемой транзакцией до 3 +дополнительных маркеров посредством `mdbx_canary_put()`, и +прочитать их в транзакции чтения посредством +`mdbx_canary_get()`. -9. Возможность узнать есть ли за текущей позицией курсора строка данных -посредством `mdbx_cursor_eof()`. +9. Возможность узнать есть ли за текущей позицией курсора +строка данных посредством `mdbx_cursor_eof()`. -10. Возможность явно запросить обновление существующей записи, без создания -новой посредством флажка `MDB_CURRENT` для `mdb_put()`. +10. Возможность явно запросить обновление существующей записи, +без создания новой посредством флажка `MDB_CURRENT` для +`mdb_put()`. -11. Возможность обновить или удалить запись с получением предыдущего значения -данных посредством `mdbx_replace()`. +11. Возможность обновить или удалить запись с получением +предыдущего значения данных посредством `mdbx_replace()`. 12. Поддержка ключей нулевого размера. -13. Исправленный вариант `mdb_cursor_count()`, возвращающий корректное -количество дубликатов для всех типов таблиц и любого положения курсора. +13. Исправленный вариант `mdb_cursor_count()`, возвращающий +корректное количество дубликатов для всех типов таблиц и любого +положения курсора. 14. Возможность открыть БД в эксклюзивном режиме посредством `mdbx_env_open_ex()`, например в целях её проверки. -15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и -формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`. +15. Возможность закрыть БД в "грязном" состоянии (без сброса +данных и формирования сильной точки фиксации) посредством +`mdbx_env_close_ex()`. -16. Возможность получить посредством `mdbx_env_info()` дополнительную -информацию, включая номер самой старой версии БД (снимка данных), который -используется одним из читателей. +16. Возможность получить посредством `mdbx_env_info()` +дополнительную информацию, включая номер самой старой версии БД +(снимка данных), который используется одним из читателей. From 14b466bd2def54cb0516706e8de60e4b48eb1ad1 Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 18 Jan 2017 17:15:51 +0300 Subject: [PATCH 6/7] mdbx: fix cursor EOF tricks. LY: Don't touch cursor's C_EOF flag in functions that don't moves the cursor. HYC: Further fix f8ce8a82717ddefdc912fa47c07f1bdee2a3336b Fully revert the change to GET_MULTIPLE Change-Id: Ia8e6dc0af04e5c7b2fd1a2fc9632ccfecc01819a --- mdb.c | 30 +++++++++--------------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/mdb.c b/mdb.c index 8cac1718..092a0e89 100644 --- a/mdb.c +++ b/mdb.c @@ -6537,16 +6537,9 @@ mdb_cursor_get(MDB_cursor *mc, MDB_val *key, MDB_val *data, break; } rc = MDB_SUCCESS; - if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) + if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) || + (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF)) break; - if (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF) { - MDB_cursor *mx = &mc->mc_xcursor->mx_cursor; - if (mx->mc_ki[mx->mc_top] >= NUMKEYS(mx->mc_pg[mx->mc_top])-1) { - rc = MDB_NOTFOUND; - break; - } - mx->mc_flags ^= C_EOF; - } goto fetchm; case MDB_NEXT_MULTIPLE: if (unlikely(data == NULL)) { @@ -7896,12 +7889,9 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) } MDB_page *mp = mc->mc_pg[mc->mc_top]; - if (mc->mc_flags & C_EOF) { - if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)) { - *countp = 0; - return MDB_NOTFOUND; - } - mc->mc_flags ^= C_EOF; + if ((mc->mc_flags & C_EOF) && mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)) { + *countp = 0; + return MDB_NOTFOUND; } if (mc->mc_xcursor == NULL || IS_LEAF2(mp)) { @@ -7922,13 +7912,11 @@ mdb_cursor_count(MDB_cursor *mc, size_t *countp) if (!mc->mc_snum) return MDB_NOTFOUND; - if (mc->mc_flags & C_EOF) { - if (mc->mc_ki[mc->mc_top] >= NUMKEYS(mc->mc_pg[mc->mc_top])) - return MDB_NOTFOUND; - mc->mc_flags ^= C_EOF; - } + MDB_page *mp = mc->mc_pg[mc->mc_top]; + if ((mc->mc_flags & C_EOF) && mc->mc_ki[mc->mc_top] >= NUMKEYS(mp)) + return MDB_NOTFOUND; - MDB_node *leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + MDB_node *leaf = NODEPTR(mp, mc->mc_ki[mc->mc_top]); if (!F_ISSET(leaf->mn_flags, F_DUPDATA)) { *countp = 1; } else { From 5bb931f7c4b06028a146fffb2335e1c15f658e4d Mon Sep 17 00:00:00 2001 From: Leo Yuriev Date: Wed, 18 Jan 2017 19:05:09 +0300 Subject: [PATCH 7/7] mdbx: fix xflags inside mdb_cursor_put(). Fix xflags preparation bug from 2956095c6ded72d22e10e6d1cad4a5410ea52994 --- mdb.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mdb.c b/mdb.c index 092a0e89..08dccaaa 100644 --- a/mdb.c +++ b/mdb.c @@ -7143,15 +7143,13 @@ put_sub: xdata.mv_size = 0; xdata.mv_data = ""; leaf = NODEPTR(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); - xflags = MDB_NOSPILL; - if (flags & MDB_NODUPDATA) - xflags |= MDB_NOOVERWRITE; - if (flags & MDB_APPENDDUP) - xflags |= MDB_APPEND; if (flags & MDB_CURRENT) { - xflags |= MDB_CURRENT; + xflags = (flags & MDB_NODUPDATA) ? + MDB_CURRENT|MDB_NOOVERWRITE|MDB_NOSPILL : MDB_CURRENT|MDB_NOSPILL; } else { mdb_xcursor_init1(mc, leaf); + xflags = (flags & MDB_NODUPDATA) ? + MDB_NOOVERWRITE|MDB_NOSPILL : MDB_NOSPILL; } if (sub_root) mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root; @@ -7185,6 +7183,8 @@ put_sub: } } ecount = mc->mc_xcursor->mx_db.md_entries; + if (flags & MDB_APPENDDUP) + xflags |= MDB_APPEND; rc = mdb_cursor_put(&mc->mc_xcursor->mx_cursor, data, &xdata, xflags); if (flags & F_SUBDATA) { void *db = NODEDATA(leaf);