summaryrefslogtreecommitdiff
path: root/plugins/Dbx_mdb/src/mdbx/README.md
blob: c962da2dc3093535464431b2af1c128b48eac454 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
libmdbx
======================================
**The revised and extended descendant of [Symas LMDB](https://symas.com/lmdb/).**

*The Future will Positive. Всё будет хорошо.*
[![Build Status](https://travis-ci.org/leo-yuriev/libmdbx.svg?branch=master)](https://travis-ci.org/leo-yuriev/libmdbx)
[![Build status](https://ci.appveyor.com/api/projects/status/ue94mlopn50dqiqg/branch/master?svg=true)](https://ci.appveyor.com/project/leo-yuriev/libmdbx/branch/master)
[![Coverity Scan Status](https://scan.coverity.com/projects/12915/badge.svg)](https://scan.coverity.com/projects/reopen-libmdbx)

English version [by Google](https://translate.googleusercontent.com/translate_c?act=url&ie=UTF8&sl=ru&tl=en&u=https://github.com/leo-yuriev/libmdbx/tree/master)
and [by Yandex](https://translate.yandex.ru/translate?url=https%3A%2F%2Fgithub.com%2FReOpen%2Flibmdbx%2Ftree%2Fmaster&lang=ru-en).

### Project Status

**Now MDBX is under _active development_** and until 2018 is expected a big
change both of API and database format.  Unfortunately those update will lead to
loss of compatibility with previous versions.

The aim of this revolution in providing a clearer robust API and adding new
features, including the database properties.


## Содержание

- [Обзор](#Обзор)
  - [Сравнение с другими СУБД](#Сравнение-с-другими-СУБД)
  - [История & Acknowledgements](#История)
- [Основные свойства](#Основные-свойства)
- [Сравнение производительности](#Сравнение-производительности)
  - [Интегральная производительность](#Интегральная-производительность)
  - [Масштабируемость чтения](#Масштабируемость-чтения)
  - [Синхронная фиксация](#Синхронная-фиксация)
  - [Отложенная фиксация](#Отложенная-фиксация)
  - [Асинхронная фиксация](#Асинхронная-фиксация)
  - [Потребление ресурсов](#Потребление-ресурсов)
- [Недостатки и Компромиссы](#Недостатки-и-Компромиссы)
  - [Проблема долгих чтений](#Проблема-долгих-чтений)
  - [Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации)
- [Доработки и усовершенствования относительно LMDB](#Доработки-и-усовершенствования-относительно-lmdb)


## Обзор

_libmdbx_ - это встраиваемый key-value движок хранения со специфическим
набором свойств и возможностей, ориентированный на создание уникальных
легковесных решений с предельной производительностью.

_libmdbx_ позволяет множеству процессов совместно читать и обновлять
несколько key-value таблиц с соблюдением [ACID](https://ru.wikipedia.org/wiki/ACID),
при минимальных накладных расходах и амортизационной стоимости любых операций Olog(N).

_libmdbx_ обеспечивает
[serializability](https://en.wikipedia.org/wiki/Serializability)
изменений и согласованность данных после аварий. При этом транзакции
изменяющие данные никак не мешают операциям чтения и выполняются строго
последовательно с использованием единственного
[мьютекса](https://en.wikipedia.org/wiki/Mutual_exclusion).

_libmdbx_ позволяет выполнять операции чтения с гарантиями
[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom),
параллельно на каждом ядре CPU, без использования атомарных операций
и/или примитивов синхронизации.

_libmdbx_ не использует [LSM](https://en.wikipedia.org/wiki/Log-structured_merge-tree), а основан на [B+Tree](https://en.wikipedia.org/wiki/B%2B_tree) с [отображением](https://en.wikipedia.org/wiki/Memory-mapped_file) всех данных в память,
при этом текущая версия не использует [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging).
Это предопределяет многие свойства, в том числе удачные и противопоказанные сценарии использования.

### Сравнение с другими СУБД

Ввиду того, что в _libmdbx_ сейчас происходит революция, я посчитал лучшим решением
ограничится здесь ссылкой на [главу Comparison with other databases](https://github.com/coreos/bbolt#comparison-with-other-databases) в описании _BoltDB_.


### История

_libmdbx_ является результатом переработки и развития "Lightning Memory-Mapped Database",
известной под аббревиатурой
[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database).
Изначально доработка производилась в составе проекта
[ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP). Примерно за год
работы внесенные изменения приобрели самостоятельную ценность. Осенью
2015 доработанный движок был выделен в отдельный проект, который был
[представлен на конференции Highload++
2015](http://www.highload.ru/2015/abstracts/1831.html).

В начале 2017 года движок _libmdbx_ получил новый импульс развития,
благодаря использованию в [Fast Positive
Tables](https://github.com/leo-yuriev/libfpta), aka ["Позитивные
Таблицы"](https://github.com/leo-yuriev/libfpta) by [Positive
Technologies](https://www.ptsecurity.ru).


#### Acknowledgements

Howard Chu (Symas Corporation) - the author of LMDB,
from which originated the MDBX in 2015.

Martin Hedenfalk <martin@bzero.se> - the author of `btree.c` code,
which was used for begin development of LMDB.


Основные свойства
=================

_libmdbx_ наследует все ключевые возможности и особенности от
своего прародителя [LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database),
но с устранением ряда описываемых далее проблем и архитектурных недочетов.

1. Данные хранятся в упорядоченном отображении (ordered map), ключи всегда
   отсортированы, поддерживается выборка диапазонов (range lookups).

2. Данные отображается в память каждого работающего с БД процесса.
   К данным и ключам обеспечивается прямой доступ в памяти без необходимости их
   копирования.

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).
   Изменения строго последовательны и не блокируются чтением,
   конфликты между транзакциями не возможны.
   При этом гарантируется чтение только зафиксированных данных, см [relaxing serializability](https://en.wikipedia.org/wiki/Serializability).

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.
   > Для точности следует отметить, что "подключение к БД" (старт первой
   > читающей транзакции в потоке) и "отключение от БД" (закрытие БД или
   > завершение потока) требуют краткосрочного захвата блокировки для
   > регистрации/дерегистрации текущего потока в "таблице читателей".

5. Эффективное хранение дубликатов (ключей с несколькими
   значениями), без дублирования ключей, с сортировкой значений, в
   том числе целочисленных (для вторичных индексов).

6. Эффективная поддержка коротких ключей фиксированной длины, в том числе целочисленных.

7. Амортизационная стоимость любой операции Olog(N),
   [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
   Amplification Factor) и RAF (Read Amplification Factor) также Olog(N).

8. Нет [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) и журнала
   транзакций, после сбоев не требуется восстановление. Не требуется компактификация
   или какое-либо периодическое обслуживание. Поддерживается резервное копирование
   "по горячему", на работающей БД без приостановки изменения данных.

9. Отсутствует какое-либо внутреннее управление памятью или кэшированием. Всё
   необходимое штатно выполняет ядро ОС!


Сравнение производительности
============================

Все представленные ниже данные получены многократным прогоном тестов на
ноутбуке Lenovo Carbon-2, i7-4600U 2.1 ГГц, 8 Гб ОЗУ, с SSD-диском
SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Гб.

Исходный код бенчмарка [_IOArena_](https://github.com/pmwkaa/ioarena) и
сценарии тестирования [доступны на
github](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015).

--------------------------------------------------------------------------------

### Интегральная производительность

Показана соотнесенная сумма ключевых показателей производительности в трёх
бенчмарках:

   - Чтение/Поиск на машине с 4-мя процессорами;

   - Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями
     (вставка, чтение, обновление, удаление) в режиме **синхронной фиксации**
     данных (fdatasync при завершении каждой транзакции или аналог);

   - Транзакции с [CRUD](https://ru.wikipedia.org/wiki/CRUD)-операциями
     (вставка, чтение, обновление, удаление) в режиме **отложенной фиксации**
     данных (отложенная запись посредством файловой систем или аналог);

*Бенчмарк в режиме асинхронной записи не включен по двум причинам:*

  1. Такое сравнение не совсем правомочно, его следует делать с движками
     ориентированными на хранение данных в памяти ([Tarantool](https://tarantool.io/), [Redis](https://redis.io/)).

  2. Превосходство libmdbx становится еще более подавляющем, что мешает
     восприятию информации.

![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png)

--------------------------------------------------------------------------------

### Масштабируемость чтения

Для каждого движка показана суммарная производительность при
одновременном выполнении запросов чтения/поиска в 1-2-4-8 потоков на
машине с 4-мя физическими процессорами.

![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png)

--------------------------------------------------------------------------------

### Синхронная фиксация

 - Линейная шкала слева и темные прямоугольники соответствуют количеству
   транзакций в секунду, усредненному за всё время теста.

 - Логарифмическая шкала справа и желтые интервальные отрезки
   соответствуют времени выполнения транзакций. При этом каждый отрезок
   показывает минимальное и максимальное время затраченное на выполнение
   транзакций, а крестиком отмечено среднеквадратичное значение.

Выполняется **10.000 транзакций в режиме синхронной фиксации данных** на
диске. При этом требуется гарантия, что при аварийном выключении питания
(или другом подобном сбое) все данные будут консистентны и полностью
соответствовать последней завершенной транзакции. В _libmdbx_ в этом
режиме при фиксации каждой транзакции выполняется системный вызов
[fdatasync](https://linux.die.net/man/2/fdatasync).

В каждой транзакции выполняется комбинированная CRUD-операция (две
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
на пустой базе, а при завершении, в результате выполняемых действий, в
базе насчитывается 10.000 небольших key-value записей.

![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png)

--------------------------------------------------------------------------------

### Отложенная фиксация

 - Линейная шкала слева и темные прямоугольники соответствуют количеству
   транзакций в секунду, усредненному за всё время теста.

 - Логарифмическая шкала справа и желтые интервальные отрезки
   соответствуют времени выполнения транзакций. При этом каждый отрезок
   показывает минимальное и максимальное время затраченное на выполнение
   транзакций, а крестиком отмечено среднеквадратичное значение.

Выполняется **100.000 транзакций в режиме отложенной фиксации данных**
на диске. При этом требуется гарантия, что при аварийном выключении
питания (или другом подобном сбое) все данные будут консистентны на
момент завершения одной из транзакций, но допускается потеря изменений
из некоторого количества последних транзакций, что для многих движков
предполагает включение
[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) (write-ahead
logging) либо журнала транзакций, который в свою очередь опирается на
гарантию упорядоченности данных в журналируемой файловой системе.
_libmdbx_ при этом не ведет WAL, а передает весь контроль файловой
системе и ядру ОС.

В каждой транзакции выполняется комбинированная CRUD-операция (две
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
на пустой базе, а при завершении, в результате выполняемых действий, в
базе насчитывается 100.000 небольших key-value записей.

![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png)

--------------------------------------------------------------------------------

### Асинхронная фиксация

 - Линейная шкала слева и темные прямоугольники соответствуют количеству
   транзакций в секунду, усредненному за всё время теста.

 - Логарифмическая шкала справа и желтые интервальные отрезки
   соответствуют времени выполнения транзакций. При этом каждый отрезок
   показывает минимальное и максимальное время затраченное на выполнение
   транзакций, а крестиком отмечено среднеквадратичное значение.

Выполняется **1.000.000 транзакций в режиме асинхронной фиксации
данных** на диске. При этом требуется гарантия, что при аварийном
выключении питания (или другом подобном сбое) все данные будут
консистентны на момент завершения одной из транзакций, но допускается
потеря изменений из значительного количества последних транзакций. Во
всех движках при этом включался режим предполагающий минимальную
нагрузку на диск по-записи, и соответственно минимальную гарантию
сохранности данных. В _libmdbx_ при этом используется режим асинхронной
записи измененных страниц на диск посредством ядра ОС и системного
вызова [msync(MS_ASYNC)](https://linux.die.net/man/2/msync).

В каждой транзакции выполняется комбинированная CRUD-операция (две
вставки, одно чтение, одно обновление, одно удаление). Бенчмарк стартует
на пустой базе, а при завершении, в результате выполняемых действий, в
базе насчитывается 10.000 небольших key-value записей.

![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png)

--------------------------------------------------------------------------------

### Потребление ресурсов

Показана соотнесенная сумма использованных ресурсов в ходе бенчмарка в
режиме отложенной фиксации:

 - суммарное количество операций ввода-вывода (IOPS), как записи, так и
   чтения.

 - суммарное затраченное время процессора, как в режиме пользовательских процессов,
   так и в режиме ядра ОС.

 - использованное место на диске при завершении теста, после закрытия БД из тестирующего процесса,
   но без ожидания всех внутренних операций обслуживания (компактификации LSM и т.п.).

Движок _ForestDB_ был исключен при оформлении результатов, так как
относительно конкурентов многократно превысил потребление каждого из
ресурсов (потратил процессорное время на генерацию IOPS для заполнения
диска), что не позволяло наглядно сравнить показатели остальных движков
на одной диаграмме.

Все данные собирались посредством системного вызова
[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) и
сканированием директорий с данными.

![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png)

--------------------------------------------------------------------------------

## Недостатки и Компромиссы

1. Единовременно может выполняться не более одной транзакция изменения данных
   (один писатель). Зато все изменения всегда последовательны, не может быть
   конфликтов или логических ошибок при откате транзакций.

2. Отсутствие [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging)
   обуславливает относительно большой
   [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write
   Amplification Factor). Поэтому фиксация изменений на диске может быть
   достаточно дорогой и являться главным ограничением производительности
   при интенсивном изменении данных.
   > В качестве компромисса _libmdbx_ предлагает несколько режимов ленивой
   > и/или периодической фиксации. В том числе режим `MAPASYNC`, при котором
   > изменения происходят только в памяти и асинхронно фиксируются на диске
   > ядром ОС.
   >
   > Однако, следует воспринимать это свойство аккуратно и взвешенно.
   > Например, полная фиксация транзакции в БД с журналом потребует минимум 2
   > IOPS (скорее всего 3-4) из-за накладных расходов в файловой системе. В
   > _libmdbx_ фиксация транзакции также требует от 2 IOPS. Однако, в БД с
   > журналом кол-во IOPS будет меняться в зависимости от файловой системы,
   > но не от кол-ва записей или их объема. Тогда как в _libmdbx_ кол-во
   > будет расти логарифмически от кол-во записей/строк в БД (по высоте
   > b+tree).

3. [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)
   для реализации [MVCC](https://ru.wikipedia.org/wiki/MVCC) выполняется на
   уровне страниц в [B+
   дереве](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE).
   Поэтому изменение данных амортизационно требует копирования Olog(N)
   страниц, что расходует [пропускную способность оперативной
   памяти](https://en.wikipedia.org/wiki/Memory_bandwidth) и является
   основным ограничителем производительности в режиме `MAPASYNC`.
   > Этот недостаток неустраним, тем не менее следует дать некоторые пояснения.
   > Дело в том, что фиксация изменений на диске потребует гораздо более
   > значительного копирования данных в памяти и массы других затратных операций.
   > Поэтому обусловленное этим недостатком падение производительности становится
   > заметным только при отказе от фиксации изменений на диске.
   > Соответственно, корректнее сказать что _libmdbx_ позволяет
   > получить персистентность ценой минимального падения производительности.
   > Если же нет необходимости оперативно сохранять данные, то логичнее
   > использовать `std::map`.

4. В _LMDB_ существует проблема долгих чтений (приостановленных читателей),
   которая приводит к деградации производительности и переполнению БД.
   > В _libmdbx_ предложены средства для предотвращения, быстрого выхода из
   > некомфортной ситуации и устранения её последствий. Подробности ниже.

5. В _LMDB_ есть вероятность разрушения БД в режиме `WRITEMAP+MAPASYNC`.
   В _libmdbx_ для `WRITEMAP+MAPASYNC` гарантируется как сохранность базы,
   так и согласованность данных.
   > Дополнительно, в качестве альтернативы, предложен режим `UTTERLY_NOSYNC`.
   > Подробности ниже.


#### Проблема долгих чтений

*Следует отметить*, что проблема "сборки мусора" так или иначе
существует во всех СУБД (Vacuum в PostgreSQL). Однако в случае _libmdbx_
и LMDB она проявляется более остро, прежде всего из-за высокой
производительности, а также из-за намеренного упрощения внутренних
механизмов ради производительности.

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

* Изменение данных на фоне долгой операции чтения может
  приводить к исчерпанию места в БД.

* После чего любая попытка обновить данные будет приводить к
  ошибке `MAP_FULL` до завершения долгой операции чтения.

* Характерными примерами долгих чтений являются горячее
  резервное копирования и отладка клиентского приложения при
  активной транзакции чтения.

* В оригинальной _LMDB_ после этого будет наблюдаться
  устойчивая деградация производительности всех механизмов
  обратной записи на диск (в I/O контроллере, в гипервизоре,
  в ядре ОС).

* В _libmdbx_ предусмотрен механизм аварийного прерывания таких
  операций, а также режим `LIFO RECLAIM` устраняющий последующую
  деградацию производительности.

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

Другими словами, если обновление данных выполняется на фоне долгой
операции чтения, то вместо повторного использования "старых" ненужных
страниц будут выделяться новые, так как "старые" страницы составляют
снимок БД, который еще используется долгой операцией чтения.

В результате, при интенсивном изменении данных и достаточно длительной
операции чтения, в БД могут быть исчерпаны свободные страницы, что не
позволит создавать новые снимки/версии БД. Такая ситуация будет
сохраняться до завершения операции чтения, которая использует старый
снимок данных и препятствует повторному использованию страниц БД.

Однако, на этом проблемы не заканчиваются. После описанной ситуации, все
дополнительные страницы, которые были выделены пока переработка старых
была невозможна, будут участвовать в цикле выделения/освобождения до
конца жизни экземпляра БД. В оригинальной _LMDB_ этот цикл использования
страниц работает по принципу [FIFO](https://ru.wikipedia.org/wiki/FIFO).
Поэтому увеличение количества циркулирующий страниц, с точки зрения
механизмов кэширования и/или обратной записи, выглядит как увеличение
рабочего набор данных. Проще говоря, однократное попадание в ситуацию
"уснувшего читателя" приводит к устойчивому эффекту вымывания I/O кэша
при всех последующих изменениях данных.

Для устранения описанных проблемы в _libmdbx_ сделаны существенные
доработки, подробности ниже. Иллюстрации к проблеме "долгих чтений"
можно найти в [слайдах презентации](http://www.slideshare.net/leoyuriev/lmdb).

Там же приведен пример количественной оценки прироста производительности
за счет эффективной работы [BBWC](https://en.wikipedia.org/wiki/BBWC)
при включении `LIFO RECLAIM` в _libmdbx_.


#### Сохранность данных в режиме асинхронной фиксации

При работе в режиме `WRITEMAP+MAPSYNC` запись измененных страниц
выполняется ядром ОС, что имеет ряд преимуществ. Так например, при крахе
приложения, ядро ОС сохранит все изменения.

Однако, при аварийном отключении питания или сбое в ядре ОС, на диске
может быть сохранена только часть измененных страниц БД. При этом с большой
вероятностью может оказаться так, что будут сохранены мета-страницы со
ссылками на страницы с новыми версиями данных, но не сами новые данные.
В этом случае БД будет безвозвратна разрушена, даже если до аварии
производилась полная синхронизация данных (посредством
`mdbx_env_sync()`).

В _libmdbx_ эта проблема устранена путем полной переработки
пути записи данных:

* В режиме `WRITEMAP+MAPSYNC` _libmdbx_ не обновляет
  мета-страницы непосредственно, а поддерживает их теневые копии
  с переносом изменений после фиксации данных.

* При завершении транзакций, в зависимости от состояния
  синхронности данных между диском и оперативной память,
  _libmdbx_ помечает точки фиксации либо как сильные (strong),
  либо как слабые (weak). Так например, в режиме
  `WRITEMAP+MAPSYNC` завершаемые транзакции помечаются как
  слабые, а при явной синхронизации данных как сильные.

* В _libmdbx_ поддерживается не две, а три отдельные мета-страницы.
  Это позволяет выполнять фиксацию транзакций с формированием как
  сильной, так и слабой точки фиксации, без потери двух предыдущих
  точек фиксации (из которых одна может быть сильной, а вторая слабой).
  В результате, _libmdbx_ позволяет в произвольном порядке чередовать
  сильные и слабые точки фиксации без нарушения соответствующих
  гарантий в случае неожиданной системной аварии во время фиксации.

* При открытии БД выполняется автоматический откат к последней
  сильной фиксации. Этим обеспечивается гарантия сохранности БД.

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

Таким образом, в режиме безопасной асинхронной фиксации _libmdbx_ будет
всегда использовать новые страницы до исчерпания места в БД или до явного
формирования сильной точки фиксации посредством `mdbx_env_sync()`.
При этом суммарный трафик записи на диск будет примерно такой-же,
как если бы отдельно фиксировалась каждая транзакций.

В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC` когда
при системной аварии есть шанс полного разрушения БД как в LMDB.

В последующих версиях _libmdbx_ будут предусмотрены средства
для асинхронной записи данных на диск с автоматическим
формированием сильных точек фиксации.

--------------------------------------------------------------------------------

Доработки и усовершенствования относительно LMDB
================================================

1. Режим `LIFO RECLAIM`.

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

	В результате механизмы кэширования и обратной записи работают с
	максимально возможной эффективностью. В случае использования
	контроллера дисков или системы хранения с
	[BBWC](https://en.wikipedia.org/wiki/BBWC) возможно
	многократное увеличение производительности по записи
	(обновлению данных).

2. Обработчик `OOM-KICK`.

	Посредством `mdbx_env_set_oomfunc()` может быть установлен
	внешний обработчик (callback), который будет вызван при
	исчерпания свободных страниц из-за долгой операцией чтения.
	Обработчику будет передан PID и pthread_id виновника.
	В свою очередь	обработчик может предпринять одно из действий:

	* нейтрализовать виновника (отправить сигнал kill #9), если
	  долгое чтение выполняется сторонним процессом;

	* отменить или перезапустить проблемную операцию чтения, если
	  операция выполняется одним из потоков текущего процесса;

	* подождать некоторое время, в расчете что проблемная операция
	  чтения будет штатно завершена;

	* прервать текущую операцию изменения данных с возвратом кода
	  ошибки.

3. Гарантия сохранности БД в режиме `WRITEMAP+MAPSYNC`.

В текущей версии _libmdbx_ вам предоставляется выбор между безопасным
режимом (по умолчанию) асинхронной фиксации, и режимом `UTTERLY_NOSYNC`
когда при системной аварии есть шанс полного разрушения БД как в LMDB.
Для подробностей смотрите раздел
[Сохранность данных в режиме асинхронной фиксации](#Сохранность-данных-в-режиме-асинхронной-фиксации).

4. Возможность автоматического формирования контрольных точек
(сброса данных на диск) при накоплении заданного объёма изменений,
устанавливаемого функцией `mdbx_env_set_syncbytes()`.

5. Возможность получить отставание текущей транзакции чтения от
последней версии данных в БД посредством `mdbx_txn_straggler()`.

6. Утилита mdbx_chk для проверки БД и функция `mdbx_env_pgwalk()` для
обхода всех страниц БД.

7. Управление отладкой и получение отладочных сообщений посредством
`mdbx_setup_debug()`.

8. Возможность связать с каждой завершаемой транзакцией до 3
дополнительных маркеров посредством `mdbx_canary_put()`, и прочитать их
в транзакции чтения посредством `mdbx_canary_get()`.

9. Возможность узнать есть ли за текущей позицией курсора строка данных
посредством `mdbx_cursor_eof()`.

10. Возможность явно запросить обновление существующей записи, без
создания новой посредством флажка `MDBX_CURRENT` для `mdbx_put()`.

11. Возможность посредством `mdbx_replace()` обновить или удалить запись
с получением предыдущего значения данных, а также адресно изменить
конкретное multi-значение.

12. Поддержка ключей и значений нулевой длины, включая сортированные
дубликаты.

13. Исправленный вариант `mdbx_cursor_count()`, возвращающий корректное
количество дубликатов для всех типов таблиц и любого положения курсора.

14. Возможность открыть БД в эксклюзивном режиме посредством
`mdbx_env_open_ex()`, например в целях её проверки.

15. Возможность закрыть БД в "грязном" состоянии (без сброса данных и
формирования сильной точки фиксации) посредством `mdbx_env_close_ex()`.

16. Возможность получить посредством `mdbx_env_info()` дополнительную
информацию, включая номер самой старой версии БД (снимка данных),
который используется одним из читателей.

17. Функция `mdbx_del()` не игнорирует дополнительный (уточняющий)
аргумент `data` для таблиц без дубликатов (без флажка `MDBX_DUPSORT`), а
при его ненулевом значении всегда использует его для сверки с удаляемой
записью.

18. Возможность открыть dbi-таблицу, одновременно с установкой
компараторов для ключей и данных, посредством `mdbx_dbi_open_ex()`.

19. Возможность посредством `mdbx_is_dirty()` определить находятся ли
некоторый ключ или данные в "грязной" странице БД. Таким образом,
избегая лишнего копирования данных перед выполнением модифицирующих
операций (значения в размещенные "грязных" страницах могут быть
перезаписаны при изменениях, иначе они будут неизменны).

20. Корректное обновление текущей записи, в том числе сортированного
дубликата, при использовании режима `MDBX_CURRENT` в
`mdbx_cursor_put()`.

21. Все курсоры, как в транзакциях только для чтения, так и в пишущих,
могут быть переиспользованы посредством `mdbx_cursor_renew()` и ДОЛЖНЫ
ОСВОБОЖДАТЬСЯ ЯВНО.
  >
  > ## _ВАЖНО_, Обратите внимание!
  >
  > Это единственное изменение в API, которое значимо меняет
  > семантику управления курсорами и может приводить к утечкам
  > памяти. Следует отметить, что это изменение вынужденно.
  > Так устраняется неоднозначность с массой тяжких последствий:
  >
  >  - обращение к уже освобожденной памяти;
  >  - попытки повторного освобождения памяти;
  >  - memory corruption and segfaults.

22. Дополнительный код ошибки `MDBX_EMULTIVAL`, который возвращается из
`mdbx_put()` и `mdbx_replace()` при попытке выполнить неоднозначное
обновление или удаления одного из нескольких значений с одним ключом.

23. Возможность посредством `mdbx_get_ex()` получить значение по
заданному ключу, одновременно с количеством дубликатов.

24. Наличие функций `mdbx_cursor_on_first()` и `mdbx_cursor_on_last()`,
которые позволяют быстро выяснить стоит ли курсор на первой/последней
позиции.

25. При завершении читающих транзакций, открытые в них DBI-хендлы не
закрываются и не теряются при завершении таких транзакций посредством
`mdbx_txn_abort()` или `mdbx_txn_reset()`. Что позволяет избавится от ряда
сложно обнаруживаемых ошибок.

26. Генерация последовательностей посредством `mdbx_dbi_sequence()`.

27. Расширенное динамическое управление размером БД, включая выбор
размера страницы посредством `mdbx_env_set_geometry()`.

28. Три мета-страницы вместо двух, что позволяет гарантированно
консистентно обновлять слабые контрольные точки фиксации без риска
повредить крайнюю сильную точку фиксации.

29. В _libmdbx_ реализован автоматический возврат освобождающихся
страниц в область нераспределенного резерва в конце файла данных. При
этом уменьшается количество страниц загруженных в память и участвующих в
цикле обновления данных и записи на диск. Фактически _libmdbx_ выполняет
постоянную компактификацию данных, но не затрачивая на это
дополнительных ресурсов, а только освобождая их. При освобождении места
в БД, в случае наличия поддержки со стороны операционной системы и
установки соответствующих параметров геометрии базы данных, также будет
уменьшаться размер файла на диске.

--------------------------------------------------------------------------------

```
$ objdump -f -h -j .text libmdbx.so

libmdbx.so:     file format elf64-x86-64
architecture: i386:x86-64, flags 0x00000150:
HAS_SYMS, DYNAMIC, D_PAGED
start address 0x000030e0

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
 11 .text         00014661  000030e0  000030e0  000030e0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
```

```
$ objdump -C -T libmdbx.so | grep mdbx | sort

00004057 g    DF .text	0000003f  Base        mdbx_strerror_r
00004096 g    DF .text	00000031  Base        mdbx_strerror
00004207 g    DF .text	00000025  Base        mdbx_env_get_maxkeysize
0000422c g    DF .text	000000b8  Base        mdbx_env_create
000042e4 g    DF .text	0000001f  Base        mdbx_env_set_mapsize
00004f9f g    DF .text	00000037  Base        mdbx_env_set_maxdbs
00004fd6 g    DF .text	00000036  Base        mdbx_env_set_maxreaders
0000500c g    DF .text	00000027  Base        mdbx_env_get_maxreaders
00005033 g    DF .text	0000066a  Base        mdbx_env_open_ex
0000569d g    DF .text	00000008  Base        mdbx_env_open
000056a5 g    DF .text	00000096  Base        mdbx_env_close_ex
0000573b g    DF .text	00000007  Base        mdbx_env_close
00005742 g    DF .text	00000047  Base        mdbx_env_set_flags
00005789 g    DF .text	0000001d  Base        mdbx_env_get_flags
000057a6 g    DF .text	00000014  Base        mdbx_env_set_userctx
000057ba g    DF .text	0000000f  Base        mdbx_env_get_userctx
000057c9 g    DF .text	0000000d  Base        mdbx_env_set_assert
000057d6 g    DF .text	0000001d  Base        mdbx_env_get_path
000057f3 g    DF .text	00000018  Base        mdbx_env_get_fd
0000580b g    DF .text	00000056  Base        mdbx_env_stat
00005861 g    DF .text	00000276  Base        mdbx_env_info
00005ad7 g    DF .text	00000148  Base        mdbx_reader_list
0000656a g    DF .text	0000012a  Base        mdbx_dbi_stat
0000693a g    DF .text	00000146  Base        mdbx_env_copy2fd
00006a80 g    DF .text	0000012e  Base        mdbx_env_copy
00006bae g    DF .text	0000002a  Base        mdbx_reader_check
00006bd8 g    DF .text	000000f9  Base        mdbx_setup_debug
00006cd1 g    DF .text	00000033  Base        mdbx_env_set_syncbytes
00006d04 g    DF .text	00000023  Base        mdbx_env_set_oomfunc
00006d27 g    DF .text	00000019  Base        mdbx_env_get_oomfunc
00006d40 g    DF .text	00000121  Base        mdbx_env_pgwalk
0000ac60 g    DF .text	00000163  Base        mdbx_dkey
0000add0 g    DF .text	00000016  Base        mdbx_cmp
0000adf0 g    DF .text	00000016  Base        mdbx_dcmp
0000ae10 g    DF .text	00000271  Base        mdbx_env_sync
0000b090 g    DF .text	0000001b  Base        mdbx_txn_env
0000b0b0 g    DF .text	0000001c  Base        mdbx_txn_id
0000b0d0 g    DF .text	00000077  Base        mdbx_txn_reset
0000b150 g    DF .text	00000077  Base        mdbx_txn_abort
0000b1d0 g    DF .text	00000057  Base        mdbx_get_maxkeysize
0000b230 g    DF .text	000006b7  Base        mdbx_env_set_geometry
0000b8f0 g    DF .text	000000ef  Base        mdbx_cursor_count
0000b9e0 g    DF .text	000000ad  Base        mdbx_cursor_close
0000ba90 g    DF .text	0000001b  Base        mdbx_cursor_txn
0000bab0 g    DF .text	00000017  Base        mdbx_cursor_dbi
0000bad0 g    DF .text	0000007d  Base        mdbx_dbi_close
0000bb50 g    DF .text	000000cc  Base        mdbx_dbi_flags_ex
0000bc20 g    DF .text	00000038  Base        mdbx_dbi_flags
0000c250 g    DF .text	00000077  Base        mdbx_txn_renew
0000c2d0 g    DF .text	000004e5  Base        mdbx_txn_begin
0000dcb0 g    DF .text	00000128  Base        mdbx_cursor_open
0000dde0 g    DF .text	0000011d  Base        mdbx_cursor_renew
0000e970 g    DF .text	000000fc  Base        mdbx_get
0000ef00 g    DF .text	00000489  Base        mdbx_cursor_get
000125e0 g    DF .text	00000719  Base        mdbx_cursor_del
00012e00 g    DF .text	000000e4  Base        mdbx_del
00012ef0 g    DF .text	000002c3  Base        mdbx_drop
000131c0 g    DF .text	0000129e  Base        mdbx_cursor_put
000145d0 g    DF .text	000000a7  Base        mdbx_put
00014b60 g    DF .text	000000bf  Base        mdbx_dbi_open_ex
00014c20 g    DF .text	0000000b  Base        mdbx_dbi_open
00014c30 g    DF .text	00001347  Base        mdbx_txn_commit
00015f80 g    DF .text	00000105  Base        mdbx_txn_straggler
00016090 g    DF .text	000000e7  Base        mdbx_canary_put
00016180 g    DF .text	00000078  Base        mdbx_canary_get
00016200 g    DF .text	0000006e  Base        mdbx_cursor_on_first
00016270 g    DF .text	00000096  Base        mdbx_cursor_on_last
00016310 g    DF .text	00000066  Base        mdbx_cursor_eof
00016380 g    DF .text	00000504  Base        mdbx_replace
00016890 g    DF .text	0000017d  Base        mdbx_get_ex
00016a10 g    DF .text	000000a4  Base        mdbx_is_dirty
00016ac0 g    DF .text	00000120  Base        mdbx_dbi_sequence
00016be0 g    DF .text	00000064  Base        mdbx_cursor_get_attr
00016c50 g    DF .text	00000064  Base        mdbx_get_attr
00016cc0 g    DF .text	000000c7  Base        mdbx_put_attr
00016d90 g    DF .text	000000c7  Base        mdbx_cursor_put_attr
00016e60 g    DF .text	00000244  Base        mdbx_set_attr
```