mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
521
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51e7cb6d39 | ||
|
|
3e417a6607 | ||
|
|
d382ae7de6 | ||
|
|
53d66df0c4 | ||
|
|
a993c0139d | ||
|
|
e80709a33a | ||
|
|
7ffce2805b | ||
|
|
09b6bf828a | ||
|
|
1cbc96d236 | ||
|
|
82137f0ce8 | ||
|
|
2e3d407778 | ||
|
|
4d51feab0b | ||
|
|
118058ba69 | ||
|
|
88c3e26cc0 | ||
|
|
620823f88b | ||
|
|
de6cf95a53 | ||
|
|
7429b20e39 | ||
|
|
fa8c050e9f | ||
|
|
7a6353bd1c | ||
|
|
70282cf876 | ||
|
|
2ce8f63f81 | ||
|
|
d5585bb605 | ||
|
|
f1b7b790c9 | ||
|
|
8823487ff7 | ||
|
|
6383e42362 | ||
|
|
61785c73ed | ||
|
|
93d52696bf | ||
|
|
d1b26507bd | ||
|
|
ccb761364d | ||
|
|
1139422dfb | ||
|
|
bef95be5d8 | ||
|
|
cc340268e9 | ||
|
|
ecfca1ff59 | ||
|
|
da82aab126 | ||
|
|
cc118b0e6f | ||
|
|
0cdaa1a804 | ||
|
|
449627f0ea | ||
|
|
6f7b7f91b5 | ||
|
|
e003d22526 | ||
|
|
09e5d7af8c | ||
|
|
2256dab135 | ||
|
|
7153153e4b | ||
|
|
f6156fb558 | ||
|
|
c5302a8a58 | ||
|
|
e476d0e252 | ||
|
|
a3a3f5497c | ||
|
|
a277b0f2b7 | ||
|
|
b560fc9f62 | ||
|
|
e69f6e8629 | ||
|
|
0de710f5b8 | ||
|
|
f021f1d9e1 | ||
|
|
0a3db28d98 | ||
|
|
20c508c1ed | ||
|
|
3462c94be7 | ||
|
|
6a3eebbab0 | ||
|
|
d518fe1da6 | ||
|
|
8bc41f4f5d | ||
|
|
c364eb42b5 | ||
|
|
b864bc9b5b | ||
|
|
0a2354ca8f | ||
|
|
62277e15c3 | ||
|
|
9cb4856dbd | ||
|
|
5d68243e61 | ||
|
|
1ccdc2c337 | ||
|
|
92b1a68d87 | ||
|
|
680864ae54 | ||
|
|
15f55e5e06 | ||
|
|
db2445ad24 | ||
|
|
1209b6db5c | ||
|
|
2ac988c67e | ||
|
|
d060421c77 | ||
|
|
bf937cf15b | ||
|
|
ec5843dca9 | ||
|
|
3ae0047278 | ||
|
|
c287c098a4 | ||
|
|
d6336563e5 | ||
|
|
ad05cbb182 | ||
|
|
7eb292da14 | ||
|
|
dfbe52e099 | ||
|
|
30649dc6a1 | ||
|
|
90eca1e616 | ||
|
|
243220d08a | ||
|
|
4624edc440 | ||
|
|
aba3409740 | ||
|
|
f4a030ce81 | ||
|
|
b092977643 | ||
|
|
828211e901 | ||
|
|
8ada876dfe | ||
|
|
e9c31ab159 | ||
|
|
1960e73e21 | ||
|
|
5263da6396 | ||
|
|
ab446dc22d | ||
|
|
c178da053b | ||
|
|
97307d888f | ||
|
|
0454f781c2 | ||
|
|
989d12313c | ||
|
|
8eb1d445c3 | ||
|
|
4e7a182d09 | ||
|
|
c88c57cde1 | ||
|
|
6a30b98fdc | ||
|
|
ba6ee1f749 | ||
|
|
b3c5351335 | ||
|
|
d08d05cb62 | ||
|
|
dadf01672a | ||
|
|
74748611a8 | ||
|
|
3c380fdffd | ||
|
|
b555ed30a4 | ||
|
|
8a04ee4fd1 | ||
|
|
ee1c802675 | ||
|
|
0a0fad447b | ||
|
|
3f1bb07351 | ||
|
|
ef29d2a234 | ||
|
|
fe228da0a9 | ||
|
|
9fc72d6f16 | ||
|
|
d62af7fa4f | ||
|
|
945f618ba5 | ||
|
|
e78715c29a | ||
|
|
a0931b3185 | ||
|
|
821e0b1683 | ||
|
|
1336a7742d | ||
|
|
8feee28020 | ||
|
|
8f8eb4f1c0 | ||
|
|
81736d8afe | ||
|
|
2f29991701 | ||
|
|
88d8b2a2f5 | ||
|
|
4b124fb9d3 | ||
|
|
c3401846ef | ||
|
|
cb5b8f2090 | ||
|
|
cd5092e168 | ||
|
|
a247617e6f | ||
|
|
6e5b341e8b | ||
|
|
e502839e25 | ||
|
|
ba8aa8fdc8 | ||
|
|
b78ed0460b | ||
|
|
813719e952 | ||
|
|
e62a763752 | ||
|
|
1edac32b77 | ||
|
|
5eccf0b9d5 | ||
|
|
6efa8e270c | ||
|
|
5400800a56 | ||
|
|
ab5ab36ac2 | ||
|
|
ec225d2e97 | ||
|
|
ab43ff58b5 | ||
|
|
77dc069eb9 | ||
|
|
f3fe6f883b | ||
|
|
4bdf06e78f | ||
|
|
3073b1c573 | ||
|
|
e6605e5302 | ||
|
|
3fe0937180 | ||
|
|
e2d4b0efb1 | ||
|
|
b8eb32f8cf | ||
|
|
9f7ccc8445 | ||
|
|
3f666f79af | ||
|
|
65cd6cd4b6 | ||
|
|
46acdc9883 | ||
|
|
439855a774 | ||
|
|
4927b4e662 | ||
|
|
7fcc1d0ddf | ||
|
|
0e6e405fec | ||
|
|
d938226af4 | ||
|
|
65d431639b | ||
|
|
1039133f2d | ||
|
|
c7226428dd | ||
|
|
d6fdd59c63 | ||
|
|
35d8e65a04 | ||
|
|
d46e832e94 | ||
|
|
edc258127e | ||
|
|
ec0167eecb | ||
|
|
0ea7170d7d | ||
|
|
7e3d3326ce | ||
|
|
7291a3f813 | ||
|
|
c615689bb5 | ||
|
|
6f5ba0bf5b | ||
|
|
f2f034ef3b | ||
|
|
5568aec421 | ||
|
|
47ad6b81ff | ||
|
|
f1cb83fcf4 | ||
|
|
b9873162f0 | ||
|
|
4decff6fa8 | ||
|
|
46e599fc6b | ||
|
|
266d85fbec | ||
|
|
dc360df81e | ||
|
|
bf6f03f3cd | ||
|
|
af92d4ad11 | ||
|
|
d0f1b49ab6 | ||
|
|
68829ed89c | ||
|
|
43549c7d59 | ||
|
|
ba295cda29 | ||
|
|
e446d14093 | ||
|
|
6d7e3b9faf | ||
|
|
45828c7215 | ||
|
|
0a7ba0e548 | ||
|
|
9c2f64e148 | ||
|
|
204af1eccc | ||
|
|
d4da02d147 | ||
|
|
a53c571d2d | ||
|
|
b010116d82 | ||
|
|
0c6e8be9e2 | ||
|
|
199405192d | ||
|
|
bafec6bb30 | ||
|
|
a478e85697 | ||
|
|
677f249d6d | ||
|
|
6aa95f4d0f | ||
|
|
0f0d2ab95a | ||
|
|
46ec52499e | ||
|
|
00b33c2474 | ||
|
|
24e2c1640d | ||
|
|
398d72fa61 | ||
|
|
30a017feca | ||
|
|
3e955fad09 | ||
|
|
84ddbd186a | ||
|
|
90c1d81975 | ||
|
|
1c9ada59cc | ||
|
|
ea8ccd2267 | ||
|
|
00e5e1ef7f | ||
|
|
ccc095a016 | ||
|
|
b5c99cc908 | ||
|
|
f00e176c5b | ||
|
|
02a2c11732 | ||
|
|
0ef3fdd732 | ||
|
|
58b841b356 | ||
|
|
f54d7f5fea | ||
|
|
e763e1b623 | ||
|
|
48cf8da2bb | ||
|
|
06149429d9 | ||
|
|
1563801bce | ||
|
|
2190e96727 | ||
|
|
0faa026db6 | ||
|
|
78c2eedb4f | ||
|
|
aa6509d8e4 | ||
|
|
a6d3c762df | ||
|
|
fccc12f386 | ||
|
|
95583e1532 | ||
|
|
237b292515 | ||
|
|
a79c7c05e8 | ||
|
|
5a7e08468a | ||
|
|
b4d88d7128 | ||
|
|
def6a00740 | ||
|
|
6b77c07379 | ||
|
|
cd2e5cae7f | ||
|
|
546a63272f | ||
|
|
35dfbd58dd | ||
|
|
51c2ea0feb | ||
|
|
9089373a01 | ||
|
|
7393ef779c | ||
|
|
4bcb7fb148 | ||
|
|
e3a06f12d2 | ||
|
|
a9c8d4ef15 | ||
|
|
bb5ed4b1d1 | ||
|
|
0d5692e02b | ||
|
|
2f1a3a4d74 | ||
|
|
9a27ac5d89 | ||
|
|
fcc8a6574d | ||
|
|
250a51a3f9 | ||
|
|
6a183d1ae8 | ||
|
|
e3814a8608 | ||
|
|
fe608e32ab | ||
|
|
34aa245dd8 | ||
|
|
baff91c1ad | ||
|
|
2e3a00987e | ||
|
|
e1c569c324 | ||
|
|
bbef8c3884 | ||
|
|
d51fcb21f4 | ||
|
|
53a516ab58 | ||
|
|
78d1a5ec72 | ||
|
|
a7d32776f0 | ||
|
|
7f04af32a5 | ||
|
|
b5798bd324 | ||
|
|
20995c5729 | ||
|
|
36911f55dd | ||
|
|
1b0c58d7e7 | ||
|
|
a37d734596 | ||
|
|
4634c735a8 | ||
|
|
57056bb606 | ||
|
|
63f1c0a57d | ||
|
|
18dcf7f98d | ||
|
|
3e40a5e832 | ||
|
|
fad14050ae | ||
|
|
54095d3389 | ||
|
|
ffacaaa3ea | ||
|
|
ed3af9ef99 | ||
|
|
c85f8ccca3 | ||
|
|
c1ed005a21 | ||
|
|
81cf262ff5 | ||
|
|
75d57a5d53 | ||
|
|
b45fbc1175 | ||
|
|
7b57510a17 | ||
|
|
cf0d6aa007 | ||
|
|
78279350aa | ||
|
|
3cf562be31 | ||
|
|
1bdb44de95 | ||
|
|
022c598abb | ||
|
|
4b65cfc723 | ||
|
|
2f09524762 | ||
|
|
b72b3c6f51 | ||
|
|
f0dde49cda | ||
|
|
e59cb2a19b | ||
|
|
5fac4729cc | ||
|
|
fe187e7e6c | ||
|
|
8954f830a0 | ||
|
|
d394a6bb48 | ||
|
|
578f36e431 | ||
|
|
a6c6b8b38c | ||
|
|
30285ee31c | ||
|
|
0996e140bd | ||
|
|
0213990b3a | ||
|
|
2d04ed65e4 | ||
|
|
32e31d49d1 | ||
|
|
e27f60b1c8 | ||
|
|
dd49f89466 | ||
|
|
bbcd3b0bd2 | ||
|
|
54b43563be | ||
|
|
53863b76f9 | ||
|
|
175d5d6a9e | ||
|
|
9871ea4357 | ||
|
|
42564ada53 | ||
|
|
2515266725 | ||
|
|
2edc92bc28 | ||
|
|
857adf388f | ||
|
|
07c2738ffa | ||
|
|
f8e2db0717 | ||
|
|
eefd75a228 | ||
|
|
f1c5eaba56 | ||
|
|
93f69cb93a | ||
|
|
5e9e5a4702 | ||
|
|
ca75f0a64a | ||
|
|
4f9f124347 | ||
|
|
114896c4e0 | ||
|
|
65c95d9c59 | ||
|
|
e03377c7fd | ||
|
|
be410dede8 | ||
|
|
a6d8e30c05 | ||
|
|
4c8f336401 | ||
|
|
cfb120f737 | ||
|
|
d956169563 | ||
|
|
2581c0a5a1 | ||
|
|
24ad430600 | ||
|
|
c4c1f961e7 | ||
|
|
30e4e01e05 | ||
|
|
602fe9454c | ||
|
|
62578d80c1 | ||
|
|
76c3fbd651 | ||
|
|
7bfa88037e | ||
|
|
f662f8f0b6 | ||
|
|
167ba599ec | ||
|
|
25ac1697b4 | ||
|
|
3c208e7616 | ||
|
|
25fbd9a996 | ||
|
|
60d83df23d | ||
|
|
6778690b51 | ||
|
|
cd124215df | ||
|
|
02693f64fc | ||
|
|
7fe3b32896 | ||
|
|
17731a43a6 | ||
|
|
c1cf94c787 | ||
|
|
f6082d1944 | ||
|
|
f5078dde2d | ||
|
|
33c7d4ccd9 | ||
|
|
c1be8d86c6 | ||
|
|
4d43c6a6a4 | ||
|
|
b7bc9cc038 | ||
|
|
792ef10ca8 | ||
|
|
6a9335dbbb | ||
|
|
84a04af9a9 | ||
|
|
05993155ef | ||
|
|
3ebb7ba7b9 | ||
|
|
682db81385 | ||
|
|
d9240b548c | ||
|
|
50e95a63dd | ||
|
|
47166baeac | ||
|
|
67b29e26be | ||
|
|
95667383db | ||
|
|
5a2a6483dc | ||
|
|
9b18cc2363 | ||
|
|
96e3a600ba | ||
|
|
addfe1ef4b | ||
|
|
386a57e6ef | ||
|
|
57fcdc264a | ||
|
|
66a2c44ef4 | ||
|
|
d2a65c59e1 | ||
|
|
63822eb761 | ||
|
|
a02ed12638 | ||
|
|
3ef55d2c7c | ||
|
|
d75793d6b4 | ||
|
|
f8b5bb2fd8 | ||
|
|
57fd4a823b | ||
|
|
ee2b1ec1e8 | ||
|
|
f0804db7f7 | ||
|
|
ebab2e2d42 | ||
|
|
b74999458f | ||
|
|
7deed2b43c | ||
|
|
7e38238981 | ||
|
|
7891af8b53 | ||
|
|
eaaef91178 | ||
|
|
0552029b5c | ||
|
|
966b32b57c | ||
|
|
c0208dffbe | ||
|
|
b8cea7cc27 | ||
|
|
f7843f30a8 | ||
|
|
8e63cad078 | ||
|
|
731895214b | ||
|
|
60c09f5fbb | ||
|
|
10ba50e9eb | ||
|
|
31d3e41810 | ||
|
|
cc67b22d6e | ||
|
|
560e984995 | ||
|
|
8dd0a7e11a | ||
|
|
fb4ae4d810 | ||
|
|
019aa7074c | ||
|
|
38bbc879a5 | ||
|
|
0f3b36964e | ||
|
|
70aa942153 | ||
|
|
1a61ba179e | ||
|
|
e2548366e1 | ||
|
|
8c392a31d7 | ||
|
|
5a38e18627 | ||
|
|
17c6325e8a | ||
|
|
ec6c5383d0 | ||
|
|
da29eba43b | ||
|
|
d1b74b0c82 | ||
|
|
01542400a8 | ||
|
|
cc20ec3689 | ||
|
|
5a6ad9d52a | ||
|
|
4e3c3d8c6a | ||
|
|
1026e794a3 | ||
|
|
75f7f42d41 | ||
|
|
88ed1f6ea6 | ||
|
|
5b2cb64bfb | ||
|
|
821887036e | ||
|
|
283d60761e | ||
|
|
377e004048 | ||
|
|
92ccae7123 | ||
|
|
d1cab2b64e | ||
|
|
880411f54c | ||
|
|
d27258d3a6 | ||
|
|
bb38cd03a9 | ||
|
|
cf51d3eb73 | ||
|
|
983028f097 | ||
|
|
2a3363d52e | ||
|
|
593d3de371 | ||
|
|
2b22baf304 | ||
|
|
ab0542f5ec | ||
|
|
5df172da2f | ||
|
|
385049baf2 | ||
|
|
8c724f5c7f | ||
|
|
93c2b91740 | ||
|
|
a48a398e7c | ||
|
|
c70586621c | ||
|
|
c2f6e45aa3 | ||
|
|
6a541afcc4 | ||
|
|
ec48e5c77f | ||
|
|
0806801dc8 | ||
|
|
aa67bae6cf | ||
|
|
4748911357 | ||
|
|
1d6700f9e6 | ||
|
|
be97dbb15c | ||
|
|
4708a6875c | ||
|
|
fc7476bec1 | ||
|
|
c8f3606731 | ||
|
|
96a13b4f4b | ||
|
|
65a9cd6168 | ||
|
|
8fc3de3c62 | ||
|
|
1480e6f7cf | ||
|
|
3fc08fa88e | ||
|
|
8ae81684e9 | ||
|
|
b4596c6174 | ||
|
|
34ebadf930 | ||
|
|
e4234fbdcf | ||
|
|
60beefd6e0 | ||
|
|
c57050b770 | ||
|
|
c319792059 | ||
|
|
6b3c71f6ed | ||
|
|
f47b4eeb1e | ||
|
|
382277d0fe | ||
|
|
edcbb36944 | ||
|
|
885b1c682e | ||
|
|
ffac68367f | ||
|
|
9a970c81af | ||
|
|
a843df668b | ||
|
|
2d6e42122b | ||
|
|
464fb36de9 | ||
|
|
09713a64b3 | ||
|
|
72e4190918 | ||
|
|
9d115d3689 | ||
|
|
f615f5604b | ||
|
|
5785b1fcb8 | ||
|
|
82860bd55c | ||
|
|
2d30aaae47 | ||
|
|
f5148ade10 | ||
|
|
3c42807794 | ||
|
|
025b85b4ac | ||
|
|
044a71e27e | ||
|
|
64b6452e0c | ||
|
|
2dd22e5449 | ||
|
|
2a5915049e | ||
|
|
f46464d383 | ||
|
|
9a4df72994 | ||
|
|
fce6c892ab | ||
|
|
dcd36a6aee | ||
|
|
3cd7ea2e8a | ||
|
|
0e99323ac2 | ||
|
|
ba3c58cab6 | ||
|
|
7e19a571e9 | ||
|
|
10ddd59ba7 | ||
|
|
79810e2d49 | ||
|
|
ab95e293d2 | ||
|
|
37ae8cc60f | ||
|
|
3b23b1d8c6 | ||
|
|
3b9a000c15 | ||
|
|
0ec90a7cc2 | ||
|
|
4a90cbf429 | ||
|
|
b97685aef6 | ||
|
|
725bc403f8 | ||
|
|
e612e31740 | ||
|
|
8fa4d108a2 | ||
|
|
825a22c00c | ||
|
|
26ac24f199 | ||
|
|
06b37eef7b | ||
|
|
c10cf166fa | ||
|
|
8a6708f5f2 | ||
|
|
dc5f29f32f |
No files matched your search
+19
-7
@@ -20,7 +20,9 @@ addons:
|
||||
env:
|
||||
- TEST_GROUP=platform_dependent # 16-18 minutes
|
||||
- TEST_GROUP=1 # 33-35 minutes
|
||||
- TEST_GROUP=2 # 30-32 minutes
|
||||
- TEST_GROUP=2 # 18-20 minutes
|
||||
- TEST_GROUP=3 # 20-22 minutes
|
||||
- TEST_GROUP=4 # 12-14 minutes
|
||||
# Run java tests
|
||||
- JOB_NAME=java_test # 4-11 minutes
|
||||
# Build ROCKSDB_LITE
|
||||
@@ -36,6 +38,10 @@ matrix:
|
||||
env: TEST_GROUP=1
|
||||
- os: osx
|
||||
env: TEST_GROUP=2
|
||||
- os: osx
|
||||
env: TEST_GROUP=3
|
||||
- os: osx
|
||||
env: TEST_GROUP=4
|
||||
- os : osx
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os : linux
|
||||
@@ -49,6 +55,9 @@ install:
|
||||
brew install ccache;
|
||||
PATH=$PATH:/usr/local/opt/ccache/libexec;
|
||||
fi
|
||||
- if [[ "${JOB_NAME}" == cmake* ]] && [ "${TRAVIS_OS_NAME}" == linux ]; then
|
||||
mkdir cmake-dist && curl -sfSL https://cmake.org/files/v3.8/cmake-3.8.1-Linux-x86_64.tar.gz | tar --strip-components=1 -C cmake-dist -xz && export PATH=$PWD/cmake-dist/bin:$PATH;
|
||||
fi
|
||||
|
||||
before_script:
|
||||
# Increase the maximum number of open file descriptors, since some tests use
|
||||
@@ -57,14 +66,17 @@ before_script:
|
||||
|
||||
script:
|
||||
- ${CXX} --version
|
||||
- if [ "${TEST_GROUP}" == 'platform_dependent' ]; then ccache -C && OPT=-DTRAVIS V=1 ROCKSDBTESTS_END=db_block_cache_test make -j4 all_but_some_tests check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '1' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=db_block_cache_test ROCKSDBTESTS_END=comparator_db_test make -j4 check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '2' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=comparator_db_test make -j4 check_some; fi
|
||||
- if [ `command -v ccache` ]; then ccache -C; fi
|
||||
- if [ "${TEST_GROUP}" == 'platform_dependent' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_END=db_block_cache_test make -j4 all_but_some_tests check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '1' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=db_block_cache_test ROCKSDBTESTS_END=full_filter_block_test make -j4 check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '2' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=full_filter_block_test ROCKSDBTESTS_END=write_batch_with_index_test make -j4 check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '3' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_batch_with_index_test ROCKSDBTESTS_END=write_prepared_transaction_test make -j4 check_some; fi
|
||||
- if [ "${TEST_GROUP}" == '4' ]; then OPT=-DTRAVIS V=1 ROCKSDBTESTS_START=write_prepared_transaction_test make -j4 check_some; fi
|
||||
- if [ "${JOB_NAME}" == 'java_test' ]; then OPT=-DTRAVIS V=1 make clean jclean && make rocksdbjava jtest; fi
|
||||
- if [ "${JOB_NAME}" == 'lite_build' ]; then OPT="-DTRAVIS -DROCKSDB_LITE" V=1 make -j4 static_lib; fi
|
||||
- if [ "${JOB_NAME}" == 'lite_build' ]; then OPT="-DTRAVIS -DROCKSDB_LITE" V=1 make -j4 static_lib tools; fi
|
||||
- if [ "${JOB_NAME}" == 'examples' ]; then OPT=-DTRAVIS V=1 make -j4 static_lib; cd examples; make -j4; fi
|
||||
- if [ "${JOB_NAME}" == 'cmake' ]; then mkdir build && cd build && cmake .. && make -j4 rocksdb; fi
|
||||
- if [ "${JOB_NAME}" == 'cmake-mingw' ]; then mkdir build && cd build && cmake .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb; fi
|
||||
- if [ "${JOB_NAME}" == 'cmake' ]; then mkdir build && cd build && cmake -DJNI=1 .. && make -j4 rocksdb rocksdbjni; fi
|
||||
- if [ "${JOB_NAME}" == 'cmake-mingw' ]; then mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni; fi
|
||||
notifications:
|
||||
email:
|
||||
- leveldb@fb.com
|
||||
|
||||
@@ -9,3 +9,4 @@ Sanjay Ghemawat <sanjay@google.com>
|
||||
# Partial list of contributors:
|
||||
Kevin Regan <kevin.d.regan@gmail.com>
|
||||
Johan Bilien <jobi@litl.com>
|
||||
Matthew Von-Maszewski <https://github.com/matthewvon> (Basho Technologies)
|
||||
+148
-52
@@ -14,7 +14,7 @@
|
||||
# cd build
|
||||
# 3. Run cmake to generate project files for Windows, add more options to enable required third-party libraries.
|
||||
# See thirdparty.inc for more information.
|
||||
# sample command: cmake -G "Visual Studio 14 Win64" -DGFLAGS=1 -DSNAPPY=1 -DJEMALLOC=1 -DJNI=1 ..
|
||||
# sample command: cmake -G "Visual Studio 15 Win64" -DWITH_GFLAGS=1 -DWITH_SNAPPY=1 -DWITH_JEMALLOC=1 -DWITH_JNI=1 ..
|
||||
# 4. Then build the project in debug mode (you may want to add /m[:<N>] flag to run msbuild in <N> parallel threads
|
||||
# or simply /m ot use all avail cores)
|
||||
# msbuild rocksdb.sln
|
||||
@@ -32,21 +32,31 @@
|
||||
# 3. cmake ..
|
||||
# 4. make -j
|
||||
|
||||
cmake_minimum_required(VERSION 2.6)
|
||||
cmake_minimum_required(VERSION 2.8.12)
|
||||
project(rocksdb)
|
||||
enable_language(CXX)
|
||||
enable_language(C)
|
||||
enable_language(ASM)
|
||||
|
||||
if(POLICY CMP0042)
|
||||
cmake_policy(SET CMP0042 NEW)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules/")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/")
|
||||
|
||||
option(WITH_JEMALLOC "build with JeMalloc" OFF)
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
option(WITH_LZ4 "build with lz4" OFF)
|
||||
option(WITH_ZLIB "build with zlib" OFF)
|
||||
if(MSVC)
|
||||
# Defaults currently different for GFLAGS.
|
||||
# We will address find_package work a little later
|
||||
option(WITH_GFLAGS "build with GFlags" OFF)
|
||||
option(WITH_XPRESS "build with windows built in compression" OFF)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
else()
|
||||
option(WITH_JEMALLOC "build with JeMalloc" OFF)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
# FreeBSD has jemaloc as default malloc
|
||||
# FreeBSD has jemalloc as default malloc
|
||||
# but it does not have all the jemalloc files in include/...
|
||||
set(WITH_JEMALLOC ON)
|
||||
else()
|
||||
@@ -56,8 +66,18 @@ else()
|
||||
include_directories(${JEMALLOC_INCLUDE_DIR})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# No config file for this
|
||||
option(WITH_GFLAGS "build with GFlags" ON)
|
||||
if(WITH_GFLAGS)
|
||||
find_package(gflags)
|
||||
if(gflags_FOUND)
|
||||
add_definitions(-DGFLAGS=1)
|
||||
include_directories(${gflags_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${gflags_LIBRARIES})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
if(WITH_SNAPPY)
|
||||
find_package(snappy REQUIRED)
|
||||
add_definitions(-DSNAPPY)
|
||||
@@ -65,7 +85,6 @@ else()
|
||||
list(APPEND THIRDPARTY_LIBS ${SNAPPY_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_ZLIB "build with zlib" OFF)
|
||||
if(WITH_ZLIB)
|
||||
find_package(zlib REQUIRED)
|
||||
add_definitions(-DZLIB)
|
||||
@@ -81,7 +100,6 @@ else()
|
||||
list(APPEND THIRDPARTY_LIBS ${BZIP2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
option(WITH_LZ4 "build with lz4" OFF)
|
||||
if(WITH_LZ4)
|
||||
find_package(lz4 REQUIRED)
|
||||
add_definitions(-DLZ4)
|
||||
@@ -98,17 +116,7 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
execute_process(COMMAND powershell -noprofile -Command "Get-Date -format MM_dd_yyyy" OUTPUT_VARIABLE DATE)
|
||||
execute_process(COMMAND powershell -noprofile -Command "Get-Date -format HH:mm:ss" OUTPUT_VARIABLE TIME)
|
||||
string(REGEX REPLACE "(..)_(..)_..(..).*" "\\1/\\2/\\3" DATE "${DATE}")
|
||||
string(REGEX REPLACE "(..):(.....).*" " \\1:\\2" TIME "${TIME}")
|
||||
set(GIT_DATE_TIME "${DATE} ${TIME}")
|
||||
else()
|
||||
execute_process(COMMAND date "+%Y/%m/%d %H:%M:%S" OUTPUT_VARIABLE DATETIME)
|
||||
string(REGEX REPLACE "\n" "" DATETIME ${DATETIME})
|
||||
set(GIT_DATE_TIME "${DATETIME}")
|
||||
endif()
|
||||
string(TIMESTAMP GIT_DATE_TIME "%Y/%m/%d %H:%M:%S" UTC)
|
||||
|
||||
find_package(Git)
|
||||
|
||||
@@ -124,20 +132,19 @@ endif()
|
||||
|
||||
string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA "${GIT_SHA}")
|
||||
|
||||
if(NOT WIN32)
|
||||
execute_process(COMMAND
|
||||
"./build_tools/version.sh" "full"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE ROCKSDB_VERSION
|
||||
)
|
||||
string(STRIP "${ROCKSDB_VERSION}" ROCKSDB_VERSION)
|
||||
execute_process(COMMAND
|
||||
"./build_tools/version.sh" "major"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE ROCKSDB_VERSION_MAJOR
|
||||
)
|
||||
string(STRIP "${ROCKSDB_VERSION_MAJOR}" ROCKSDB_VERSION_MAJOR)
|
||||
endif()
|
||||
set(SH_CMD "sh")
|
||||
execute_process(COMMAND
|
||||
${SH_CMD} -c "build_tools/version.sh full"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE ROCKSDB_VERSION
|
||||
)
|
||||
string(STRIP "${ROCKSDB_VERSION}" ROCKSDB_VERSION)
|
||||
execute_process(COMMAND
|
||||
${SH_CMD} -c "build_tools/version.sh major"
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
OUTPUT_VARIABLE ROCKSDB_VERSION_MAJOR
|
||||
)
|
||||
string(STRIP "${ROCKSDB_VERSION_MAJOR}" ROCKSDB_VERSION_MAJOR)
|
||||
|
||||
option(WITH_MD_LIBRARY "build with MD" ON)
|
||||
if(WIN32 AND MSVC)
|
||||
@@ -155,10 +162,10 @@ target_include_directories(build_version PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util)
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W3 /wd4127 /wd4800 /wd4996 /wd4351")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4800 /wd4996 /wd4351 /wd4100 /wd4204 /wd4324")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing")
|
||||
if(MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format")
|
||||
endif()
|
||||
@@ -173,31 +180,53 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(CheckCCompilerFlag)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
|
||||
CHECK_C_COMPILER_FLAG("-maltivec" HAS_ALTIVEC)
|
||||
if(HAS_ALTIVEC)
|
||||
message(STATUS " HAS_ALTIVEC yes")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maltivec")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maltivec")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mcpu=power8")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mcpu=power8")
|
||||
endif(HAS_ALTIVEC)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "ppc64le")
|
||||
|
||||
option(PORTABLE "build a portable binary" OFF)
|
||||
option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
|
||||
if(PORTABLE)
|
||||
# MSVC does not need a separate compiler flag to enable SSE4.2; if nmmintrin.h
|
||||
# is available, it is available by default.
|
||||
if(FORCE_SSE42 AND NOT MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -msse4.2 -mpclmul")
|
||||
endif()
|
||||
else()
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
||||
if(NOT HAVE_POWER8)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS ${CMAKE_CXX_FLAGS})
|
||||
include(CheckCXXSourceCompiles)
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <cstdint>
|
||||
#include <nmmintrin.h>
|
||||
#include <wmmintrin.h>
|
||||
int main() {
|
||||
volatile uint32_t x = _mm_crc32_u32(0, 0);
|
||||
const auto a = _mm_set_epi64x(0, 0);
|
||||
const auto b = _mm_set_epi64x(0, 0);
|
||||
const auto c = _mm_clmulepi64_si128(a, b, 0x00);
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
}
|
||||
" HAVE_SSE42)
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
if(HAVE_SSE42)
|
||||
add_definitions(-DHAVE_SSE42)
|
||||
elseif(FORCE_SSE42)
|
||||
@@ -256,6 +285,20 @@ if(WITH_UBSAN)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
find_package(NUMA)
|
||||
if(NUMA_FOUND)
|
||||
add_definitions(-DNUMA)
|
||||
include_directories(${NUMA_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${NUMA_LIBRARIES})
|
||||
endif()
|
||||
|
||||
find_package(TBB)
|
||||
if(TBB_FOUND)
|
||||
add_definitions(-DTBB)
|
||||
include_directories(${TBB_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${TBB_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# Used to run CI build and tests so we can run faster
|
||||
set(OPTIMIZE_DEBUG_DEFAULT 0) # Debug build is unoptimized by default use -DOPTDBG=1 to optimize
|
||||
|
||||
@@ -323,11 +366,8 @@ if(NOT WIN32)
|
||||
endif()
|
||||
|
||||
option(WITH_FALLOCATE "build with fallocate" ON)
|
||||
|
||||
if(WITH_FALLOCATE)
|
||||
set(CMAKE_REQUIRED_FLAGS ${CMAKE_C_FLAGS})
|
||||
include(CheckCSourceCompiles)
|
||||
CHECK_C_SOURCE_COMPILES("
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <fcntl.h>
|
||||
#include <linux/falloc.h>
|
||||
int main() {
|
||||
@@ -340,12 +380,38 @@ int main() {
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include(CheckFunctionExists)
|
||||
CHECK_FUNCTION_EXISTS(malloc_usable_size HAVE_MALLOC_USABLE_SIZE)
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <fcntl.h>
|
||||
int main() {
|
||||
int fd = open(\"/dev/null\", 0);
|
||||
sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE);
|
||||
}
|
||||
" HAVE_SYNC_FILE_RANGE_WRITE)
|
||||
if(HAVE_SYNC_FILE_RANGE_WRITE)
|
||||
add_definitions(-DROCKSDB_RANGESYNC_PRESENT)
|
||||
endif()
|
||||
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <pthread.h>
|
||||
int main() {
|
||||
(void) PTHREAD_MUTEX_ADAPTIVE_NP;
|
||||
}
|
||||
" HAVE_PTHREAD_MUTEX_ADAPTIVE_NP)
|
||||
if(HAVE_PTHREAD_MUTEX_ADAPTIVE_NP)
|
||||
add_definitions(-DROCKSDB_PTHREAD_ADAPTIVE_MUTEX)
|
||||
endif()
|
||||
|
||||
include(CheckCXXSymbolExists)
|
||||
check_cxx_symbol_exists(malloc_usable_size malloc.h HAVE_MALLOC_USABLE_SIZE)
|
||||
if(HAVE_MALLOC_USABLE_SIZE)
|
||||
add_definitions(-DROCKSDB_MALLOC_USABLE_SIZE)
|
||||
endif()
|
||||
|
||||
check_cxx_symbol_exists(sched_getcpu sched.h HAVE_SCHED_GETCPU)
|
||||
if(HAVE_SCHED_GETCPU)
|
||||
add_definitions(-DROCKSDB_SCHED_GETCPU_PRESENT)
|
||||
endif()
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
include_directories(SYSTEM ${PROJECT_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src)
|
||||
@@ -448,6 +514,7 @@ set(SOURCES
|
||||
table/block_based_table_factory.cc
|
||||
table/block_based_table_reader.cc
|
||||
table/block_builder.cc
|
||||
table/block_fetcher.cc
|
||||
table/block_prefix_index.cc
|
||||
table/bloom_block.cc
|
||||
table/cuckoo_table_builder.cc
|
||||
@@ -502,12 +569,14 @@ set(SOURCES
|
||||
util/status_message.cc
|
||||
util/string_util.cc
|
||||
util/sync_point.cc
|
||||
util/sync_point_impl.cc
|
||||
util/testutil.cc
|
||||
util/thread_local.cc
|
||||
util/threadpool_imp.cc
|
||||
util/transaction_test_util.cc
|
||||
util/xxhash.cc
|
||||
utilities/backupable/backupable_db.cc
|
||||
utilities/blob_db/blob_compaction_filter.cc
|
||||
utilities/blob_db/blob_db.cc
|
||||
utilities/blob_db/blob_db_impl.cc
|
||||
utilities/blob_db/blob_dump_tool.cc
|
||||
@@ -535,6 +604,7 @@ set(SOURCES
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
utilities/lua/rocks_lua_compaction_filter.cc
|
||||
utilities/memory/memory_util.cc
|
||||
utilities/merge_operators/bytesxor.cc
|
||||
utilities/merge_operators/max.cc
|
||||
utilities/merge_operators/put.cc
|
||||
utilities/merge_operators/string_append/stringappend.cc
|
||||
@@ -553,18 +623,34 @@ set(SOURCES
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
utilities/transactions/optimistic_transaction_db_impl.cc
|
||||
utilities/transactions/optimistic_transaction.cc
|
||||
utilities/transactions/transaction_base.cc
|
||||
utilities/transactions/pessimistic_transaction_db.cc
|
||||
utilities/transactions/transaction_db_mutex_impl.cc
|
||||
utilities/transactions/pessimistic_transaction.cc
|
||||
utilities/transactions/pessimistic_transaction_db.cc
|
||||
utilities/transactions/snapshot_checker.cc
|
||||
utilities/transactions/transaction_base.cc
|
||||
utilities/transactions/transaction_db_mutex_impl.cc
|
||||
utilities/transactions/transaction_lock_mgr.cc
|
||||
utilities/transactions/transaction_util.cc
|
||||
utilities/transactions/write_prepared_txn.cc
|
||||
utilities/transactions/write_prepared_txn_db.cc
|
||||
utilities/ttl/db_ttl_impl.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_internal.cc
|
||||
$<TARGET_OBJECTS:build_version>)
|
||||
|
||||
if(HAVE_SSE42 AND NOT FORCE_SSE42)
|
||||
if(NOT MSVC)
|
||||
set_source_files_properties(
|
||||
util/crc32c.cc
|
||||
PROPERTIES COMPILE_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(HAVE_POWER8)
|
||||
list(APPEND SOURCES
|
||||
util/crc32c_ppc.c
|
||||
util/crc32c_ppc_asm.S)
|
||||
endif(HAVE_POWER8)
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND SOURCES
|
||||
port/win/io_win.cc
|
||||
@@ -574,6 +660,12 @@ if(WIN32)
|
||||
port/win/win_logger.cc
|
||||
port/win/win_thread.cc
|
||||
port/win/xpress_win.cc)
|
||||
|
||||
if(WITH_JEMALLOC)
|
||||
list(APPEND SOURCES
|
||||
port/win/win_jemalloc.cc)
|
||||
endif()
|
||||
|
||||
else()
|
||||
list(APPEND SOURCES
|
||||
port/port_posix.cc
|
||||
@@ -653,7 +745,7 @@ if(NOT WIN32 OR ROCKSDB_INSTALL_ON_WINDOWS)
|
||||
set(package_config_destination ${CMAKE_INSTALL_LIBDIR}/cmake/rocksdb)
|
||||
|
||||
configure_package_config_file(
|
||||
${CMAKE_SOURCE_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake
|
||||
${CMAKE_CURRENT_LIST_DIR}/cmake/RocksDBConfig.cmake.in RocksDBConfig.cmake
|
||||
INSTALL_DESTINATION ${package_config_destination}
|
||||
)
|
||||
|
||||
@@ -713,6 +805,7 @@ if(WITH_TESTS)
|
||||
db/corruption_test.cc
|
||||
db/cuckoo_table_db_test.cc
|
||||
db/db_basic_test.cc
|
||||
db/db_blob_index_test.cc
|
||||
db/db_block_cache_test.cc
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_compaction_filter_test.cc
|
||||
@@ -778,6 +871,7 @@ if(WITH_TESTS)
|
||||
options/options_test.cc
|
||||
table/block_based_filter_block_test.cc
|
||||
table/block_test.cc
|
||||
table/cleanable_test.cc
|
||||
table/cuckoo_table_builder_test.cc
|
||||
table/cuckoo_table_reader_test.cc
|
||||
table/full_filter_block_test.cc
|
||||
@@ -830,6 +924,7 @@ if(WITH_TESTS)
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
utilities/transactions/transaction_test.cc
|
||||
utilities/transactions/write_prepared_transaction_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
)
|
||||
@@ -880,17 +975,18 @@ if(WITH_TESTS)
|
||||
|
||||
foreach(sourcefile ${TEST_EXES})
|
||||
get_filename_component(exename ${sourcefile} NAME_WE)
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile}
|
||||
add_executable(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} ${sourcefile}
|
||||
$<TARGET_OBJECTS:testharness>)
|
||||
set_target_properties(${exename}${ARTIFACT_SUFFIX}
|
||||
set_target_properties(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
OUTPUT_NAME ${exename}${ARTIFACT_SUFFIX}
|
||||
)
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} gtest ${LIBS})
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} gtest ${LIBS})
|
||||
if(NOT "${exename}" MATCHES "db_sanity_test")
|
||||
add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX})
|
||||
add_dependencies(check ${exename}${ARTIFACT_SUFFIX})
|
||||
add_dependencies(check ${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX})
|
||||
endif()
|
||||
endforeach(sourcefile ${TEST_EXES})
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Code of Conduct
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/codeofconduct) so that you can understand what actions will and will not be tolerated.
|
||||
@@ -1,5 +1,8 @@
|
||||
# Contributing to RocksDB
|
||||
|
||||
## Code of Conduct
|
||||
The code of conduct is described in [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md)
|
||||
|
||||
## Contributor License Agreement ("CLA")
|
||||
|
||||
In order to accept your pull request, we need you to submit a CLA. You
|
||||
|
||||
+102
@@ -1,8 +1,110 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### Public API Change
|
||||
|
||||
### New Features
|
||||
|
||||
### Bug Fixes
|
||||
* Fsync after writing global seq number to the ingestion file in ExternalSstFileIngestionJob.
|
||||
|
||||
### Java API Changes
|
||||
* Add `BlockBasedTableConfig.setBlockCache` to allow sharing a block cache across DB instances.
|
||||
|
||||
## 5.13.0 (3/20/2018)
|
||||
### Public API Change
|
||||
* RocksDBOptionsParser::Parse()'s `ignore_unknown_options` argument will only be effective if the option file shows it is generated using a higher version of RocksDB than the current version.
|
||||
* Remove CompactionEventListener.
|
||||
|
||||
### New Features
|
||||
* SstFileManager now can cancel compactions if they will result in max space errors. SstFileManager users can also use SetCompactionBufferSize to specify how much space must be leftover during a compaction for auxiliary file functions such as logging and flushing.
|
||||
* Avoid unnecessarily flushing in `CompactRange()` when the range specified by the user does not overlap unflushed memtables.
|
||||
* If `ColumnFamilyOptions::max_subcompactions` is set greater than one, we now parallelize large manual level-based compactions.
|
||||
* Add "rocksdb.live-sst-files-size" DB property to return total bytes of all SST files belong to the latest LSM tree.
|
||||
* NewSstFileManager to add an argument bytes_max_delete_chunk with default 64MB. With this argument, a file larger than 64MB will be ftruncated multiple times based on this size.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a leak in prepared_section_completed_ where the zeroed entries would not removed from the map.
|
||||
* Fix WAL corruption caused by race condition between user write thread and backup/checkpoint thread.
|
||||
|
||||
## 5.12.0 (2/14/2018)
|
||||
### Public API Change
|
||||
* Iterator::SeekForPrev is now a pure virtual method. This is to prevent user who implement the Iterator interface fail to implement SeekForPrev by mistake.
|
||||
* Add `include_end` option to make the range end exclusive when `include_end == false` in `DeleteFilesInRange()`.
|
||||
* Add `CompactRangeOptions::allow_write_stall`, which makes `CompactRange` start working immediately, even if it causes user writes to stall. The default value is false, meaning we add delay to `CompactRange` calls until stalling can be avoided when possible. Note this delay is not present in previous RocksDB versions.
|
||||
* Creating checkpoint with empty directory now returns `Status::InvalidArgument`; previously, it returned `Status::IOError`.
|
||||
* Adds a BlockBasedTableOption to turn off index block compression.
|
||||
* Close() method now returns a status when closing a db.
|
||||
|
||||
### New Features
|
||||
* Improve the performance of iterators doing long range scans by using readahead.
|
||||
* Add new function `DeleteFilesInRanges()` to delete files in multiple ranges at once for better performance.
|
||||
* FreeBSD build support for RocksDB and RocksJava.
|
||||
* Improved performance of long range scans with readahead.
|
||||
* Updated to and now continuously tested in Visual Studio 2017.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix `DisableFileDeletions()` followed by `GetSortedWalFiles()` to not return obsolete WAL files that `PurgeObsoleteFiles()` is going to delete.
|
||||
* Fix Handle error return from WriteBuffer() during WAL file close and DB close.
|
||||
* Fix advance reservation of arena block addresses.
|
||||
* Fix handling of empty string as checkpoint directory.
|
||||
|
||||
## 5.11.0 (01/08/2018)
|
||||
### Public API Change
|
||||
* Add `autoTune` and `getBytesPerSecond()` to RocksJava RateLimiter
|
||||
|
||||
### New Features
|
||||
* Add a new histogram stat called rocksdb.db.flush.micros for memtable flush.
|
||||
* Add "--use_txn" option to use transactional API in db_stress.
|
||||
* Disable onboard cache for compaction output in Windows platform.
|
||||
* Improve the performance of iterators doing long range scans by using readahead.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a stack-use-after-scope bug in ForwardIterator.
|
||||
* Fix builds on platforms including Linux, Windows, and PowerPC.
|
||||
* Fix buffer overrun in backup engine for DBs with huge number of files.
|
||||
* Fix a mislabel bug for bottom-pri compaction threads.
|
||||
* Fix DB::Flush() keep waiting after flush finish under certain condition.
|
||||
|
||||
## 5.10.0 (12/11/2017)
|
||||
### Public API Change
|
||||
* When running `make` with environment variable `USE_SSE` set and `PORTABLE` unset, will use all machine features available locally. Previously this combination only compiled SSE-related features.
|
||||
|
||||
### New Features
|
||||
* Provide lifetime hints when writing files on Linux. This reduces hardware write-amp on storage devices supporting multiple streams.
|
||||
* Add a DB stat, `NUMBER_ITER_SKIP`, which returns how many internal keys were skipped during iterations (e.g., due to being tombstones or duplicate versions of a key).
|
||||
* Add PerfContext counters, `key_lock_wait_count` and `key_lock_wait_time`, which measure the number of times transactions wait on key locks and total amount of time waiting.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix IOError on WAL write doesn't propagate to write group follower
|
||||
* Make iterator invalid on merge error.
|
||||
* Fix performance issue in `IngestExternalFile()` affecting databases with large number of SST files.
|
||||
* Fix possible corruption to LSM structure when `DeleteFilesInRange()` deletes a subset of files spanned by a `DeleteRange()` marker.
|
||||
|
||||
## 5.9.0 (11/1/2017)
|
||||
### Public API Change
|
||||
* `BackupableDBOptions::max_valid_backups_to_open == 0` now means no backups will be opened during BackupEngine initialization. Previously this condition disabled limiting backups opened.
|
||||
* `DBOptions::preserve_deletes` is a new option that allows one to specify that DB should not drop tombstones for regular deletes if they have sequence number larger than what was set by the new API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)`. Disabled by default.
|
||||
* API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)` was added, users who wish to preserve deletes are expected to periodically call this function to advance the cutoff seqnum (all deletes made before this seqnum can be dropped by DB). It's user responsibility to figure out how to advance the seqnum in the way so the tombstones are kept for the desired period of time, yet are eventually processed in time and don't eat up too much space.
|
||||
* `ReadOptions::iter_start_seqnum` was added; if set to something > 0 user will see 2 changes in iterators behavior 1) only keys written with sequence larger than this parameter would be returned and 2) the `Slice` returned by iter->key() now points to the memory that keep User-oriented representation of the internal key, rather than user key. New struct `FullKey` was added to represent internal keys, along with a new helper function `ParseFullKey(const Slice& internal_key, FullKey* result);`.
|
||||
* Deprecate trash_dir param in NewSstFileManager, right now we will rename deleted files to <name>.trash instead of moving them to trash directory
|
||||
* Allow setting a custom trash/DB size ratio limit in the SstFileManager, after which files that are to be scheduled for deletion are deleted immediately, regardless of any delete ratelimit.
|
||||
* Return an error on write if write_options.sync = true and write_options.disableWAL = true to warn user of inconsistent options. Previously we will not write to WAL and not respecting the sync options in this case.
|
||||
|
||||
### New Features
|
||||
* CRC32C is now using the 3-way pipelined SSE algorithm `crc32c_3way` on supported platforms to improve performance. The system will choose to use this algorithm on supported platforms automatically whenever possible. If PCLMULQDQ is not supported it will fall back to the old Fast_CRC32 algorithm.
|
||||
* `DBOptions::writable_file_max_buffer_size` can now be changed dynamically.
|
||||
* `DBOptions::bytes_per_sync`, `DBOptions::compaction_readahead_size`, and `DBOptions::wal_bytes_per_sync` can now be changed dynamically, `DBOptions::wal_bytes_per_sync` will flush all memtables and switch to a new WAL file.
|
||||
* Support dynamic adjustment of rate limit according to demand for background I/O. It can be enabled by passing `true` to the `auto_tuned` parameter in `NewGenericRateLimiter()`. The value passed as `rate_bytes_per_sec` will still be respected as an upper-bound.
|
||||
* Support dynamically changing `ColumnFamilyOptions::compaction_options_fifo`.
|
||||
* Introduce `EventListener::OnStallConditionsChanged()` callback. Users can implement it to be notified when user writes are stalled, stopped, or resumed.
|
||||
* Add a new db property "rocksdb.estimate-oldest-key-time" to return oldest data timestamp. The property is available only for FIFO compaction with compaction_options_fifo.allow_compaction = false.
|
||||
* Upon snapshot release, recompact bottommost files containing deleted/overwritten keys that previously could not be dropped due to the snapshot. This alleviates space-amp caused by long-held snapshots.
|
||||
* Support lower bound on iterators specified via `ReadOptions::iterate_lower_bound`.
|
||||
* Support for differential snapshots (via iterator emitting the sequence of key-values representing the difference between DB state at two different sequence numbers). Supports preserving and emitting puts and regular deletes, doesn't support SingleDeletes, MergeOperator, Blobs and Range Deletes.
|
||||
|
||||
### Bug Fixes
|
||||
* Fix a potential data inconsistency issue during point-in-time recovery. `DB:Open()` will abort if column family inconsistency is found during PIT recovery.
|
||||
* Fix possible metadata corruption in databases using `DeleteRange()`.
|
||||
|
||||
## 5.8.0 (08/30/2017)
|
||||
### Public API Change
|
||||
|
||||
+57
-1
@@ -107,6 +107,62 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
* run `brew tap homebrew/versions; brew install gcc48 --use-llvm` to install gcc 4.8 (or higher).
|
||||
* run `brew install rocksdb`
|
||||
|
||||
* **FreeBSD** (11.01):
|
||||
|
||||
* You can either install RocksDB from the Ports system using `cd /usr/ports/databases/rocksdb && make install`, or you can follow the details below to install dependencies and compile from source code:
|
||||
|
||||
* Install the dependencies for RocksDB:
|
||||
|
||||
export BATCH=YES
|
||||
cd /usr/ports/devel/gmake && make install
|
||||
cd /usr/ports/devel/gflags && make install
|
||||
|
||||
cd /usr/ports/archivers/snappy && make install
|
||||
cd /usr/ports/archivers/bzip2 && make install
|
||||
cd /usr/ports/archivers/liblz4 && make install
|
||||
cd /usr/ports/archivesrs/zstd && make install
|
||||
|
||||
cd /usr/ports/devel/git && make install
|
||||
|
||||
|
||||
* Install the dependencies for RocksJava (optional):
|
||||
|
||||
export BATCH=yes
|
||||
cd /usr/ports/java/openjdk7 && make install
|
||||
|
||||
* Build RocksDB from source:
|
||||
cd ~
|
||||
git clone https://github.com/facebook/rocksdb.git
|
||||
cd rocksdb
|
||||
gmake static_lib
|
||||
|
||||
* Build RocksJava from source (optional):
|
||||
cd rocksdb
|
||||
export JAVA_HOME=/usr/local/openjdk7
|
||||
gmake rocksdbjava
|
||||
|
||||
* **OpenBSD** (6.3/-current):
|
||||
|
||||
* As RocksDB is not available in the ports yet you have to build it on your own:
|
||||
|
||||
* Install the dependencies for RocksDB:
|
||||
|
||||
pkg_add gmake gflags snappy bzip2 lz4 zstd git jdk bash findutils gnuwatch
|
||||
|
||||
* Build RocksDB from source:
|
||||
|
||||
cd ~
|
||||
git clone https://github.com/facebook/rocksdb.git
|
||||
cd rocksdb
|
||||
gmake static_lib
|
||||
|
||||
* Build RocksJava from source (optional):
|
||||
|
||||
cd rocksdb
|
||||
export JAVA_HOME=/usr/local/jdk-1.8.0
|
||||
export PATH=$PATH:/usr/local/jdk-1.8.0/bin
|
||||
gmake rocksdbjava
|
||||
|
||||
* **iOS**:
|
||||
* Run: `TARGET_OS=IOS make static_lib`. When building the project which uses rocksdb iOS library, make sure to define two important pre-processing macros: `ROCKSDB_LITE` and `IOS_CROSS_COMPILE`.
|
||||
|
||||
@@ -114,7 +170,7 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
* For building with MS Visual Studio 13 you will need Update 4 installed.
|
||||
* Read and follow the instructions at CMakeLists.txt
|
||||
* Or install via [vcpkg](https://github.com/microsoft/vcpkg)
|
||||
* run `vcpkg install rocksdb`
|
||||
* run `vcpkg install rocksdb:x64-windows`
|
||||
|
||||
* **AIX 6.1**
|
||||
* Install AIX Toolbox rpms with gcc
|
||||
|
||||
@@ -14,3 +14,5 @@ This is the list of all known third-party language bindings for RocksDB. If some
|
||||
* https://github.com/bh1xuw/rust-rocks
|
||||
* D programming language - https://github.com/b1naryth1ef/rocksdb
|
||||
* Erlang - https://gitlab.com/barrel-db/erlang-rocksdb
|
||||
* Elixir - https://github.com/urbint/rox
|
||||
* Nim - https://github.com/status-im/nim-rocksdb
|
||||
@@ -96,11 +96,21 @@ OPT += -momit-leaf-frame-pointer
|
||||
endif
|
||||
endif
|
||||
|
||||
# if we're compiling for release, compile without debug code (-DNDEBUG) and
|
||||
# don't treat warnings as errors
|
||||
ifeq (,$(shell $(CXX) -fsyntax-only -maltivec -xc /dev/null 2>&1))
|
||||
CXXFLAGS += -DHAS_ALTIVEC
|
||||
CFLAGS += -DHAS_ALTIVEC
|
||||
HAS_ALTIVEC=1
|
||||
endif
|
||||
|
||||
ifeq (,$(shell $(CXX) -fsyntax-only -mcpu=power8 -xc /dev/null 2>&1))
|
||||
CXXFLAGS += -DHAVE_POWER8
|
||||
CFLAGS += -DHAVE_POWER8
|
||||
HAVE_POWER8=1
|
||||
endif
|
||||
|
||||
# if we're compiling for release, compile without debug code (-DNDEBUG)
|
||||
ifeq ($(DEBUG_LEVEL),0)
|
||||
OPT += -DNDEBUG
|
||||
DISABLE_WARNING_AS_ERROR=1
|
||||
|
||||
ifneq ($(USE_RTTI), 1)
|
||||
CXXFLAGS += -fno-rtti
|
||||
@@ -222,9 +232,18 @@ endif
|
||||
# USAN doesn't work well with jemalloc. If we're compiling with USAN, we should use regular malloc.
|
||||
ifdef COMPILE_WITH_UBSAN
|
||||
DISABLE_JEMALLOC=1
|
||||
EXEC_LDFLAGS += -fsanitize=undefined
|
||||
PLATFORM_CCFLAGS += -fsanitize=undefined -DROCKSDB_UBSAN_RUN
|
||||
PLATFORM_CXXFLAGS += -fsanitize=undefined -DROCKSDB_UBSAN_RUN
|
||||
# Suppress alignment warning because murmurhash relies on casting unaligned
|
||||
# memory to integer. Fixing it may cause performance regression. 3-way crc32
|
||||
# relies on it too, although it can be rewritten to eliminate with minimal
|
||||
# performance regression.
|
||||
EXEC_LDFLAGS += -fsanitize=undefined -fno-sanitize-recover=all
|
||||
PLATFORM_CCFLAGS += -fsanitize=undefined -fno-sanitize-recover=all -DROCKSDB_UBSAN_RUN
|
||||
PLATFORM_CXXFLAGS += -fsanitize=undefined -fno-sanitize-recover=all -DROCKSDB_UBSAN_RUN
|
||||
endif
|
||||
|
||||
ifdef ROCKSDB_VALGRIND_RUN
|
||||
PLATFORM_CCFLAGS += -DROCKSDB_VALGRIND_RUN
|
||||
PLATFORM_CXXFLAGS += -DROCKSDB_VALGRIND_RUN
|
||||
endif
|
||||
|
||||
ifndef DISABLE_JEMALLOC
|
||||
@@ -259,6 +278,10 @@ default: all
|
||||
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
|
||||
-Wno-unused-parameter
|
||||
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
WARNING_FLAGS += -Wno-unused-lambda-capture
|
||||
endif
|
||||
|
||||
ifndef DISABLE_WARNING_AS_ERROR
|
||||
WARNING_FLAGS += -Werror
|
||||
endif
|
||||
@@ -290,6 +313,9 @@ LDFLAGS += $(LUA_LIB)
|
||||
|
||||
endif
|
||||
|
||||
ifeq ($(NO_THREEWAY_CRC32C), 1)
|
||||
CXXFLAGS += -DNO_THREEWAY_CRC32C
|
||||
endif
|
||||
|
||||
CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers
|
||||
@@ -322,6 +348,14 @@ util/build_version.cc: FORCE
|
||||
endif
|
||||
|
||||
LIBOBJECTS = $(LIB_SOURCES:.cc=.o)
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
LIB_CC_OBJECTS = $(LIB_SOURCES:.cc=.o)
|
||||
LIBOBJECTS += $(LIB_SOURCES_C:.c=.o)
|
||||
LIBOBJECTS += $(LIB_SOURCES_ASM:.S=.o)
|
||||
else
|
||||
LIB_CC_OBJECTS = $(LIB_SOURCES:.cc=.o)
|
||||
endif
|
||||
|
||||
LIBOBJECTS += $(TOOL_LIB_SOURCES:.cc=.o)
|
||||
MOCKOBJECTS = $(MOCK_LIB_SOURCES:.cc=.o)
|
||||
|
||||
@@ -360,6 +394,7 @@ TESTS = \
|
||||
db_wal_test \
|
||||
db_block_cache_test \
|
||||
db_test \
|
||||
db_blob_index_test \
|
||||
db_bloom_filter_test \
|
||||
db_iter_test \
|
||||
db_log_iter_test \
|
||||
@@ -375,7 +410,6 @@ TESTS = \
|
||||
db_range_del_test \
|
||||
db_sst_test \
|
||||
db_tailing_iter_test \
|
||||
db_universal_compaction_test \
|
||||
db_io_failure_test \
|
||||
db_properties_test \
|
||||
db_table_properties_test \
|
||||
@@ -476,11 +510,14 @@ TESTS = \
|
||||
object_registry_test \
|
||||
repair_test \
|
||||
env_timed_test \
|
||||
write_prepared_transaction_test \
|
||||
db_universal_compaction_test \
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_merge_operator_test \
|
||||
db_sst_test \
|
||||
db_test \
|
||||
db_universal_compaction_test \
|
||||
@@ -491,8 +528,13 @@ PARALLEL_TEST = \
|
||||
manual_compaction_test \
|
||||
persistent_cache_test \
|
||||
table_test \
|
||||
transaction_test
|
||||
transaction_test \
|
||||
write_prepared_transaction_test \
|
||||
|
||||
# options_settable_test doesn't pass with UBSAN as we use hack in the test
|
||||
ifdef COMPILE_WITH_UBSAN
|
||||
TESTS := $(shell echo $(TESTS) | sed 's/\boptions_settable_test\b//g')
|
||||
endif
|
||||
SUBSET := $(TESTS)
|
||||
ifdef ROCKSDBTESTS_START
|
||||
SUBSET := $(shell echo $(SUBSET) | sed 's/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/')
|
||||
@@ -571,14 +613,35 @@ $(SHARED2): $(SHARED4)
|
||||
$(SHARED3): $(SHARED4)
|
||||
ln -fs $(SHARED4) $(SHARED3)
|
||||
endif
|
||||
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
SHARED_C_OBJECTS = $(LIB_SOURCES_C:.c=.o)
|
||||
SHARED_ASM_OBJECTS = $(LIB_SOURCES_ASM:.S=.o)
|
||||
SHARED_C_LIBOBJECTS = $(patsubst %.o,shared-objects/%.o,$(SHARED_C_OBJECTS))
|
||||
SHARED_ASM_LIBOBJECTS = $(patsubst %.o,shared-objects/%.o,$(SHARED_ASM_OBJECTS))
|
||||
shared_libobjects = $(patsubst %,shared-objects/%,$(LIB_CC_OBJECTS))
|
||||
else
|
||||
shared_libobjects = $(patsubst %,shared-objects/%,$(LIBOBJECTS))
|
||||
CLEAN_FILES += shared-objects
|
||||
endif
|
||||
|
||||
CLEAN_FILES += shared-objects
|
||||
shared_all_libobjects = $(shared_libobjects)
|
||||
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
shared-ppc-objects = $(SHARED_C_LIBOBJECTS) $(SHARED_ASM_LIBOBJECTS)
|
||||
|
||||
shared-objects/util/crc32c_ppc.o: util/crc32c_ppc.c
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
shared-objects/util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
|
||||
endif
|
||||
$(shared_libobjects): shared-objects/%.o: %.cc
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) -c $< -o $@
|
||||
|
||||
$(SHARED4): $(shared_libobjects)
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
shared_all_libobjects = $(shared_libobjects) $(shared-ppc-objects)
|
||||
endif
|
||||
$(SHARED4): $(shared_all_libobjects)
|
||||
$(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(shared_libobjects) $(LDFLAGS) -o $@
|
||||
|
||||
endif # PLATFORM_SHARED_EXT
|
||||
@@ -615,7 +678,7 @@ coverage:
|
||||
COVERAGEFLAGS="-fprofile-arcs -ftest-coverage" LDFLAGS+="-lgcov" $(MAKE) J=1 all check
|
||||
cd coverage && ./coverage_test.sh
|
||||
# Delete intermediate files
|
||||
find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
$(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
|
||||
ifneq (,$(filter check parallel_check,$(MAKECMDGOALS)),)
|
||||
# Use /dev/shm if it has the sticky bit set (otherwise, /tmp),
|
||||
@@ -700,7 +763,7 @@ gen_parallel_tests:
|
||||
# 107.816 PASS t/DBTest.EncodeDecompressedBlockSizeTest
|
||||
#
|
||||
slow_test_regexp = \
|
||||
^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$
|
||||
^.*SnapshotConcurrentAccessTest.*$$|^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$
|
||||
prioritize_long_running_tests = \
|
||||
perl -pe 's,($(slow_test_regexp)),100 $$1,' \
|
||||
| sort -k1,1gr \
|
||||
@@ -762,7 +825,7 @@ CLEAN_FILES += t LOG $(TMPD)
|
||||
# regardless of their duration. As with any use of "watch", hit ^C to
|
||||
# interrupt.
|
||||
watch-log:
|
||||
watch --interval=0 'sort -k7,7nr -k4,4gr LOG|$(quoted_perl_command)'
|
||||
$(WATCH) --interval=0 'sort -k7,7nr -k4,4gr LOG|$(quoted_perl_command)'
|
||||
|
||||
# If J != 1 and GNU parallel is installed, run the tests in parallel,
|
||||
# via the check_0 rule above. Otherwise, run them sequentially.
|
||||
@@ -830,7 +893,7 @@ ubsan_crash_test:
|
||||
$(MAKE) clean
|
||||
|
||||
valgrind_test:
|
||||
DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
|
||||
valgrind_check: $(TESTS)
|
||||
$(MAKE) DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" gen_parallel_tests
|
||||
@@ -927,14 +990,14 @@ rocksdb.h rocksdb.cc: build_tools/amalgamate.py Makefile $(LIB_SOURCES) unity.cc
|
||||
clean:
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(LIBRARY) $(SHARED)
|
||||
rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
|
||||
find . -name "*.[oda]" -exec rm -f {} \;
|
||||
find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
$(FIND) . -name "*.[oda]" -exec rm -f {} \;
|
||||
$(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
rm -rf bzip2* snappy* zlib* lz4* zstd*
|
||||
cd java; $(MAKE) clean
|
||||
|
||||
tags:
|
||||
ctags * -R
|
||||
cscope -b `find . -name '*.cc'` `find . -name '*.h'` `find . -name '*.c'`
|
||||
ctags -R .
|
||||
cscope -b `$(FIND) . -name '*.cc'` `$(FIND) . -name '*.h'` `$(FIND) . -name '*.c'`
|
||||
ctags -e -R -o etags *
|
||||
|
||||
format:
|
||||
@@ -1063,6 +1126,9 @@ db_test: db/db_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_test2: db/db_test2.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_index_test: db/db_blob_index_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_block_cache_test: db/db_block_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1227,7 +1293,7 @@ env_test: env/env_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
fault_injection_test: db/fault_injection_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
rate_limiter_test: util/rate_limiter_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
rate_limiter_test: util/rate_limiter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
delete_scheduler_test: util/delete_scheduler_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
@@ -1392,6 +1458,9 @@ heap_test: util/heap_test.o $(GTEST)
|
||||
transaction_test: utilities/transactions/transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
write_prepared_transaction_test: utilities/transactions/write_prepared_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_dump: tools/sst_dump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1445,10 +1514,10 @@ uninstall:
|
||||
|
||||
install-headers:
|
||||
install -d $(INSTALL_PATH)/lib
|
||||
for header_dir in `find "include/rocksdb" -type d`; do \
|
||||
for header_dir in `$(FIND) "include/rocksdb" -type d`; do \
|
||||
install -d $(INSTALL_PATH)/$$header_dir; \
|
||||
done
|
||||
for header in `find "include/rocksdb" -type f -name *.h`; do \
|
||||
for header in `$(FIND) "include/rocksdb" -type f -name *.h`; do \
|
||||
install -C -m 644 $$header $(INSTALL_PATH)/$$header; \
|
||||
done
|
||||
|
||||
@@ -1475,6 +1544,12 @@ install: install-static
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux
|
||||
ifeq ($(PLATFORM), OS_SOLARIS)
|
||||
ARCH := $(shell isainfo -b)
|
||||
else ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
ifneq (,$(filter $(MACHINE), amd64 arm64 sparc64))
|
||||
ARCH := 64
|
||||
else
|
||||
ARCH := 32
|
||||
endif
|
||||
else
|
||||
ARCH := $(shell getconf LONG_BIT)
|
||||
endif
|
||||
@@ -1499,12 +1574,13 @@ BZIP2_DOWNLOAD_BASE ?= http://www.bzip.org
|
||||
SNAPPY_VER ?= 1.1.4
|
||||
SNAPPY_SHA256 ?= 134bfe122fd25599bb807bb8130e7ba6d9bdb851e0b16efcb83ac4f5d0b70057
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/releases/download
|
||||
LZ4_VER ?= 1.7.5
|
||||
LZ4_SHA256 ?= 0190cacd63022ccb86f44fa5041dc6c3804407ad61550ca21c382827319e7e7e
|
||||
LZ4_VER ?= 1.8.0
|
||||
LZ4_SHA256 ?= 2ca482ea7a9bb103603108b5a7510b7592b90158c151ff50a28f1ca8389fccf6
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.2.0
|
||||
ZSTD_SHA256 ?= 4a7e4593a3638276ca7f2a09dc4f38e674d8317bbea51626393ca73fc047cbfb
|
||||
ZSTD_VER ?= 1.3.3
|
||||
ZSTD_SHA256 ?= a77c47153ee7de02626c5b2a097005786b71688be61e9fb81806a011f90b297b
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
ifeq ($(PLATFORM), OS_MACOSX)
|
||||
ROCKSDBJNILIB = librocksdbjni-osx.jnilib
|
||||
@@ -1517,7 +1593,7 @@ else
|
||||
endif
|
||||
endif
|
||||
ifeq ($(PLATFORM), OS_FREEBSD)
|
||||
JAVA_INCLUDE += -I$(JAVA_HOME)/include/freebsd
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/freebsd
|
||||
ROCKSDBJNILIB = librocksdbjni-freebsd$(ARCH).so
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-freebsd$(ARCH).jar
|
||||
endif
|
||||
@@ -1533,6 +1609,11 @@ ifeq ($(PLATFORM), OS_AIX)
|
||||
EXTRACT_SOURCES = gunzip < TAR_GZ | tar xvf -
|
||||
SNAPPY_MAKE_TARGET = libsnappy.la
|
||||
endif
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/openbsd
|
||||
ROCKSDBJNILIB = librocksdbjni-openbsd$(ARCH).so
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH)-openbsd$(ARCH).jar
|
||||
endif
|
||||
|
||||
libz.a:
|
||||
-rm -rf zlib-$(ZLIB_VER)
|
||||
@@ -1543,7 +1624,7 @@ libz.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf zlib-$(ZLIB_VER).tar.gz
|
||||
cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && make
|
||||
cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && $(MAKE)
|
||||
cp zlib-$(ZLIB_VER)/libz.a .
|
||||
|
||||
libbz2.a:
|
||||
@@ -1555,12 +1636,12 @@ libbz2.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf bzip2-$(BZIP2_VER).tar.gz
|
||||
cd bzip2-$(BZIP2_VER) && make CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}'
|
||||
cd bzip2-$(BZIP2_VER) && $(MAKE) CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}'
|
||||
cp bzip2-$(BZIP2_VER)/libbz2.a .
|
||||
|
||||
libsnappy.a:
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
curl -O -L ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER)/snappy-$(SNAPPY_VER).tar.gz
|
||||
curl -O -L ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER)/snappy-$(SNAPPY_VER).tar.gz
|
||||
SNAPPY_SHA256_ACTUAL=`$(SHA256_CMD) snappy-$(SNAPPY_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(SNAPPY_SHA256)" != "$$SNAPPY_SHA256_ACTUAL" ]; then \
|
||||
echo snappy-$(SNAPPY_VER).tar.gz checksum mismatch, expected=\"$(SNAPPY_SHA256)\" actual=\"$$SNAPPY_SHA256_ACTUAL\"; \
|
||||
@@ -1568,12 +1649,12 @@ libsnappy.a:
|
||||
fi
|
||||
tar xvzf snappy-$(SNAPPY_VER).tar.gz
|
||||
cd snappy-$(SNAPPY_VER) && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --with-pic --enable-static --disable-shared
|
||||
cd snappy-$(SNAPPY_VER) && make ${SNAPPY_MAKE_TARGET}
|
||||
cd snappy-$(SNAPPY_VER) && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/.libs/libsnappy.a .
|
||||
|
||||
liblz4.a:
|
||||
-rm -rf lz4-$(LZ4_VER)
|
||||
curl -O -L ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
|
||||
curl -O -L ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
|
||||
mv v$(LZ4_VER).tar.gz lz4-$(LZ4_VER).tar.gz
|
||||
LZ4_SHA256_ACTUAL=`$(SHA256_CMD) lz4-$(LZ4_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(LZ4_SHA256)" != "$$LZ4_SHA256_ACTUAL" ]; then \
|
||||
@@ -1581,12 +1662,12 @@ liblz4.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf lz4-$(LZ4_VER).tar.gz
|
||||
cd lz4-$(LZ4_VER)/lib && make CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
|
||||
cd lz4-$(LZ4_VER)/lib && $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
|
||||
cp lz4-$(LZ4_VER)/lib/liblz4.a .
|
||||
|
||||
libzstd.a:
|
||||
-rm -rf zstd-$(ZSTD_VER)
|
||||
curl -O -L ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
|
||||
curl -O -L ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
|
||||
mv v$(ZSTD_VER).tar.gz zstd-$(ZSTD_VER).tar.gz
|
||||
ZSTD_SHA256_ACTUAL=`$(SHA256_CMD) zstd-$(ZSTD_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZSTD_SHA256)" != "$$ZSTD_SHA256_ACTUAL" ]; then \
|
||||
@@ -1594,29 +1675,45 @@ libzstd.a:
|
||||
exit 1; \
|
||||
fi
|
||||
tar xvzf zstd-$(ZSTD_VER).tar.gz
|
||||
cd zstd-$(ZSTD_VER)/lib && make CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
|
||||
cd zstd-$(ZSTD_VER)/lib && DESTDIR=. PREFIX= $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' install
|
||||
cp zstd-$(ZSTD_VER)/lib/libzstd.a .
|
||||
|
||||
# A version of each $(LIBOBJECTS) compiled with -fPIC and a fixed set of static compression libraries
|
||||
java_static_libobjects = $(patsubst %,jls/%,$(LIBOBJECTS))
|
||||
java_static_libobjects = $(patsubst %,jls/%,$(LIB_CC_OBJECTS))
|
||||
CLEAN_FILES += jls
|
||||
java_static_all_libobjects = $(java_static_libobjects)
|
||||
|
||||
ifneq ($(ROCKSDB_JAVA_NO_COMPRESSION), 1)
|
||||
JAVA_COMPRESSIONS = libz.a libbz2.a libsnappy.a liblz4.a libzstd.a
|
||||
endif
|
||||
|
||||
JAVA_STATIC_FLAGS = -DZLIB -DBZIP2 -DSNAPPY -DLZ4 -DZSTD
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib/include
|
||||
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
JAVA_STATIC_C_LIBOBJECTS = $(patsubst %.c.o,jls/%.c.o,$(LIB_SOURCES_C:.c=.o))
|
||||
JAVA_STATIC_ASM_LIBOBJECTS = $(patsubst %.S.o,jls/%.S.o,$(LIB_SOURCES_ASM:.S=.o))
|
||||
|
||||
java_static_ppc_libobjects = $(JAVA_STATIC_C_LIBOBJECTS) $(JAVA_STATIC_ASM_LIBOBJECTS)
|
||||
|
||||
jls/util/crc32c_ppc.o: util/crc32c_ppc.c
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -c $< -o $@
|
||||
|
||||
jls/util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -c $< -o $@
|
||||
|
||||
java_static_all_libobjects += $(java_static_ppc_libobjects)
|
||||
endif
|
||||
|
||||
$(java_static_libobjects): jls/%.o: %.cc $(JAVA_COMPRESSIONS)
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
|
||||
rocksdbjavastatic: $(java_static_libobjects)
|
||||
rocksdbjavastatic: $(java_static_all_libobjects)
|
||||
cd java;$(MAKE) javalib;
|
||||
rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC \
|
||||
-o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) \
|
||||
$(java_static_libobjects) $(COVERAGEFLAGS) \
|
||||
$(java_static_all_libobjects) $(COVERAGEFLAGS) \
|
||||
$(JAVA_COMPRESSIONS) $(JAVA_STATIC_LDFLAGS)
|
||||
cd java/target;strip $(STRIPFLAGS) $(ROCKSDBJNILIB)
|
||||
cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md
|
||||
@@ -1646,6 +1743,14 @@ rocksdbjavastaticreleasedocker: rocksdbjavastatic
|
||||
cd java/target;jar -uf $(ROCKSDB_JAR_ALL) librocksdbjni-*.so librocksdbjni-*.jnilib
|
||||
cd java/target/classes;jar -uf ../$(ROCKSDB_JAR_ALL) org/rocksdb/*.class org/rocksdb/util/*.class
|
||||
|
||||
rocksdbjavastaticdockerppc64le:
|
||||
mkdir -p java/target
|
||||
DOCKER_LINUX_PPC64LE_CONTAINER=`docker ps -aqf name=rocksdb_linux_ppc64le-be`; \
|
||||
if [ -z "$$DOCKER_LINUX_PPC64LE_CONTAINER" ]; then \
|
||||
docker container create --attach stdin --attach stdout --attach stderr --volume `pwd`:/rocksdb-host --name rocksdb_linux_ppc64le-be evolvedbinary/rocksjava:centos7_ppc64le-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh; \
|
||||
fi
|
||||
docker start -a rocksdb_linux_ppc64le-be
|
||||
|
||||
rocksdbjavastaticpublish: rocksdbjavastaticrelease rocksdbjavastaticpublishcentral
|
||||
|
||||
rocksdbjavastaticpublishdocker: rocksdbjavastaticreleasedocker rocksdbjavastaticpublishcentral
|
||||
@@ -1660,13 +1765,36 @@ rocksdbjavastaticpublishcentral:
|
||||
mvn gpg:sign-and-deploy-file -Durl=https://oss.sonatype.org/service/local/staging/deploy/maven2/ -DrepositoryId=sonatype-nexus-staging -DpomFile=java/rocksjni.pom -Dfile=java/target/rocksdbjni-$(ROCKSDB_MAJOR).$(ROCKSDB_MINOR).$(ROCKSDB_PATCH).jar
|
||||
|
||||
# A version of each $(LIBOBJECTS) compiled with -fPIC
|
||||
java_libobjects = $(patsubst %,jl/%,$(LIBOBJECTS))
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
JAVA_CC_OBJECTS = $(SHARED_CC_OBJECTS)
|
||||
JAVA_C_OBJECTS = $(SHARED_C_OBJECTS)
|
||||
JAVA_ASM_OBJECTS = $(SHARED_ASM_OBJECTS)
|
||||
|
||||
JAVA_C_LIBOBJECTS = $(patsubst %.c.o,jl/%.c.o,$(JAVA_C_OBJECTS))
|
||||
JAVA_ASM_LIBOBJECTS = $(patsubst %.S.o,jl/%.S.o,$(JAVA_ASM_OBJECTS))
|
||||
endif
|
||||
|
||||
java_libobjects = $(patsubst %,jl/%,$(LIB_CC_OBJECTS))
|
||||
CLEAN_FILES += jl
|
||||
java_all_libobjects = $(java_libobjects)
|
||||
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
java_ppc_libobjects = $(JAVA_C_LIBOBJECTS) $(JAVA_ASM_LIBOBJECTS)
|
||||
|
||||
jl/crc32c_ppc.o: util/crc32c_ppc.c
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
jl/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
|
||||
java_all_libobjects += $(java_ppc_libobjects)
|
||||
endif
|
||||
|
||||
$(java_libobjects): jl/%.o: %.cc
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
|
||||
rocksdbjava: $(java_libobjects)
|
||||
|
||||
|
||||
rocksdbjava: $(java_all_libobjects)
|
||||
$(AM_V_GEN)cd java;$(MAKE) javalib;
|
||||
$(AM_V_at)rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
$(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(java_libobjects) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
|
||||
@@ -1721,30 +1849,54 @@ IOSVERSION=$(shell defaults read $(PLATFORMSROOT)/iPhoneOS.platform/version CFBu
|
||||
lipo ios-x86/$@ ios-arm/$@ -create -output $@
|
||||
|
||||
else
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
util/crc32c_ppc.o: util/crc32c_ppc.c
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
util/crc32c_ppc_asm.o: util/crc32c_ppc_asm.S
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
|
||||
endif
|
||||
.cc.o:
|
||||
$(AM_V_CC)$(CXX) $(CXXFLAGS) -c $< -o $@ $(COVERAGEFLAGS)
|
||||
|
||||
.c.o:
|
||||
$(AM_V_CC)$(CC) $(CFLAGS) -c $< -o $@
|
||||
endif
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source files dependencies detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
all_sources = $(LIB_SOURCES) $(MAIN_SOURCES) $(MOCK_LIB_SOURCES) $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(TEST_LIB_SOURCES) $(EXP_LIB_SOURCES)
|
||||
DEPFILES = $(all_sources:.cc=.d)
|
||||
DEPFILES = $(all_sources:.cc=.cc.d)
|
||||
|
||||
# Add proper dependency support so changing a .h file forces a .cc file to
|
||||
# rebuild.
|
||||
|
||||
# The .d file indicates .cc file's dependencies on .h files. We generate such
|
||||
# dependency by g++'s -MM option, whose output is a make dependency rule.
|
||||
$(DEPFILES): %.d: %.cc
|
||||
%.cc.d: %.cc
|
||||
@$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \
|
||||
-MM -MT'$@' -MT'$(<:.cc=.o)' "$<" -o '$@'
|
||||
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
DEPFILES_C = $(LIB_SOURCES_C:.c=.c.d)
|
||||
DEPFILES_ASM = $(LIB_SOURCES_ASM:.S=.S.d)
|
||||
|
||||
%.c.d: %.c
|
||||
@$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \
|
||||
-MM -MT'$@' -MT'$(<:.c=.o)' "$<" -o '$@'
|
||||
|
||||
%.S.d: %.S
|
||||
@$(CXX) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) \
|
||||
-MM -MT'$@' -MT'$(<:.S=.o)' "$<" -o '$@'
|
||||
|
||||
$(DEPFILES_C): %.c.d
|
||||
|
||||
$(DEPFILES_ASM): %.S.d
|
||||
depend: $(DEPFILES) $(DEPFILES_C) $(DEPFILES_ASM)
|
||||
else
|
||||
depend: $(DEPFILES)
|
||||
endif
|
||||
|
||||
# if the make goal is either "clean" or "format", we shouldn't
|
||||
# try to import the *.d files.
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](http://140.211.168.68:8080/job/Rocksdb)
|
||||
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
@@ -25,3 +26,7 @@ rely on the details of any other header files in this package. Those
|
||||
internal APIs may be changed without warning.
|
||||
|
||||
Design discussions are conducted in https://www.facebook.com/groups/rocksdb.dev/
|
||||
|
||||
## License
|
||||
|
||||
RocksDB is dual-licensed under both the GPLv2 (found in the COPYING file in the root directory) and Apache 2.0 License (found in the LICENSE.Apache file in the root directory). You may select, at your option, one of the above-listed licenses.
|
||||
+1
-1
@@ -5,7 +5,7 @@ RocksDBLite is a project focused on mobile use cases, which don't need a lot of
|
||||
Some examples of the features disabled by ROCKSDB_LITE:
|
||||
* compiled-in support for LDB tool
|
||||
* No backupable DB
|
||||
* No support for replication (which we provide in form of TrasactionalIterator)
|
||||
* No support for replication (which we provide in form of TransactionalIterator)
|
||||
* No advanced monitoring tools
|
||||
* No special-purpose memtables that are highly optimized for specific use cases
|
||||
* No Transactions
|
||||
|
||||
@@ -5,12 +5,14 @@ At Facebook, we use RocksDB as storage engines in multiple data management servi
|
||||
|
||||
1. MyRocks -- https://github.com/MySQLOnRocksDB/mysql-5.6
|
||||
2. MongoRocks -- https://github.com/mongodb-partners/mongo-rocks
|
||||
3. ZippyDB -- Facebook's distributed key-value store with Paxos-style replication, built on top of RocksDB.[*] https://www.youtube.com/watch?v=DfiN7pG0D0khtt
|
||||
4. Laser -- Laser is a high query throughput, low (millisecond) latency, key-value storage service built on top of RocksDB.[*]
|
||||
3. ZippyDB -- Facebook's distributed key-value store with Paxos-style replication, built on top of RocksDB.[1] https://www.youtube.com/watch?v=DfiN7pG0D0khtt
|
||||
4. Laser -- Laser is a high query throughput, low (millisecond) latency, key-value storage service built on top of RocksDB.[1]
|
||||
4. Dragon -- a distributed graph query engine. https://code.facebook.com/posts/1737605303120405/dragon-a-distributed-graph-query-engine/
|
||||
5. Stylus -- a low-level stream processing framework writtenin C++.[*]
|
||||
5. Stylus -- a low-level stream processing framework writtenin C++.[1]
|
||||
6. LogDevice -- a distributed data store for logs [2]
|
||||
|
||||
[*] https://research.facebook.com/publications/realtime-data-processing-at-facebook/
|
||||
[1] https://research.facebook.com/publications/realtime-data-processing-at-facebook/
|
||||
[2] https://code.facebook.com/posts/357056558062811/logdevice-a-distributed-data-store-for-logs/
|
||||
|
||||
## LinkedIn
|
||||
Two different use cases at Linkedin are using RocksDB as a storage engine:
|
||||
@@ -83,3 +85,6 @@ quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
|
||||
## LzLabs
|
||||
LzLabs is using RocksDB as a storage engine in their multi-database distributed framework to store application configuration and user data.
|
||||
|
||||
## ProfaneDB
|
||||
[ProfaneDB](https://profanedb.gitlab.io/) is a database for Protocol Buffers, and uses RocksDB for storage. It is accessible via gRPC, and the schema is defined using directly `.proto` files.
|
||||
Vendored
+5
@@ -14,6 +14,11 @@ Vagrant.configure("2") do |config|
|
||||
box.vm.box = "chef/centos-6.5"
|
||||
end
|
||||
|
||||
config.vm.define "centos7" do |box|
|
||||
box.vm.box = "centos/7"
|
||||
box.vm.provision "shell", path: "build_tools/setup_centos7.sh"
|
||||
end
|
||||
|
||||
config.vm.define "FreeBSD10" do |box|
|
||||
box.vm.guest = :freebsd
|
||||
box.vm.box = "robin/freebsd-10"
|
||||
|
||||
+3
-3
@@ -1,14 +1,14 @@
|
||||
version: 1.0.{build}
|
||||
image: Visual Studio 2015
|
||||
image: Visual Studio 2017
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 14 2015 Win64" -DOPTDBG=1 -DXPRESS=1 ..
|
||||
- cmake -G "Visual Studio 15 Win64" -DOPTDBG=1 -DWITH_XPRESS=1 -DPORTABLE=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
verbosity: normal
|
||||
test:
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1 -SuiteRun db_basic_test,db_test2,db_test,env_basic_test,env_test -Concurrency 8
|
||||
|
||||
@@ -36,7 +36,7 @@ def parse_src_mk(repo_path):
|
||||
# get all .cc / .c files
|
||||
def get_cc_files(repo_path):
|
||||
cc_files = []
|
||||
for root, dirnames, filenames in os.walk(repo_path):
|
||||
for root, dirnames, filenames in os.walk(repo_path): # noqa: B007 T25377293 Grandfathered in
|
||||
root = root[(len(repo_path) + 1):]
|
||||
if "java" in root:
|
||||
# Skip java
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create a tmp directory for the test to use
|
||||
TEST_DIR=$(mktemp -d /dev/shm/fbcode_rocksdb_XXXXXXX)
|
||||
# shellcheck disable=SC2068
|
||||
TEST_TMPDIR="$TEST_DIR" $@ && rm -rf "$TEST_DIR"
|
||||
@@ -3,19 +3,17 @@ from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import targets_cfg
|
||||
import pprint
|
||||
|
||||
# TODO(tec): replace this with PrettyPrinter
|
||||
def pretty_list(lst, indent=6):
|
||||
def pretty_list(lst, indent=8):
|
||||
if lst is None or len(lst) == 0:
|
||||
return ""
|
||||
|
||||
if len(lst) == 1:
|
||||
return "\"%s\"" % lst[0]
|
||||
|
||||
|
||||
separator = "\",\n%s\"" % (" " * indent)
|
||||
res = separator.join(lst)
|
||||
res = "\n" + (" " * indent) + "\"" + res + "\",\n" + (" " * (indent - 2))
|
||||
res = separator.join(sorted(lst))
|
||||
res = "\n" + (" " * indent) + "\"" + res + "\",\n" + (" " * (indent - 4))
|
||||
return res
|
||||
|
||||
|
||||
@@ -27,7 +25,7 @@ class TARGETSBuilder:
|
||||
self.total_lib = 0
|
||||
self.total_bin = 0
|
||||
self.total_test = 0
|
||||
self.tests_cfg = []
|
||||
self.tests_cfg = ""
|
||||
|
||||
def __del__(self):
|
||||
self.targets_file.close()
|
||||
@@ -37,8 +35,8 @@ class TARGETSBuilder:
|
||||
headers = "AutoHeaders.RECURSIVE_GLOB"
|
||||
self.targets_file.write(targets_cfg.library_template % (
|
||||
name,
|
||||
headers,
|
||||
pretty_list(srcs),
|
||||
headers,
|
||||
pretty_list(deps)))
|
||||
self.total_lib = self.total_lib + 1
|
||||
|
||||
@@ -53,13 +51,13 @@ class TARGETSBuilder:
|
||||
exec_mode = "serial"
|
||||
if is_parallel:
|
||||
exec_mode = "parallel"
|
||||
self.tests_cfg.append([test_name, str(src), str(exec_mode)])
|
||||
self.tests_cfg += targets_cfg.test_cfg_template % (
|
||||
test_name,
|
||||
str(src),
|
||||
str(exec_mode))
|
||||
|
||||
self.total_test = self.total_test + 1
|
||||
|
||||
def flush_tests(self):
|
||||
self.targets_file.write(targets_cfg.unittests_template % (
|
||||
pprint.PrettyPrinter().pformat(self.tests_cfg)
|
||||
))
|
||||
|
||||
self.tests_cfg = []
|
||||
self.targets_file.write(targets_cfg.unittests_template % self.tests_cfg)
|
||||
self.tests_cfg = ""
|
||||
+92
-71
@@ -2,123 +2,144 @@ from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
rocksdb_target_header = """
|
||||
import os
|
||||
rocksdb_target_header = """REPO_PATH = package_name() + "/"
|
||||
|
||||
TARGETS_PATH = os.path.dirname(__file__)
|
||||
REPO_PATH = "rocksdb/src/"
|
||||
BUCK_BINS = "buck-out/gen/" + REPO_PATH
|
||||
|
||||
TEST_RUNNER = REPO_PATH + "buckifier/rocks_test_runner.sh"
|
||||
|
||||
rocksdb_compiler_flags = [
|
||||
"-fno-builtin-memcmp",
|
||||
"-DROCKSDB_PLATFORM_POSIX",
|
||||
"-DROCKSDB_LIB_IO_POSIX",
|
||||
"-DROCKSDB_FALLOCATE_PRESENT",
|
||||
"-DROCKSDB_MALLOC_USABLE_SIZE",
|
||||
"-DROCKSDB_RANGESYNC_PRESENT",
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DROCKSDB_SUPPORT_THREAD_LOCAL",
|
||||
"-DOS_LINUX",
|
||||
# Flags to enable libs we include
|
||||
"-DSNAPPY",
|
||||
"-DZLIB",
|
||||
"-DBZIP2",
|
||||
"-DLZ4",
|
||||
"-DZSTD",
|
||||
"-DGFLAGS=gflags",
|
||||
"-DNUMA",
|
||||
"-DTBB",
|
||||
# Needed to compile in fbcode
|
||||
"-Wno-expansion-to-defined",
|
||||
"-fno-builtin-memcmp",
|
||||
"-DROCKSDB_PLATFORM_POSIX",
|
||||
"-DROCKSDB_LIB_IO_POSIX",
|
||||
"-DROCKSDB_FALLOCATE_PRESENT",
|
||||
"-DROCKSDB_MALLOC_USABLE_SIZE",
|
||||
"-DROCKSDB_RANGESYNC_PRESENT",
|
||||
"-DROCKSDB_SCHED_GETCPU_PRESENT",
|
||||
"-DROCKSDB_SUPPORT_THREAD_LOCAL",
|
||||
"-DOS_LINUX",
|
||||
# Flags to enable libs we include
|
||||
"-DSNAPPY",
|
||||
"-DZLIB",
|
||||
"-DBZIP2",
|
||||
"-DLZ4",
|
||||
"-DZSTD",
|
||||
"-DGFLAGS=gflags",
|
||||
"-DNUMA",
|
||||
"-DTBB",
|
||||
# Needed to compile in fbcode
|
||||
"-Wno-expansion-to-defined",
|
||||
]
|
||||
|
||||
rocksdb_external_deps = [
|
||||
('bzip2', None, 'bz2'),
|
||||
('snappy', None, "snappy"),
|
||||
('zlib', None, 'z'),
|
||||
('gflags', None, 'gflags'),
|
||||
('lz4', None, 'lz4'),
|
||||
('zstd', None),
|
||||
('tbb', None),
|
||||
("numa", None, "numa"),
|
||||
("googletest", None, "gtest"),
|
||||
("bzip2", None, "bz2"),
|
||||
("snappy", None, "snappy"),
|
||||
("zlib", None, "z"),
|
||||
("gflags", None, "gflags"),
|
||||
("lz4", None, "lz4"),
|
||||
("zstd", None),
|
||||
("tbb", None),
|
||||
("numa", None, "numa"),
|
||||
("googletest", None, "gtest"),
|
||||
]
|
||||
|
||||
rocksdb_preprocessor_flags = [
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
]
|
||||
|
||||
rocksdb_arch_preprocessor_flags = {
|
||||
"x86_64": ["-DHAVE_SSE42"],
|
||||
"x86_64": [
|
||||
"-DHAVE_SSE42",
|
||||
"-DHAVE_PCLMUL",
|
||||
],
|
||||
}
|
||||
|
||||
build_mode = read_config("fbcode", "build_mode")
|
||||
|
||||
is_opt_mode = build_mode.startswith("opt")
|
||||
|
||||
# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice
|
||||
# doesn't harm and avoid forgetting to add it.
|
||||
if is_opt_mode:
|
||||
rocksdb_compiler_flags.append("-DNDEBUG")
|
||||
"""
|
||||
|
||||
|
||||
library_template = """
|
||||
cpp_library(
|
||||
name = "%s",
|
||||
headers = %s,
|
||||
srcs = [%s],
|
||||
deps = [%s],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
headers = %s,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
deps = [%s],
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
"""
|
||||
|
||||
binary_template = """
|
||||
cpp_binary(
|
||||
name = "%s",
|
||||
srcs = [%s],
|
||||
deps = [%s],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
name = "%s",
|
||||
srcs = [%s],
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
deps = [%s],
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
"""
|
||||
|
||||
test_cfg_template = """ [
|
||||
"%s",
|
||||
"%s",
|
||||
"%s",
|
||||
],
|
||||
"""
|
||||
|
||||
unittests_template = """
|
||||
# [test_name, test_src, test_type]
|
||||
ROCKS_TESTS = %s
|
||||
|
||||
ROCKS_TESTS = [
|
||||
%s]
|
||||
|
||||
# Generate a test rule for each entry in ROCKS_TESTS
|
||||
for test_cfg in ROCKS_TESTS:
|
||||
test_name = test_cfg[0]
|
||||
test_cc = test_cfg[1]
|
||||
ttype = "gtest" if test_cfg[2] == "parallel" else "simple"
|
||||
test_bin = test_name + "_bin"
|
||||
# Do not build the tests in opt mode, since SyncPoint and other test code
|
||||
# will not be included.
|
||||
if not is_opt_mode:
|
||||
for test_cfg in ROCKS_TESTS:
|
||||
test_name = test_cfg[0]
|
||||
test_cc = test_cfg[1]
|
||||
ttype = "gtest" if test_cfg[2] == "parallel" else "simple"
|
||||
test_bin = test_name + "_bin"
|
||||
|
||||
cpp_binary (
|
||||
name = test_bin,
|
||||
srcs = [test_cc],
|
||||
deps = [":rocksdb_test_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
cpp_binary (
|
||||
name = test_bin,
|
||||
srcs = [test_cc],
|
||||
deps = [":rocksdb_test_lib"],
|
||||
preprocessor_flags = rocksdb_preprocessor_flags,
|
||||
arch_preprocessor_flags = rocksdb_arch_preprocessor_flags,
|
||||
compiler_flags = rocksdb_compiler_flags,
|
||||
external_deps = rocksdb_external_deps,
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = test_name,
|
||||
type = ttype,
|
||||
deps = [":" + test_bin],
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin]
|
||||
)
|
||||
custom_unittest(
|
||||
name = test_name,
|
||||
type = ttype,
|
||||
deps = [":" + test_bin],
|
||||
command = [TEST_RUNNER, BUCK_BINS + test_bin]
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdbjavastatic",
|
||||
type = "simple",
|
||||
command = ["internal_repo_rocksdb/make_rocksdbjavastatic.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
|
||||
custom_unittest(
|
||||
name = "make_rocksdb_lite_release",
|
||||
type = "simple",
|
||||
command = ["internal_repo_rocksdb/make_rocksdb_lite_release.sh"],
|
||||
type = "simple",
|
||||
)
|
||||
"""
|
||||
+234
-155
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Detects OS we're compiling on and outputs a file specified by the first
|
||||
# argument, which in turn gets read while processing Makefile.
|
||||
@@ -16,6 +16,8 @@
|
||||
# PLATFORM_CXXFLAGS C++ compiler flags. Will contain:
|
||||
# PLATFORM_SHARED_VERSIONED Set to 'true' if platform supports versioned
|
||||
# shared libraries, empty otherwise.
|
||||
# FIND Command for the find utility
|
||||
# WATCH Command for the watch utility
|
||||
#
|
||||
# The PLATFORM_CCFLAGS and PLATFORM_CXXFLAGS might include the following:
|
||||
#
|
||||
@@ -64,11 +66,19 @@ rm -f "$OUTPUT"
|
||||
touch "$OUTPUT"
|
||||
|
||||
if test -z "$CC"; then
|
||||
CC=cc
|
||||
if [ -x "$(command -v clang)" ]; then
|
||||
CC=clang
|
||||
else
|
||||
CC=cc
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -z "$CXX"; then
|
||||
CXX=g++
|
||||
if [ -x "$(command -v clang++)" ]; then
|
||||
CXX=clang++
|
||||
else
|
||||
CXX=g++
|
||||
fi
|
||||
fi
|
||||
|
||||
# Detect OS
|
||||
@@ -85,7 +95,15 @@ if test -z "$CLANG_SCAN_BUILD"; then
|
||||
fi
|
||||
|
||||
if test -z "$CLANG_ANALYZER"; then
|
||||
CLANG_ANALYZER=$(which clang++ 2> /dev/null)
|
||||
CLANG_ANALYZER=$(command -v clang++ 2> /dev/null)
|
||||
fi
|
||||
|
||||
if test -z "$FIND"; then
|
||||
FIND=find
|
||||
fi
|
||||
|
||||
if test -z "$WATCH"; then
|
||||
WATCH=watch
|
||||
fi
|
||||
|
||||
COMMON_FLAGS="$COMMON_FLAGS ${CFLAGS}"
|
||||
@@ -141,6 +159,7 @@ case "$TARGET_OS" in
|
||||
;;
|
||||
FreeBSD)
|
||||
PLATFORM=OS_FREEBSD
|
||||
CXX=clang++
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_FREEBSD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread"
|
||||
# PORT_FILES=port/freebsd/freebsd_specific.cc
|
||||
@@ -153,9 +172,12 @@ case "$TARGET_OS" in
|
||||
;;
|
||||
OpenBSD)
|
||||
PLATFORM=OS_OPENBSD
|
||||
CXX=clang++
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_OPENBSD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -pthread"
|
||||
# PORT_FILES=port/openbsd/openbsd_specific.cc
|
||||
FIND=gfind
|
||||
WATCH=gnuwatch
|
||||
;;
|
||||
DragonFly)
|
||||
PLATFORM=OS_DRAGONFLYBSD
|
||||
@@ -210,119 +232,137 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether Snappy library is installed
|
||||
# http://code.google.com/p/snappy/
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <snappy.h>
|
||||
int main() {}
|
||||
if ! test $ROCKSDB_DISABLE_SNAPPY; then
|
||||
# Test whether Snappy library is installed
|
||||
# http://code.google.com/p/snappy/
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <snappy.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DSNAPPY"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lsnappy"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lsnappy"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DSNAPPY"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lsnappy"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lsnappy"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether gflags library is installed
|
||||
# http://gflags.github.io/gflags/
|
||||
# check if the namespace is gflags
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace gflags;
|
||||
int main() {}
|
||||
if ! test $ROCKSDB_DISABLE_GFLAGS; then
|
||||
# Test whether gflags library is installed
|
||||
# http://gflags.github.io/gflags/
|
||||
# check if the namespace is gflags
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=gflags"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
else
|
||||
# check if namespace is google
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace google;
|
||||
int main() {}
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
else
|
||||
# check if namespace is google
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace google;
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=google"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
fi
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=google"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether zlib library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zlib.h>
|
||||
int main() {}
|
||||
if ! test $ROCKSDB_DISABLE_ZLIB; then
|
||||
# Test whether zlib library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zlib.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DZLIB"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lz"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lz"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DZLIB"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lz"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lz"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether bzip library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <bzlib.h>
|
||||
int main() {}
|
||||
if ! test $ROCKSDB_DISABLE_BZIP; then
|
||||
# Test whether bzip library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <bzlib.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DBZIP2"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbz2"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lbz2"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DBZIP2"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbz2"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lbz2"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether lz4 library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <lz4.h>
|
||||
#include <lz4hc.h>
|
||||
int main() {}
|
||||
if ! test $ROCKSDB_DISABLE_LZ4; then
|
||||
# Test whether lz4 library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <lz4.h>
|
||||
#include <lz4hc.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DLZ4"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -llz4"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -llz4"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DLZ4"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -llz4"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -llz4"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether zstd library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zstd.h>
|
||||
int main() {}
|
||||
if ! test $ROCKSDB_DISABLE_ZSTD; then
|
||||
# Test whether zstd library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zstd.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DZSTD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lzstd"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lzstd"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DZSTD"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lzstd"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lzstd"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether numa is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
|
||||
#include <numa.h>
|
||||
#include <numaif.h>
|
||||
int main() {}
|
||||
if ! test $ROCKSDB_DISABLE_NUMA; then
|
||||
# Test whether numa is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
|
||||
#include <numa.h>
|
||||
#include <numaif.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DNUMA"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lnuma"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lnuma"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DNUMA"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lnuma"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lnuma"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether tbb is available
|
||||
$CXX $CFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null <<EOF
|
||||
#include <tbb/tbb.h>
|
||||
int main() {}
|
||||
if ! test $ROCKSDB_DISABLE_TBB; then
|
||||
# Test whether tbb is available
|
||||
$CXX $CFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null <<EOF
|
||||
#include <tbb/tbb.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DTBB"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltbb"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltbb"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DTBB"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltbb"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltbb"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
2>/dev/null; then
|
||||
# This will enable some preprocessor identifiers in the Makefile
|
||||
JEMALLOC=1
|
||||
# JEMALLOC can be enabled either using the flag (like here) or by
|
||||
# providing direct link to the jemalloc library
|
||||
WITH_JEMALLOC_FLAG=1
|
||||
else
|
||||
if ! test $ROCKSDB_DISABLE_JEMALLOC; then
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
2>/dev/null; then
|
||||
# This will enable some preprocessor identifiers in the Makefile
|
||||
JEMALLOC=1
|
||||
# JEMALLOC can be enabled either using the flag (like here) or by
|
||||
# providing direct link to the jemalloc library
|
||||
WITH_JEMALLOC_FLAG=1
|
||||
fi
|
||||
fi
|
||||
if ! test $JEMALLOC && ! test $ROCKSDB_DISABLE_TCMALLOC; then
|
||||
# jemalloc is not available. Let's try tcmalloc
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null \
|
||||
-ltcmalloc 2>/dev/null; then
|
||||
@@ -331,88 +371,100 @@ EOF
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether malloc_usable_size is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <malloc.h>
|
||||
int main() {
|
||||
size_t res = malloc_usable_size(0);
|
||||
return 0;
|
||||
}
|
||||
if ! test $ROCKSDB_DISABLE_MALLOC_USABLE_SIZE; then
|
||||
# Test whether malloc_usable_size is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <malloc.h>
|
||||
int main() {
|
||||
size_t res = malloc_usable_size(0);
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <pthread.h>
|
||||
int main() {
|
||||
int x = PTHREAD_MUTEX_ADAPTIVE_NP;
|
||||
return 0;
|
||||
}
|
||||
if ! test $ROCKSDB_DISABLE_PTHREAD_MUTEX_ADAPTIVE_NP; then
|
||||
# Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <pthread.h>
|
||||
int main() {
|
||||
int x = PTHREAD_MUTEX_ADAPTIVE_NP;
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_PTHREAD_ADAPTIVE_MUTEX"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_PTHREAD_ADAPTIVE_MUTEX"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether backtrace is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
backtrace_symbols(frames, backtrace(frames, 1));
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
else
|
||||
# Test whether execinfo library is installed
|
||||
$CXX $CFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
if ! test $ROCKSDB_DISABLE_BACKTRACE; then
|
||||
# Test whether backtrace is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
backtrace_symbols(frames, backtrace(frames, 1));
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lexecinfo"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lexecinfo"
|
||||
else
|
||||
# Test whether execinfo library is installed
|
||||
$CXX $CFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
backtrace_symbols(frames, backtrace(frames, 1));
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lexecinfo"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lexecinfo"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test if -pg is supported
|
||||
$CXX $CFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
if ! test $ROCKSDB_DISABLE_PG; then
|
||||
# Test if -pg is supported
|
||||
$CXX $CFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
PROFILING_FLAGS=-pg
|
||||
if [ "$?" = 0 ]; then
|
||||
PROFILING_FLAGS=-pg
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether sync_file_range is supported for compatibility with an old glibc
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
int main() {
|
||||
int fd = open("/dev/null", 0);
|
||||
sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE);
|
||||
}
|
||||
if ! test $ROCKSDB_DISABLE_SYNC_FILE_RANGE; then
|
||||
# Test whether sync_file_range is supported for compatibility with an old glibc
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
int main() {
|
||||
int fd = open("/dev/null", 0);
|
||||
sync_file_range(fd, 0, 1024, SYNC_FILE_RANGE_WRITE);
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_RANGESYNC_PRESENT"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_RANGESYNC_PRESENT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test whether sched_getcpu is supported
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sched.h>
|
||||
int main() {
|
||||
int cpuid = sched_getcpu();
|
||||
}
|
||||
if ! test $ROCKSDB_DISABLE_SCHED_GETCPU; then
|
||||
# Test whether sched_getcpu is supported
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sched.h>
|
||||
int main() {
|
||||
int cpuid = sched_getcpu();
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_SCHED_GETCPU_PRESENT"
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_SCHED_GETCPU_PRESENT"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -444,18 +496,25 @@ if test "$USE_HDFS"; then
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS $HDFS_LDFLAGS"
|
||||
fi
|
||||
|
||||
if test "$USE_SSE"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -msse4.2"
|
||||
elif test -z "$PORTABLE"; then
|
||||
if test -z "$PORTABLE"; then
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^ppc64`"; then
|
||||
# Tune for this POWER processor, treating '+' models as base models
|
||||
POWER=`LD_SHOW_AUXV=1 /bin/true | grep AT_PLATFORM | grep -E -o power[0-9]+`
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mcpu=$POWER -mtune=$POWER "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z10 "
|
||||
elif [ "$TARGET_OS" != AIX ] && [ "$TARGET_OS" != SunOS ]; then
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^arm`"; then
|
||||
# TODO: Handle this with approprite options.
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif [ "$TARGET_OS" == "IOS" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif [ "$TARGET_OS" != "AIX" ] && [ "$TARGET_OS" != "SunOS" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=native "
|
||||
elif test "$USE_SSE"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -msse4.2 -mpclmul"
|
||||
fi
|
||||
elif test "$USE_SSE"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -msse4.2 -mpclmul"
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
@@ -469,6 +528,24 @@ if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DHAVE_SSE42"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use SSE intrinsics, disabling"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <cstdint>
|
||||
#include <wmmintrin.h>
|
||||
int main() {
|
||||
const auto a = _mm_set_epi64x(0, 0);
|
||||
const auto b = _mm_set_epi64x(0, 0);
|
||||
const auto c = _mm_clmulepi64_si128(a, b, 0x00);
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DHAVE_PCLMUL"
|
||||
elif test "$USE_SSE"; then
|
||||
echo "warning: USE_SSE specified but compiler could not use PCLMUL intrinsics, disabling"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# iOS doesn't support thread-local storage, but this check would erroneously
|
||||
@@ -519,6 +596,8 @@ echo "ROCKSDB_PATCH=$ROCKSDB_PATCH" >> "$OUTPUT"
|
||||
echo "CLANG_SCAN_BUILD=$CLANG_SCAN_BUILD" >> "$OUTPUT"
|
||||
echo "CLANG_ANALYZER=$CLANG_ANALYZER" >> "$OUTPUT"
|
||||
echo "PROFILING_FLAGS=$PROFILING_FLAGS" >> "$OUTPUT"
|
||||
echo "FIND=$FIND" >> "$OUTPUT"
|
||||
echo "WATCH=$WATCH" >> "$OUTPUT"
|
||||
# This will enable some related identifiers for the preprocessor
|
||||
if test -n "$JEMALLOC"; then
|
||||
echo "JEMALLOC=1" >> "$OUTPUT"
|
||||
|
||||
@@ -13,10 +13,12 @@ error=0
|
||||
|
||||
function log {
|
||||
DATE=`date +%Y-%m-%d:%H:%M:%S`
|
||||
# shellcheck disable=SC2068
|
||||
echo $DATE $@
|
||||
}
|
||||
|
||||
function log_err {
|
||||
# shellcheck disable=SC2145
|
||||
log "ERROR: $@ Error code: $error."
|
||||
}
|
||||
|
||||
|
||||
+19
-18
@@ -1,18 +1,19 @@
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/2928bb3ed95bf64f5b388ee88c30dc74710c3b35/5.x/centos6-native/f4950a1
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/a5fea028cb7ba43498976e1f8054b0b2e790c295/stable/centos6-native/6aaf4de
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/7a9099f6587ee4378c0b1fa32bb8934019d30ca4/5.x/gcc-5-glibc-2.23/339d858
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/3b7c6469854dfc7832a1c3cc5b86919a84e5f865/2.23/gcc-5-glibc-2.23/ca1d1c0
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/8c38a4c1e52b4c2cc8a9cdc31b9c947ed7dbfcb4/1.1.3/gcc-5-glibc-2.23/9bc6787
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/d7861abe6f0e27ab98c9303b95a662f0e4cdedb5/1.2.8/gcc-5-glibc-2.23/9bc6787
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/740325875f6729f42d28deaa2147b0854f3a347e/1.0.6/gcc-5-glibc-2.23/9bc6787
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0815d59804160c96caac5f27ca004f51af893dc6/r131/gcc-5-glibc-2.23/9bc6787
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/c15a4f5f619a2930478d01e2e34dc1e0652b0873/1.1.4/gcc-5-glibc-2.23/03859b5
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/f905a5e1032fb30c05db3d3752319857388c0c49/2.2.0/gcc-5-glibc-2.23/9bc6787
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/8d60633d822a2a55849c73db24e74a25e52b71db/master/gcc-5-glibc-2.23/1c32b4b
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/17c514c4d102a25ca15f4558be564eeed76f4b6a/2.0.8/gcc-5-glibc-2.23/9bc6787
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/8db74270cd6d0212ac92d69e7fc7beefe617d772/trunk/gcc-5-glibc-2.23/b1847cb
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/9d9a554877d0c5bef330fe818ab7178806dd316a/4.0_update2/gcc-5-glibc-2.23/9bc6787
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/90c9734afc5579c9d1db529fa788d09f97763b85/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/9e829389ef61b92c62de8748c80169aaf25ce1f0/2.26.1/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d7f4d4d86674a57668e3a96f76f0e17dd0eb8765/3.11.0/gcc-5-glibc-2.23/9bc6787
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/61e4abf5813bbc39bc4f548757ccfcadde175a48/5.2.3/gcc-5-glibc-2.23/65372bd
|
||||
# shellcheck disable=SC2148
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/8219ec1bcedf8ad9da05e121e193364de2cc4f61/5.x/centos6-native/c447969
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/64d8d58e3d84f8bde7a029763d4f5baf39d0d5b9/stable/centos6-native/6aaf4de
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/ba9be983c81de7299b59fe71950c664a84dcb5f8/5.x/gcc-5-glibc-2.23/339d858
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/f20197cf3d4bd50339c9777aaa0b2ccadad9e2cb/2.23/gcc-5-glibc-2.23/ca1d1c0
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/6427ce8c7496e4ab06c2da81543b94c0de8be3d0/1.1.3/gcc-5-glibc-2.23/9bc6787
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/8f1e8b867d26efef93eac2fabbdb2e1d512665d7/1.2.8/gcc-5-glibc-2.23/9bc6787
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/70471c0571559fe0af7db6d7e8860b93a7eadfe1/1.0.6/gcc-5-glibc-2.23/9bc6787
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/453c89d6f0e68cdf1c151c769197fabedad9cac8/r131/gcc-5-glibc-2.23/9bc6787
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/00a40fa5f8bd2cd0622f2e868552793aef37ccf4/1.3.0/gcc-5-glibc-2.23/03859b5
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/47eef08f9acb77de982fbda6047c26d330739538/2.2.0/gcc-5-glibc-2.23/9bc6787
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/4414ddc78df8008b35cc4adac23590ad29148584/master/gcc-5-glibc-2.23/d506c82
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/9d7ae2693d05d62f9a579cb21e6b717cf257a75d/2.0.11/gcc-5-glibc-2.23/9bc6787
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/2b2dd58e3a52ccf2c1d827def59e5f740de0ad15/1.2/gcc-5-glibc-2.23/b443de1
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/379addf7ab2468a2b4293b47456cfcd1c9cb318d/4.3/gcc-5-glibc-2.23/9bc6787
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/3f68f5fe65a85b7c2d3e66852268fbd1efdb3151/4.0.9-36_fbk5_2933_gd092e3f/gcc-5-glibc-2.23/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/b9fab0aec99d9c36408e810b2677e91c12807afd/2.28/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/423431d61786b20bcc3bde8972901130cb29e6b3/3.11.0/gcc-5-glibc-2.23/9bc6787
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/3b0bb3bd9a0f690a069c479fcc0f7424fc7456d2/5.2.3/gcc-5-glibc-2.23/65372bd
|
||||
@@ -1,3 +1,4 @@
|
||||
# shellcheck disable=SC2148
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/cf7d14c625ce30bae1a4661c2319c5a283e4dd22/4.8.1/centos6-native/cc6c9dc
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/llvm-fb/8598c375b0e94e1448182eb3df034704144a838d/stable/centos6-native/3f16ddd
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/d6e0a7da6faba45f5e5b1638f9edd7afc2f34e7d/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
|
||||
|
||||
@@ -83,6 +83,7 @@ CFLAGS+=" -DTBB"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
export PORTABLE=1
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
@@ -54,6 +54,7 @@ TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
export PORTABLE=1
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# shellcheck disable=SC1113
|
||||
#/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
@@ -28,12 +29,14 @@ function package() {
|
||||
if dpkg --get-selections | grep --quiet $1; then
|
||||
log "$1 is already installed. skipping."
|
||||
else
|
||||
# shellcheck disable=SC2068
|
||||
apt-get install $@ -y
|
||||
fi
|
||||
elif [[ $OS = "centos" ]]; then
|
||||
if rpm -qa | grep --quiet $1; then
|
||||
log "$1 is already installed. skipping."
|
||||
else
|
||||
# shellcheck disable=SC2068
|
||||
yum install $@ -y
|
||||
fi
|
||||
fi
|
||||
@@ -52,6 +55,7 @@ function gem_install() {
|
||||
if gem list | grep --quiet $1; then
|
||||
log "$1 is already installed. skipping."
|
||||
else
|
||||
# shellcheck disable=SC2068
|
||||
gem install $@
|
||||
fi
|
||||
}
|
||||
@@ -125,4 +129,5 @@ function main() {
|
||||
include $LIB_DIR
|
||||
}
|
||||
|
||||
# shellcheck disable=SC2068
|
||||
main $@
|
||||
@@ -343,7 +343,7 @@ LITE_BUILD_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB debug version',
|
||||
'shell':'$LITE make J=1 static_lib || $CONTRUN_NAME=lite_static_lib $TASK_CREATION_TOOL',
|
||||
'shell':'$LITE make J=1 all check || $CONTRUN_NAME=lite $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -353,23 +353,19 @@ LITE_BUILD_COMMANDS="[
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB lite tests
|
||||
#
|
||||
LITE_UNIT_TEST_COMMANDS="[
|
||||
# Report RocksDB lite binary size to scuba
|
||||
REPORT_LITE_BINARY_SIZE_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Lite Unit Test',
|
||||
'name':'Rocksdb Lite Binary Size',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB debug version',
|
||||
'shell':'$SHM $LITE make J=1 check || $CONTRUN_NAME=lite_check $TASK_CREATION_TOOL',
|
||||
'name':'Report RocksDB Lite binary size to scuba',
|
||||
'shell':'tools/report_lite_binary_size.sh',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
@@ -748,8 +744,8 @@ case $1 in
|
||||
lite)
|
||||
echo $LITE_BUILD_COMMANDS
|
||||
;;
|
||||
lite_test)
|
||||
echo $LITE_UNIT_TEST_COMMANDS
|
||||
report_lite_binary_size)
|
||||
echo $REPORT_LITE_BINARY_SIZE_COMMANDS
|
||||
;;
|
||||
stress_crash)
|
||||
echo $STRESS_CRASH_TEST_COMMANDS
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# This script enables you running RocksDB tests by running
|
||||
# All the tests concurrently and utilizing all the cores
|
||||
Param(
|
||||
[switch]$EnableJE = $false, # Look for and use _je executable, append _je to listed exclusions
|
||||
[switch]$EnableJE = $false, # Look for and use test executable, append _je to listed exclusions
|
||||
[switch]$RunAll = $false, # Will attempt discover all *_test[_je].exe binaries and run all
|
||||
# of them as Google suites. I.e. It will run test cases concurrently
|
||||
# except those mentioned as $Run, those will run as individual test cases
|
||||
# And any execlued with $ExcludeExes or $ExcludeCases
|
||||
# It will also not run any individual test cases
|
||||
# excluded but $ExcludeCasese
|
||||
[switch]$RunAllExe = $false, # Look for and use test exdcutables, append _je to exclusions automatically
|
||||
# It will attempt to run them in parallel w/o breaking them up on individual
|
||||
# test cases. Those listed with $ExcludeExes will be excluded
|
||||
[string]$SuiteRun = "", # Split test suites in test cases and run in parallel, not compatible with $RunAll
|
||||
[string]$Run = "", # Run specified executables in parallel but do not split to test cases
|
||||
[string]$ExcludeCases = "", # Exclude test cases, expects a comma separated list, no spaces
|
||||
@@ -39,13 +42,18 @@ $RunOnly.Add("compact_on_deletion_collector_test") | Out-Null
|
||||
$RunOnly.Add("merge_test") | Out-Null
|
||||
$RunOnly.Add("stringappend_test") | Out-Null # Apparently incorrectly written
|
||||
$RunOnly.Add("backupable_db_test") | Out-Null # Disabled
|
||||
|
||||
$RunOnly.Add("timer_queue_test") | Out-Null # Not a gtest
|
||||
|
||||
if($RunAll -and $SuiteRun -ne "") {
|
||||
Write-Error "$RunAll and $SuiteRun are not compatible"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if($RunAllExe -and $Run -ne "") {
|
||||
Write-Error "$RunAllExe and $Run are not compatible"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# If running under Appveyor assume that root
|
||||
[string]$Appveyor = $Env:APPVEYOR_BUILD_FOLDER
|
||||
if($Appveyor -ne "") {
|
||||
@@ -131,12 +139,8 @@ function ExtractTestCases([string]$GTestExe, $HashTable) {
|
||||
|
||||
# Leading whitespace is fine
|
||||
$l = $l -replace '^\s+',''
|
||||
# but no whitespace any other place
|
||||
if($l -match "\s+") {
|
||||
continue
|
||||
}
|
||||
# Trailing dot is a test group but no whitespace
|
||||
elseif ( $l -match "\.$" ) {
|
||||
if ($l -match "\.$" -and $l -notmatch "\s+") {
|
||||
$Group = $l
|
||||
} else {
|
||||
# Otherwise it is a test name, remove leading space
|
||||
@@ -223,13 +227,11 @@ $TestExes = [ordered]@{}
|
||||
if($Run -ne "") {
|
||||
|
||||
$test_list = $Run -split ' '
|
||||
|
||||
ForEach($t in $test_list) {
|
||||
|
||||
if($EnableJE) {
|
||||
$t += "_je"
|
||||
}
|
||||
|
||||
MakeAndAdd -token $t -HashTable $TestExes
|
||||
}
|
||||
|
||||
@@ -237,6 +239,38 @@ if($Run -ne "") {
|
||||
Write-Error "Failed to extract tests from $Run"
|
||||
exit 1
|
||||
}
|
||||
} elseif($RunAllExe) {
|
||||
# Discover all the test binaries
|
||||
if($EnableJE) {
|
||||
$pattern = "*_test_je.exe"
|
||||
} else {
|
||||
$pattern = "*_test.exe"
|
||||
}
|
||||
|
||||
$search_path = -join ($BinariesFolder, $pattern)
|
||||
Write-Host "Binaries Search Path: $search_path"
|
||||
|
||||
$DiscoveredExe = @()
|
||||
dir -Path $search_path | ForEach-Object {
|
||||
$DiscoveredExe += ($_.Name)
|
||||
}
|
||||
|
||||
# Remove exclusions
|
||||
ForEach($e in $DiscoveredExe) {
|
||||
$e = $e -replace '.exe$', ''
|
||||
$bare_name = $e -replace '_je$', ''
|
||||
|
||||
if($ExcludeExesSet.Contains($bare_name)) {
|
||||
Write-Warning "Test $e is excluded"
|
||||
continue
|
||||
}
|
||||
MakeAndAdd -token $e -HashTable $TestExes
|
||||
}
|
||||
|
||||
if($TestExes.Count -lt 1) {
|
||||
Write-Error "Failed to discover test executables"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# Ordered by exe @{ Exe = @{ TestCase = LogName }}
|
||||
@@ -245,9 +279,7 @@ $CasesToRun = [ordered]@{}
|
||||
if($SuiteRun -ne "") {
|
||||
$suite_list = $SuiteRun -split ' '
|
||||
ProcessSuites -ListOfSuites $suite_list -HashOfHashes $CasesToRun
|
||||
}
|
||||
|
||||
if($RunAll) {
|
||||
} elseif ($RunAll) {
|
||||
# Discover all the test binaries
|
||||
if($EnableJE) {
|
||||
$pattern = "*_test_je.exe"
|
||||
@@ -255,7 +287,6 @@ if($RunAll) {
|
||||
$pattern = "*_test.exe"
|
||||
}
|
||||
|
||||
|
||||
$search_path = -join ($BinariesFolder, $pattern)
|
||||
Write-Host "Binaries Search Path: $search_path"
|
||||
|
||||
@@ -287,8 +318,6 @@ if($RunAll) {
|
||||
}
|
||||
|
||||
|
||||
Write-Host "Attempting to start: $NumTestsToStart tests"
|
||||
|
||||
# Invoke a test with a filter and redirect all output
|
||||
$InvokeTestCase = {
|
||||
param($exe, $test, $log);
|
||||
@@ -307,7 +336,7 @@ $InvokeTestAsync = {
|
||||
# Test limiting factor here
|
||||
[int]$count = 0
|
||||
# Overall status
|
||||
[bool]$success = $true;
|
||||
[bool]$script:success = $true;
|
||||
|
||||
function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal)
|
||||
{
|
||||
@@ -365,6 +394,7 @@ function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal)
|
||||
break
|
||||
}
|
||||
|
||||
Write-Host "Starting $exe_name"
|
||||
[string]$Exe = -Join ($BinariesFolder, $exe_name)
|
||||
$job = Start-Job -Name $exe_name -ScriptBlock $InvokeTestAsync -ArgumentList @($Exe,$log_path)
|
||||
$JobToLog.Add($job, $log_path)
|
||||
@@ -395,7 +425,7 @@ function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal)
|
||||
$log_content = @(Get-Content $log)
|
||||
|
||||
if($completed.State -ne "Completed") {
|
||||
$success = $false
|
||||
$script:success = $false
|
||||
Write-Warning $message
|
||||
$log_content | Write-Warning
|
||||
} else {
|
||||
@@ -419,7 +449,7 @@ function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal)
|
||||
}
|
||||
|
||||
if(!$pass_found) {
|
||||
$success = $false;
|
||||
$script:success = $false;
|
||||
Write-Warning $message
|
||||
$log_content | Write-Warning
|
||||
} else {
|
||||
@@ -443,7 +473,7 @@ New-TimeSpan -Start $StartDate -End $EndDate |
|
||||
}
|
||||
|
||||
|
||||
if(!$success) {
|
||||
if(!$script:success) {
|
||||
# This does not succeed killing off jobs quick
|
||||
# So we simply exit
|
||||
# Remove-Job -Job $jobs -Force
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
ROCKSDB_VERSION="5.10.3"
|
||||
ZSTD_VERSION="1.1.3"
|
||||
|
||||
echo "This script configures CentOS with everything needed to build and run RocksDB"
|
||||
|
||||
yum update -y && yum install epel-release -y
|
||||
|
||||
yum install -y \
|
||||
wget \
|
||||
gcc-c++ \
|
||||
snappy snappy-devel \
|
||||
zlib zlib-devel \
|
||||
bzip2 bzip2-devel \
|
||||
lz4-devel \
|
||||
libasan \
|
||||
gflags
|
||||
|
||||
mkdir -pv /usr/local/rocksdb-${ROCKSDB_VERSION}
|
||||
ln -sfT /usr/local/rocksdb-${ROCKSDB_VERSION} /usr/local/rocksdb
|
||||
|
||||
wget -qO /tmp/zstd-${ZSTD_VERSION}.tar.gz https://github.com/facebook/zstd/archive/v${ZSTD_VERSION}.tar.gz
|
||||
wget -qO /tmp/rocksdb-${ROCKSDB_VERSION}.tar.gz https://github.com/facebook/rocksdb/archive/v${ROCKSDB_VERSION}.tar.gz
|
||||
|
||||
cd /tmp
|
||||
|
||||
tar xzvf zstd-${ZSTD_VERSION}.tar.gz
|
||||
tar xzvf rocksdb-${ROCKSDB_VERSION}.tar.gz -C /usr/local/
|
||||
|
||||
echo "Installing ZSTD..."
|
||||
pushd zstd-${ZSTD_VERSION}
|
||||
make && make install
|
||||
popd
|
||||
|
||||
echo "Compiling RocksDB..."
|
||||
cd /usr/local/rocksdb
|
||||
chown -R vagrant:vagrant /usr/local/rocksdb/
|
||||
sudo -u vagrant make static_lib
|
||||
cd examples/
|
||||
sudo -u vagrant make all
|
||||
sudo -u vagrant ./c_simple_example
|
||||
@@ -38,6 +38,7 @@ function get_lib_base()
|
||||
# platform is not provided, use latest gcc
|
||||
result=`ls -dr1v $result/gcc-*[^fb]/ | head -n1`
|
||||
else
|
||||
echo $lib_platform
|
||||
result="$result/$lib_platform/"
|
||||
fi
|
||||
|
||||
@@ -71,22 +72,22 @@ log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 5.x
|
||||
get_lib_base glibc 2.23
|
||||
get_lib_base snappy LATEST gcc-5-glibc-2.23
|
||||
get_lib_base zlib LATEST
|
||||
get_lib_base bzip2 LATEST
|
||||
get_lib_base lz4 LATEST
|
||||
get_lib_base zstd LATEST
|
||||
get_lib_base gflags LATEST
|
||||
get_lib_base jemalloc LATEST
|
||||
get_lib_base numa LATEST
|
||||
get_lib_base libunwind LATEST
|
||||
get_lib_base tbb 4.0_update2 gcc-5-glibc-2.23
|
||||
get_lib_base libgcc 5.x gcc-5-glibc-2.23
|
||||
get_lib_base glibc 2.23 gcc-5-glibc-2.23
|
||||
get_lib_base snappy LATEST gcc-5-glibc-2.23
|
||||
get_lib_base zlib LATEST gcc-5-glibc-2.23
|
||||
get_lib_base bzip2 LATEST gcc-5-glibc-2.23
|
||||
get_lib_base lz4 LATEST gcc-5-glibc-2.23
|
||||
get_lib_base zstd LATEST gcc-5-glibc-2.23
|
||||
get_lib_base gflags LATEST gcc-5-glibc-2.23
|
||||
get_lib_base jemalloc LATEST gcc-5-glibc-2.23
|
||||
get_lib_base numa LATEST gcc-5-glibc-2.23
|
||||
get_lib_base libunwind LATEST gcc-5-glibc-2.23
|
||||
get_lib_base tbb LATEST gcc-5-glibc-2.23
|
||||
|
||||
get_lib_base kernel-headers LATEST
|
||||
get_lib_base kernel-headers 4.0.9-36_fbk5_2933_gd092e3f gcc-5-glibc-2.23
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base valgrind 3.10.0 gcc-5-glibc-2.23
|
||||
get_lib_base valgrind LATEST gcc-5-glibc-2.23
|
||||
get_lib_base lua 5.2.3 gcc-5-glibc-2.23
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
#/usr/bin/env bash
|
||||
if [ "$#" = "0" ]; then
|
||||
echo "Usage: $0 major|minor|patch|full"
|
||||
exit 1
|
||||
|
||||
Vendored
+5
-5
@@ -17,16 +17,16 @@ int main() {
|
||||
#include <inttypes.h>
|
||||
#include <sys/types.h>
|
||||
#include <stdio.h>
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
|
||||
using GFLAGS::ParseCommandLineFlags;
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
|
||||
static const uint32_t KB = 1024;
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -40,9 +40,9 @@ static int DecodeValue(void* v) {
|
||||
const std::string kLRU = "lru";
|
||||
const std::string kClock = "clock";
|
||||
|
||||
void dumbDeleter(const Slice& key, void* value) {}
|
||||
void dumbDeleter(const Slice& /*key*/, void* /*value*/) {}
|
||||
|
||||
void eraseDeleter(const Slice& key, void* value) {
|
||||
void eraseDeleter(const Slice& /*key*/, void* value) {
|
||||
Cache* cache = reinterpret_cast<Cache*>(value);
|
||||
cache->Erase("foo");
|
||||
}
|
||||
@@ -470,7 +470,7 @@ class Value {
|
||||
};
|
||||
|
||||
namespace {
|
||||
void deleter(const Slice& key, void* value) {
|
||||
void deleter(const Slice& /*key*/, void* value) {
|
||||
delete static_cast<Value *>(value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Vendored
+1
-1
@@ -586,7 +586,7 @@ Status ClockCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** out_handle,
|
||||
Cache::Priority priority) {
|
||||
Cache::Priority /*priority*/) {
|
||||
CleanupContext context;
|
||||
HashTable::accessor accessor;
|
||||
char* key_data = new char[key.size()];
|
||||
|
||||
Vendored
+23
-8
@@ -100,7 +100,9 @@ void LRUHandleTable::Resize() {
|
||||
}
|
||||
|
||||
LRUCacheShard::LRUCacheShard()
|
||||
: high_pri_pool_usage_(0), usage_(0), lru_usage_(0) {
|
||||
: capacity_(0), high_pri_pool_usage_(0), strict_capacity_limit_(false),
|
||||
high_pri_pool_ratio_(0), high_pri_pool_capacity_(0), usage_(0),
|
||||
lru_usage_(0) {
|
||||
// Make empty circular linked list
|
||||
lru_.next = &lru_;
|
||||
lru_.prev = &lru_;
|
||||
@@ -167,6 +169,11 @@ size_t LRUCacheShard::TEST_GetLRUSize() {
|
||||
return lru_size;
|
||||
}
|
||||
|
||||
double LRUCacheShard::GetHighPriPoolRatio() {
|
||||
MutexLock l(&mutex_);
|
||||
return high_pri_pool_ratio_;
|
||||
}
|
||||
|
||||
void LRUCacheShard::LRU_Remove(LRUHandle* e) {
|
||||
assert(e->next != nullptr);
|
||||
assert(e->prev != nullptr);
|
||||
@@ -353,6 +360,7 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->deleter = deleter;
|
||||
e->charge = charge;
|
||||
e->key_length = key.size();
|
||||
e->flags = 0;
|
||||
e->hash = hash;
|
||||
e->refs = (handle == nullptr
|
||||
? 1
|
||||
@@ -465,14 +473,7 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit, double high_pri_pool_ratio)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit) {
|
||||
num_shards_ = 1 << num_shard_bits;
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4316) // We've validated the alignment with the new operators
|
||||
#endif
|
||||
shards_ = new LRUCacheShard[num_shards_];
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
SetCapacity(capacity);
|
||||
SetStrictCapacityLimit(strict_capacity_limit);
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
@@ -517,6 +518,20 @@ size_t LRUCache::TEST_GetLRUSize() {
|
||||
return lru_size_of_all_shards;
|
||||
}
|
||||
|
||||
double LRUCache::GetHighPriPoolRatio() {
|
||||
double result = 0.0;
|
||||
if (num_shards_ > 0) {
|
||||
result = shards_[0].GetHighPriPoolRatio();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit,
|
||||
cache_opts.high_pri_pool_ratio);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio) {
|
||||
|
||||
Vendored
+6
-1
@@ -57,7 +57,7 @@ struct LRUHandle {
|
||||
// Include the following flags:
|
||||
// in_cache: whether this entry is referenced by the hash table.
|
||||
// is_high_pri: whether this entry is high priority entry.
|
||||
// in_high_pro_pool: whether this entry is in high-pri pool.
|
||||
// in_high_pri_pool: whether this entry is in high-pri pool.
|
||||
char flags;
|
||||
|
||||
uint32_t hash; // Hash of key(); used for fast sharding and comparisons
|
||||
@@ -202,6 +202,9 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard : public CacheShard {
|
||||
// not threadsafe
|
||||
size_t TEST_GetLRUSize();
|
||||
|
||||
// Retrives high pri pool ratio
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
// Overloading to aligned it to cache line size
|
||||
void* operator new(size_t);
|
||||
|
||||
@@ -293,6 +296,8 @@ class LRUCache : public ShardedCache {
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only
|
||||
size_t TEST_GetLRUSize();
|
||||
// Retrives high pri pool ratio
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
LRUCacheShard* shards_;
|
||||
|
||||
Vendored
+1
-1
@@ -53,7 +53,7 @@ Status ShardedCache::Insert(const Slice& key, void* value, size_t charge,
|
||||
->Insert(key, hash, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
Cache::Handle* ShardedCache::Lookup(const Slice& key, Statistics* stats) {
|
||||
Cache::Handle* ShardedCache::Lookup(const Slice& key, Statistics* /*stats*/) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))->Lookup(key, hash);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# - Find NUMA
|
||||
# Find the NUMA library and includes
|
||||
#
|
||||
# NUMA_INCLUDE_DIR - where to find numa.h, etc.
|
||||
# NUMA_LIBRARIES - List of libraries when using NUMA.
|
||||
# NUMA_FOUND - True if NUMA found.
|
||||
|
||||
find_path(NUMA_INCLUDE_DIR
|
||||
NAMES numa.h numaif.h
|
||||
HINTS ${NUMA_ROOT_DIR}/include)
|
||||
|
||||
find_library(NUMA_LIBRARIES
|
||||
NAMES numa
|
||||
HINTS ${NUMA_ROOT_DIR}/lib)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(NUMA DEFAULT_MSG NUMA_LIBRARIES NUMA_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(
|
||||
NUMA_LIBRARIES
|
||||
NUMA_INCLUDE_DIR)
|
||||
@@ -0,0 +1,21 @@
|
||||
# - Find TBB
|
||||
# Find the Thread Building Blocks library and includes
|
||||
#
|
||||
# TBB_INCLUDE_DIR - where to find tbb.h, etc.
|
||||
# TBB_LIBRARIES - List of libraries when using TBB.
|
||||
# TBB_FOUND - True if TBB found.
|
||||
|
||||
find_path(TBB_INCLUDE_DIR
|
||||
NAMES tbb/tbb.h
|
||||
HINTS ${TBB_ROOT_DIR}/include)
|
||||
|
||||
find_library(TBB_LIBRARIES
|
||||
NAMES tbb
|
||||
HINTS ${TBB_ROOT_DIR}/lib)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(TBB DEFAULT_MSG TBB_LIBRARIES TBB_INCLUDE_DIR)
|
||||
|
||||
mark_as_advanced(
|
||||
TBB_LIBRARIES
|
||||
TBB_INCLUDE_DIR)
|
||||
@@ -72,7 +72,7 @@ def display_file_coverage(per_file_coverage, total_coverage):
|
||||
header_template = \
|
||||
"%" + str(max_file_name_length) + "s\t%s\t%s"
|
||||
separator = "-" * (max_file_name_length + 10 + 20)
|
||||
print header_template % ("Filename", "Coverage", "Lines")
|
||||
print header_template % ("Filename", "Coverage", "Lines") # noqa: E999 T25377293 Grandfathered in
|
||||
print separator
|
||||
|
||||
# -- Print body
|
||||
|
||||
+17
-16
@@ -47,35 +47,35 @@ TableBuilder* NewTableBuilder(
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const std::string* compression_dict, const bool skip_filters,
|
||||
const uint64_t creation_time) {
|
||||
const uint64_t creation_time, const uint64_t oldest_key_time) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
return ioptions.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(ioptions, internal_comparator,
|
||||
int_tbl_prop_collector_factories, compression_type,
|
||||
compression_opts, compression_dict, skip_filters,
|
||||
column_family_name, level, creation_time),
|
||||
TableBuilderOptions(
|
||||
ioptions, internal_comparator, int_tbl_prop_collector_factories,
|
||||
compression_type, compression_opts, compression_dict, skip_filters,
|
||||
column_family_name, level, creation_time, oldest_key_time),
|
||||
column_family_id, file);
|
||||
}
|
||||
|
||||
Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
std::unique_ptr<InternalIterator> range_del_iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
const EnvOptions& env_options, TableCache* table_cache,
|
||||
InternalIterator* iter, std::unique_ptr<InternalIterator> range_del_iter,
|
||||
FileMetaData* meta, const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const CompressionType compression,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level,
|
||||
const uint64_t creation_time) {
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
@@ -117,25 +117,26 @@ Status BuildTable(
|
||||
return s;
|
||||
}
|
||||
file->SetIOPriority(io_priority);
|
||||
file->SetWriteLifeTimeHint(write_hint);
|
||||
|
||||
file_writer.reset(new WritableFileWriter(std::move(file), env_options,
|
||||
ioptions.statistics));
|
||||
|
||||
builder = NewTableBuilder(
|
||||
ioptions, internal_comparator, int_tbl_prop_collector_factories,
|
||||
column_family_id, column_family_name, file_writer.get(), compression,
|
||||
compression_opts, level, nullptr /* compression_dict */,
|
||||
false /* skip_filters */, creation_time);
|
||||
false /* skip_filters */, creation_time, oldest_key_time);
|
||||
}
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator, nullptr, ioptions.info_log,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back());
|
||||
snapshots.empty() ? 0 : snapshots.back(),
|
||||
snapshot_checker);
|
||||
|
||||
CompactionIterator c_iter(
|
||||
iter, internal_comparator.user_comparator(), &merge, kMaxSequenceNumber,
|
||||
&snapshots, earliest_write_conflict_snapshot, env,
|
||||
&snapshots, earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
true /* internal key corruption is not ok */, range_del_agg.get());
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
|
||||
+6
-3
@@ -29,6 +29,7 @@ struct FileMetaData;
|
||||
class Env;
|
||||
struct EnvOptions;
|
||||
class Iterator;
|
||||
class SnapshotChecker;
|
||||
class TableCache;
|
||||
class VersionEdit;
|
||||
class TableBuilder;
|
||||
@@ -50,7 +51,8 @@ TableBuilder* NewTableBuilder(
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const std::string* compression_dict = nullptr,
|
||||
const bool skip_filters = false, const uint64_t creation_time = 0);
|
||||
const bool skip_filters = false, const uint64_t creation_time = 0,
|
||||
const uint64_t oldest_key_time = 0);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
// will be named according to number specified in meta. On success, the rest of
|
||||
@@ -71,12 +73,13 @@ extern Status BuildTable(
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const CompressionType compression,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr, int level = -1,
|
||||
const uint64_t creation_time = 0);
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -21,23 +21,25 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/universal_compaction.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/universal_compaction.h"
|
||||
#include "rocksdb/utilities/backupable_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/db_ttl.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "rocksdb/utilities/transaction.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
using rocksdb::BytewiseComparator;
|
||||
using rocksdb::Cache;
|
||||
@@ -100,6 +102,8 @@ using rocksdb::OptimisticTransactionDB;
|
||||
using rocksdb::OptimisticTransactionOptions;
|
||||
using rocksdb::Transaction;
|
||||
using rocksdb::Checkpoint;
|
||||
using rocksdb::PerfLevel;
|
||||
using rocksdb::PerfContext;
|
||||
|
||||
using std::shared_ptr;
|
||||
|
||||
@@ -117,7 +121,9 @@ struct rocksdb_flushoptions_t { FlushOptions rep; };
|
||||
struct rocksdb_fifo_compaction_options_t { CompactionOptionsFIFO rep; };
|
||||
struct rocksdb_readoptions_t {
|
||||
ReadOptions rep;
|
||||
Slice upper_bound; // stack variable to set pointer to in ReadOptions
|
||||
// stack variables to set pointers to in ReadOptions
|
||||
Slice upper_bound;
|
||||
Slice lower_bound;
|
||||
};
|
||||
struct rocksdb_writeoptions_t { WriteOptions rep; };
|
||||
struct rocksdb_options_t { Options rep; };
|
||||
@@ -138,6 +144,7 @@ struct rocksdb_envoptions_t { EnvOptions rep; };
|
||||
struct rocksdb_ingestexternalfileoptions_t { IngestExternalFileOptions rep; };
|
||||
struct rocksdb_sstfilewriter_t { SstFileWriter* rep; };
|
||||
struct rocksdb_ratelimiter_t { RateLimiter* rep; };
|
||||
struct rocksdb_perfcontext_t { PerfContext* rep; };
|
||||
struct rocksdb_pinnableslice_t {
|
||||
PinnableSlice rep;
|
||||
};
|
||||
@@ -250,7 +257,7 @@ struct rocksdb_comparator_t : public Comparator {
|
||||
// No-ops since the C binding does not support key shortening methods.
|
||||
virtual void FindShortestSeparator(std::string*,
|
||||
const Slice&) const override {}
|
||||
virtual void FindShortSuccessor(std::string* key) const override {}
|
||||
virtual void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
struct rocksdb_filterpolicy_t : public FilterPolicy {
|
||||
@@ -365,7 +372,7 @@ struct rocksdb_mergeoperator_t : public MergeOperator {
|
||||
virtual bool PartialMergeMulti(const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
Logger* /*logger*/) const override {
|
||||
size_t operand_count = operand_list.size();
|
||||
std::vector<const char*> operand_pointers(operand_count);
|
||||
std::vector<size_t> operand_sizes(operand_count);
|
||||
@@ -475,6 +482,20 @@ rocksdb_t* rocksdb_open(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_t* rocksdb_open_with_ttl(
|
||||
const rocksdb_options_t* options,
|
||||
const char* name,
|
||||
int ttl,
|
||||
char** errptr) {
|
||||
rocksdb::DBWithTTL* db;
|
||||
if (SaveError(errptr, rocksdb::DBWithTTL::Open(options->rep, std::string(name), &db, ttl))) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_t* result = new rocksdb_t;
|
||||
result->rep = db;
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_t* rocksdb_open_for_read_only(
|
||||
const rocksdb_options_t* options,
|
||||
const char* name,
|
||||
@@ -1824,6 +1845,26 @@ void rocksdb_block_based_options_set_block_restart_interval(
|
||||
options->rep.block_restart_interval = block_restart_interval;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_index_block_restart_interval(
|
||||
rocksdb_block_based_table_options_t* options, int index_block_restart_interval) {
|
||||
options->rep.index_block_restart_interval = index_block_restart_interval;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_metadata_block_size(
|
||||
rocksdb_block_based_table_options_t* options, uint64_t metadata_block_size) {
|
||||
options->rep.metadata_block_size = metadata_block_size;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_partition_filters(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char partition_filters) {
|
||||
options->rep.partition_filters = partition_filters;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_use_delta_encoding(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char use_delta_encoding) {
|
||||
options->rep.use_delta_encoding = use_delta_encoding;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_filter_policy(
|
||||
rocksdb_block_based_table_options_t* options,
|
||||
rocksdb_filterpolicy_t* filter_policy) {
|
||||
@@ -1877,6 +1918,11 @@ void rocksdb_block_based_options_set_cache_index_and_filter_blocks(
|
||||
options->rep.cache_index_and_filter_blocks = v;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_cache_index_and_filter_blocks_with_high_priority(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char v) {
|
||||
options->rep.cache_index_and_filter_blocks_with_high_priority = v;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_pin_l0_filter_and_index_blocks_in_cache(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char v) {
|
||||
options->rep.pin_l0_filter_and_index_blocks_in_cache = v;
|
||||
@@ -1891,7 +1937,6 @@ void rocksdb_options_set_block_based_table_factory(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rocksdb_cuckoo_table_options_t*
|
||||
rocksdb_cuckoo_options_create() {
|
||||
return new rocksdb_cuckoo_table_options_t;
|
||||
@@ -1973,6 +2018,11 @@ void rocksdb_options_optimize_universal_style_compaction(
|
||||
opt->rep.OptimizeUniversalStyleCompaction(memtable_memory_budget);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_allow_ingest_behind(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.allow_ingest_behind = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compaction_filter(
|
||||
rocksdb_options_t* opt,
|
||||
rocksdb_compactionfilter_t* filter) {
|
||||
@@ -2023,8 +2073,8 @@ void rocksdb_options_set_paranoid_checks(
|
||||
opt->rep.paranoid_checks = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_db_paths(rocksdb_options_t* opt,
|
||||
const rocksdb_dbpath_t** dbpath_values,
|
||||
void rocksdb_options_set_db_paths(rocksdb_options_t* opt,
|
||||
const rocksdb_dbpath_t** dbpath_values,
|
||||
size_t num_paths) {
|
||||
std::vector<DbPath> db_paths(num_paths);
|
||||
for (size_t i = 0; i < num_paths; ++i) {
|
||||
@@ -2111,7 +2161,8 @@ void rocksdb_options_enable_statistics(rocksdb_options_t* opt) {
|
||||
opt->rep.statistics = rocksdb::CreateDBStatistics();
|
||||
}
|
||||
|
||||
void rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt, unsigned char val) {
|
||||
void rocksdb_options_set_skip_stats_update_on_db_open(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.skip_stats_update_on_db_open = val;
|
||||
}
|
||||
|
||||
@@ -2134,8 +2185,8 @@ void rocksdb_options_set_level0_stop_writes_trigger(
|
||||
opt->rep.level0_stop_writes_trigger = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_mem_compaction_level(rocksdb_options_t* opt,
|
||||
int n) {}
|
||||
void rocksdb_options_set_max_mem_compaction_level(rocksdb_options_t* /*opt*/,
|
||||
int /*n*/) {}
|
||||
|
||||
void rocksdb_options_set_wal_recovery_mode(rocksdb_options_t* opt,int mode) {
|
||||
opt->rep.wal_recovery_mode = static_cast<WALRecoveryMode>(mode);
|
||||
@@ -2199,8 +2250,8 @@ void rocksdb_options_set_manifest_preallocation_size(
|
||||
}
|
||||
|
||||
// noop
|
||||
void rocksdb_options_set_purge_redundant_kvs_while_flush(rocksdb_options_t* opt,
|
||||
unsigned char v) {}
|
||||
void rocksdb_options_set_purge_redundant_kvs_while_flush(
|
||||
rocksdb_options_t* /*opt*/, unsigned char /*v*/) {}
|
||||
|
||||
void rocksdb_options_set_use_direct_reads(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
@@ -2265,11 +2316,21 @@ void rocksdb_options_set_use_adaptive_mutex(
|
||||
opt->rep.use_adaptive_mutex = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_wal_bytes_per_sync(
|
||||
rocksdb_options_t* opt, uint64_t v) {
|
||||
opt->rep.wal_bytes_per_sync = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_bytes_per_sync(
|
||||
rocksdb_options_t* opt, uint64_t v) {
|
||||
opt->rep.bytes_per_sync = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_writable_file_max_buffer_size(rocksdb_options_t* opt,
|
||||
uint64_t v) {
|
||||
opt->rep.writable_file_max_buffer_size = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_allow_concurrent_memtable_write(rocksdb_options_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.allow_concurrent_memtable_write = v;
|
||||
@@ -2360,7 +2421,7 @@ void rocksdb_options_set_table_cache_numshardbits(
|
||||
}
|
||||
|
||||
void rocksdb_options_set_table_cache_remove_scan_count_limit(
|
||||
rocksdb_options_t* opt, int v) {
|
||||
rocksdb_options_t* /*opt*/, int /*v*/) {
|
||||
// this option is deprecated
|
||||
}
|
||||
|
||||
@@ -2495,6 +2556,176 @@ void rocksdb_ratelimiter_destroy(rocksdb_ratelimiter_t *limiter) {
|
||||
delete limiter;
|
||||
}
|
||||
|
||||
void rocksdb_set_perf_level(int v) {
|
||||
PerfLevel level = static_cast<PerfLevel>(v);
|
||||
SetPerfLevel(level);
|
||||
}
|
||||
|
||||
rocksdb_perfcontext_t* rocksdb_perfcontext_create() {
|
||||
rocksdb_perfcontext_t* context = new rocksdb_perfcontext_t;
|
||||
context->rep = rocksdb::get_perf_context();
|
||||
return context;
|
||||
}
|
||||
|
||||
void rocksdb_perfcontext_reset(rocksdb_perfcontext_t* context) {
|
||||
context->rep->Reset();
|
||||
}
|
||||
|
||||
char* rocksdb_perfcontext_report(rocksdb_perfcontext_t* context,
|
||||
unsigned char exclude_zero_counters) {
|
||||
return strdup(context->rep->ToString(exclude_zero_counters).c_str());
|
||||
}
|
||||
|
||||
uint64_t rocksdb_perfcontext_metric(rocksdb_perfcontext_t* context,
|
||||
int metric) {
|
||||
PerfContext* rep = context->rep;
|
||||
switch (metric) {
|
||||
case rocksdb_user_key_comparison_count:
|
||||
return rep->user_key_comparison_count;
|
||||
case rocksdb_block_cache_hit_count:
|
||||
return rep->block_cache_hit_count;
|
||||
case rocksdb_block_read_count:
|
||||
return rep->block_read_count;
|
||||
case rocksdb_block_read_byte:
|
||||
return rep->block_read_byte;
|
||||
case rocksdb_block_read_time:
|
||||
return rep->block_read_time;
|
||||
case rocksdb_block_checksum_time:
|
||||
return rep->block_checksum_time;
|
||||
case rocksdb_block_decompress_time:
|
||||
return rep->block_decompress_time;
|
||||
case rocksdb_get_read_bytes:
|
||||
return rep->get_read_bytes;
|
||||
case rocksdb_multiget_read_bytes:
|
||||
return rep->multiget_read_bytes;
|
||||
case rocksdb_iter_read_bytes:
|
||||
return rep->iter_read_bytes;
|
||||
case rocksdb_internal_key_skipped_count:
|
||||
return rep->internal_key_skipped_count;
|
||||
case rocksdb_internal_delete_skipped_count:
|
||||
return rep->internal_delete_skipped_count;
|
||||
case rocksdb_internal_recent_skipped_count:
|
||||
return rep->internal_recent_skipped_count;
|
||||
case rocksdb_internal_merge_count:
|
||||
return rep->internal_merge_count;
|
||||
case rocksdb_get_snapshot_time:
|
||||
return rep->get_snapshot_time;
|
||||
case rocksdb_get_from_memtable_time:
|
||||
return rep->get_from_memtable_time;
|
||||
case rocksdb_get_from_memtable_count:
|
||||
return rep->get_from_memtable_count;
|
||||
case rocksdb_get_post_process_time:
|
||||
return rep->get_post_process_time;
|
||||
case rocksdb_get_from_output_files_time:
|
||||
return rep->get_from_output_files_time;
|
||||
case rocksdb_seek_on_memtable_time:
|
||||
return rep->seek_on_memtable_time;
|
||||
case rocksdb_seek_on_memtable_count:
|
||||
return rep->seek_on_memtable_count;
|
||||
case rocksdb_next_on_memtable_count:
|
||||
return rep->next_on_memtable_count;
|
||||
case rocksdb_prev_on_memtable_count:
|
||||
return rep->prev_on_memtable_count;
|
||||
case rocksdb_seek_child_seek_time:
|
||||
return rep->seek_child_seek_time;
|
||||
case rocksdb_seek_child_seek_count:
|
||||
return rep->seek_child_seek_count;
|
||||
case rocksdb_seek_min_heap_time:
|
||||
return rep->seek_min_heap_time;
|
||||
case rocksdb_seek_max_heap_time:
|
||||
return rep->seek_max_heap_time;
|
||||
case rocksdb_seek_internal_seek_time:
|
||||
return rep->seek_internal_seek_time;
|
||||
case rocksdb_find_next_user_entry_time:
|
||||
return rep->find_next_user_entry_time;
|
||||
case rocksdb_write_wal_time:
|
||||
return rep->write_wal_time;
|
||||
case rocksdb_write_memtable_time:
|
||||
return rep->write_memtable_time;
|
||||
case rocksdb_write_delay_time:
|
||||
return rep->write_delay_time;
|
||||
case rocksdb_write_pre_and_post_process_time:
|
||||
return rep->write_pre_and_post_process_time;
|
||||
case rocksdb_db_mutex_lock_nanos:
|
||||
return rep->db_mutex_lock_nanos;
|
||||
case rocksdb_db_condition_wait_nanos:
|
||||
return rep->db_condition_wait_nanos;
|
||||
case rocksdb_merge_operator_time_nanos:
|
||||
return rep->merge_operator_time_nanos;
|
||||
case rocksdb_read_index_block_nanos:
|
||||
return rep->read_index_block_nanos;
|
||||
case rocksdb_read_filter_block_nanos:
|
||||
return rep->read_filter_block_nanos;
|
||||
case rocksdb_new_table_block_iter_nanos:
|
||||
return rep->new_table_block_iter_nanos;
|
||||
case rocksdb_new_table_iterator_nanos:
|
||||
return rep->new_table_iterator_nanos;
|
||||
case rocksdb_block_seek_nanos:
|
||||
return rep->block_seek_nanos;
|
||||
case rocksdb_find_table_nanos:
|
||||
return rep->find_table_nanos;
|
||||
case rocksdb_bloom_memtable_hit_count:
|
||||
return rep->bloom_memtable_hit_count;
|
||||
case rocksdb_bloom_memtable_miss_count:
|
||||
return rep->bloom_memtable_miss_count;
|
||||
case rocksdb_bloom_sst_hit_count:
|
||||
return rep->bloom_sst_hit_count;
|
||||
case rocksdb_bloom_sst_miss_count:
|
||||
return rep->bloom_sst_miss_count;
|
||||
case rocksdb_key_lock_wait_time:
|
||||
return rep->key_lock_wait_time;
|
||||
case rocksdb_key_lock_wait_count:
|
||||
return rep->key_lock_wait_count;
|
||||
case rocksdb_env_new_sequential_file_nanos:
|
||||
return rep->env_new_sequential_file_nanos;
|
||||
case rocksdb_env_new_random_access_file_nanos:
|
||||
return rep->env_new_random_access_file_nanos;
|
||||
case rocksdb_env_new_writable_file_nanos:
|
||||
return rep->env_new_writable_file_nanos;
|
||||
case rocksdb_env_reuse_writable_file_nanos:
|
||||
return rep->env_reuse_writable_file_nanos;
|
||||
case rocksdb_env_new_random_rw_file_nanos:
|
||||
return rep->env_new_random_rw_file_nanos;
|
||||
case rocksdb_env_new_directory_nanos:
|
||||
return rep->env_new_directory_nanos;
|
||||
case rocksdb_env_file_exists_nanos:
|
||||
return rep->env_file_exists_nanos;
|
||||
case rocksdb_env_get_children_nanos:
|
||||
return rep->env_get_children_nanos;
|
||||
case rocksdb_env_get_children_file_attributes_nanos:
|
||||
return rep->env_get_children_file_attributes_nanos;
|
||||
case rocksdb_env_delete_file_nanos:
|
||||
return rep->env_delete_file_nanos;
|
||||
case rocksdb_env_create_dir_nanos:
|
||||
return rep->env_create_dir_nanos;
|
||||
case rocksdb_env_create_dir_if_missing_nanos:
|
||||
return rep->env_create_dir_if_missing_nanos;
|
||||
case rocksdb_env_delete_dir_nanos:
|
||||
return rep->env_delete_dir_nanos;
|
||||
case rocksdb_env_get_file_size_nanos:
|
||||
return rep->env_get_file_size_nanos;
|
||||
case rocksdb_env_get_file_modification_time_nanos:
|
||||
return rep->env_get_file_modification_time_nanos;
|
||||
case rocksdb_env_rename_file_nanos:
|
||||
return rep->env_rename_file_nanos;
|
||||
case rocksdb_env_link_file_nanos:
|
||||
return rep->env_link_file_nanos;
|
||||
case rocksdb_env_lock_file_nanos:
|
||||
return rep->env_lock_file_nanos;
|
||||
case rocksdb_env_unlock_file_nanos:
|
||||
return rep->env_unlock_file_nanos;
|
||||
case rocksdb_env_new_logger_nanos:
|
||||
return rep->env_new_logger_nanos;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void rocksdb_perfcontext_destroy(rocksdb_perfcontext_t* context) {
|
||||
delete context;
|
||||
}
|
||||
|
||||
/*
|
||||
TODO:
|
||||
DB::OpenForReadOnly
|
||||
@@ -2717,6 +2948,18 @@ void rocksdb_readoptions_set_iterate_upper_bound(
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_iterate_lower_bound(
|
||||
rocksdb_readoptions_t *opt,
|
||||
const char* key, size_t keylen) {
|
||||
if (key == nullptr) {
|
||||
opt->lower_bound = Slice();
|
||||
opt->rep.iterate_lower_bound = nullptr;
|
||||
} else {
|
||||
opt->lower_bound = Slice(key, keylen);
|
||||
opt->rep.iterate_lower_bound = &opt->lower_bound;
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_read_tier(
|
||||
rocksdb_readoptions_t* opt, int v) {
|
||||
opt->rep.read_tier = static_cast<rocksdb::ReadTier>(v);
|
||||
@@ -2727,11 +2970,21 @@ void rocksdb_readoptions_set_tailing(
|
||||
opt->rep.tailing = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_managed(
|
||||
rocksdb_readoptions_t* opt, unsigned char v) {
|
||||
opt->rep.managed = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_readahead_size(
|
||||
rocksdb_readoptions_t* opt, size_t v) {
|
||||
opt->rep.readahead_size = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_prefix_same_as_start(
|
||||
rocksdb_readoptions_t* opt, unsigned char v) {
|
||||
opt->rep.prefix_same_as_start = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_pin_data(rocksdb_readoptions_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.pin_data = v;
|
||||
@@ -2742,6 +2995,22 @@ void rocksdb_readoptions_set_total_order_seek(rocksdb_readoptions_t* opt,
|
||||
opt->rep.total_order_seek = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_max_skippable_internal_keys(
|
||||
rocksdb_readoptions_t* opt,
|
||||
uint64_t v) {
|
||||
opt->rep.max_skippable_internal_keys = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_background_purge_on_iterator_cleanup(
|
||||
rocksdb_readoptions_t* opt, unsigned char v) {
|
||||
opt->rep.background_purge_on_iterator_cleanup = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_ignore_range_deletions(
|
||||
rocksdb_readoptions_t* opt, unsigned char v) {
|
||||
opt->rep.ignore_range_deletions = v;
|
||||
}
|
||||
|
||||
rocksdb_writeoptions_t* rocksdb_writeoptions_create() {
|
||||
return new rocksdb_writeoptions_t;
|
||||
}
|
||||
@@ -2759,6 +3028,24 @@ void rocksdb_writeoptions_disable_WAL(rocksdb_writeoptions_t* opt, int disable)
|
||||
opt->rep.disableWAL = disable;
|
||||
}
|
||||
|
||||
void rocksdb_writeoptions_set_ignore_missing_column_families(
|
||||
rocksdb_writeoptions_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.ignore_missing_column_families = v;
|
||||
}
|
||||
|
||||
void rocksdb_writeoptions_set_no_slowdown(
|
||||
rocksdb_writeoptions_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.no_slowdown = v;
|
||||
}
|
||||
|
||||
void rocksdb_writeoptions_set_low_pri(
|
||||
rocksdb_writeoptions_t* opt,
|
||||
unsigned char v) {
|
||||
opt->rep.low_pri = v;
|
||||
}
|
||||
|
||||
rocksdb_compactoptions_t* rocksdb_compactoptions_create() {
|
||||
return new rocksdb_compactoptions_t;
|
||||
}
|
||||
@@ -2875,7 +3162,7 @@ rocksdb_sstfilewriter_t* rocksdb_sstfilewriter_create(
|
||||
|
||||
rocksdb_sstfilewriter_t* rocksdb_sstfilewriter_create_with_comparator(
|
||||
const rocksdb_envoptions_t* env, const rocksdb_options_t* io_options,
|
||||
const rocksdb_comparator_t* comparator) {
|
||||
const rocksdb_comparator_t* /*comparator*/) {
|
||||
rocksdb_sstfilewriter_t* writer = new rocksdb_sstfilewriter_t;
|
||||
writer->rep = new SstFileWriter(env->rep, io_options->rep);
|
||||
return writer;
|
||||
@@ -2913,7 +3200,7 @@ void rocksdb_sstfilewriter_delete(rocksdb_sstfilewriter_t* writer,
|
||||
|
||||
void rocksdb_sstfilewriter_finish(rocksdb_sstfilewriter_t* writer,
|
||||
char** errptr) {
|
||||
SaveError(errptr, writer->rep->Finish(NULL));
|
||||
SaveError(errptr, writer->rep->Finish(nullptr));
|
||||
}
|
||||
|
||||
void rocksdb_sstfilewriter_destroy(rocksdb_sstfilewriter_t* writer) {
|
||||
@@ -2951,6 +3238,12 @@ void rocksdb_ingestexternalfileoptions_set_allow_blocking_flush(
|
||||
opt->rep.allow_blocking_flush = allow_blocking_flush;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_set_ingest_behind(
|
||||
rocksdb_ingestexternalfileoptions_t* opt,
|
||||
unsigned char ingest_behind) {
|
||||
opt->rep.ingest_behind = ingest_behind;
|
||||
}
|
||||
|
||||
void rocksdb_ingestexternalfileoptions_destroy(
|
||||
rocksdb_ingestexternalfileoptions_t* opt) {
|
||||
delete opt;
|
||||
@@ -3339,6 +3632,14 @@ void rocksdb_transaction_rollback(rocksdb_transaction_t* txn, char** errptr) {
|
||||
SaveError(errptr, txn->rep->Rollback());
|
||||
}
|
||||
|
||||
void rocksdb_transaction_set_savepoint(rocksdb_transaction_t* txn) {
|
||||
txn->rep->SetSavePoint();
|
||||
}
|
||||
|
||||
void rocksdb_transaction_rollback_to_savepoint(rocksdb_transaction_t* txn, char** errptr) {
|
||||
SaveError(errptr, txn->rep->RollbackToSavePoint());
|
||||
}
|
||||
|
||||
void rocksdb_transaction_destroy(rocksdb_transaction_t* txn) {
|
||||
delete txn->rep;
|
||||
delete txn;
|
||||
@@ -3476,8 +3777,8 @@ void rocksdb_transactiondb_put(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* options,
|
||||
const char* key, size_t klen, const char* val,
|
||||
size_t vlen, char** errptr) {
|
||||
SaveError(errptr, txn_db->rep->Put(options->rep, Slice(key, klen),
|
||||
Slice(val, vlen)));
|
||||
SaveError(errptr,
|
||||
txn_db->rep->Put(options->rep, Slice(key, klen), Slice(val, vlen)));
|
||||
}
|
||||
|
||||
void rocksdb_transactiondb_put_cf(rocksdb_transactiondb_t* txn_db,
|
||||
@@ -3511,8 +3812,8 @@ void rocksdb_transactiondb_merge(rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* options,
|
||||
const char* key, size_t klen, const char* val,
|
||||
size_t vlen, char** errptr) {
|
||||
SaveError(errptr,
|
||||
txn_db->rep->Merge(options->rep, Slice(key, klen), Slice(val, vlen)));
|
||||
SaveError(errptr, txn_db->rep->Merge(options->rep, Slice(key, klen),
|
||||
Slice(val, vlen)));
|
||||
}
|
||||
|
||||
// Delete a key inside a transaction
|
||||
@@ -3550,6 +3851,15 @@ rocksdb_iterator_t* rocksdb_transaction_create_iterator(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create an iterator inside a transaction with column family
|
||||
rocksdb_iterator_t* rocksdb_transaction_create_iterator_cf(
|
||||
rocksdb_transaction_t* txn, const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family) {
|
||||
rocksdb_iterator_t* result = new rocksdb_iterator_t;
|
||||
result->rep = txn->rep->GetIterator(options->rep, column_family->rep);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Create an iterator outside a transaction
|
||||
rocksdb_iterator_t* rocksdb_transactiondb_create_iterator(
|
||||
rocksdb_transactiondb_t* txn_db, const rocksdb_readoptions_t* options) {
|
||||
@@ -3575,8 +3885,7 @@ rocksdb_checkpoint_t* rocksdb_transactiondb_checkpoint_object_create(
|
||||
}
|
||||
|
||||
rocksdb_optimistictransactiondb_t* rocksdb_optimistictransactiondb_open(
|
||||
const rocksdb_options_t* options, const char* name,
|
||||
char** errptr) {
|
||||
const rocksdb_options_t* options, const char* name, char** errptr) {
|
||||
OptimisticTransactionDB* otxn_db;
|
||||
if (SaveError(errptr, OptimisticTransactionDB::Open(
|
||||
options->rep, std::string(name), &otxn_db))) {
|
||||
@@ -3588,6 +3897,56 @@ rocksdb_optimistictransactiondb_t* rocksdb_optimistictransactiondb_open(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_optimistictransactiondb_t*
|
||||
rocksdb_optimistictransactiondb_open_column_families(
|
||||
const rocksdb_options_t* db_options, const char* name,
|
||||
int num_column_families, const char** column_family_names,
|
||||
const rocksdb_options_t** column_family_options,
|
||||
rocksdb_column_family_handle_t** column_family_handles, char** errptr) {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
for (int i = 0; i < num_column_families; i++) {
|
||||
column_families.push_back(ColumnFamilyDescriptor(
|
||||
std::string(column_family_names[i]),
|
||||
ColumnFamilyOptions(column_family_options[i]->rep)));
|
||||
}
|
||||
|
||||
OptimisticTransactionDB* otxn_db;
|
||||
std::vector<ColumnFamilyHandle*> handles;
|
||||
if (SaveError(errptr, OptimisticTransactionDB::Open(
|
||||
DBOptions(db_options->rep), std::string(name),
|
||||
column_families, &handles, &otxn_db))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < handles.size(); i++) {
|
||||
rocksdb_column_family_handle_t* c_handle =
|
||||
new rocksdb_column_family_handle_t;
|
||||
c_handle->rep = handles[i];
|
||||
column_family_handles[i] = c_handle;
|
||||
}
|
||||
rocksdb_optimistictransactiondb_t* result =
|
||||
new rocksdb_optimistictransactiondb_t;
|
||||
result->rep = otxn_db;
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_t* rocksdb_optimistictransactiondb_get_base_db(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db) {
|
||||
DB* base_db = otxn_db->rep->GetBaseDB();
|
||||
|
||||
if (base_db != nullptr) {
|
||||
rocksdb_t* result = new rocksdb_t;
|
||||
result->rep = base_db;
|
||||
return result;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void rocksdb_optimistictransactiondb_close_base_db(rocksdb_t* base_db) {
|
||||
delete base_db;
|
||||
}
|
||||
|
||||
rocksdb_transaction_t* rocksdb_optimistictransaction_begin(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db,
|
||||
const rocksdb_writeoptions_t* write_options,
|
||||
@@ -3623,7 +3982,7 @@ rocksdb_pinnableslice_t* rocksdb_get_pinned(
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@@ -3640,7 +3999,7 @@ rocksdb_pinnableslice_t* rocksdb_get_pinned_cf(
|
||||
if (!s.IsNotFound()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
@@ -3651,7 +4010,7 @@ const char* rocksdb_pinnableslice_value(const rocksdb_pinnableslice_t* v,
|
||||
size_t* vlen) {
|
||||
if (!v) {
|
||||
*vlen = 0;
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*vlen = v->rep.size();
|
||||
|
||||
+134
@@ -334,6 +334,20 @@ static void CheckTxnGet(
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnGetCF(rocksdb_transaction_t* txn,
|
||||
const rocksdb_readoptions_t* options,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
const char* key, const char* expected) {
|
||||
char* err = NULL;
|
||||
size_t val_len;
|
||||
char* val;
|
||||
val = rocksdb_transaction_get_cf(txn, options, column_family, key,
|
||||
strlen(key), &val_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual(expected, val, val_len);
|
||||
Free(&val);
|
||||
}
|
||||
|
||||
static void CheckTxnDBGet(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_readoptions_t* options,
|
||||
@@ -378,6 +392,8 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transactiondb_options_t* txn_db_options;
|
||||
rocksdb_transaction_t* txn;
|
||||
rocksdb_transaction_options_t* txn_options;
|
||||
rocksdb_optimistictransactiondb_t* otxn_db;
|
||||
rocksdb_optimistictransaction_options_t* otxn_options;
|
||||
char* err = NULL;
|
||||
int run = -1;
|
||||
|
||||
@@ -1447,6 +1463,25 @@ int main(int argc, char** argv) {
|
||||
CheckNoError(err);
|
||||
CheckTxnDBGet(txn_db, roptions, "bar", NULL);
|
||||
|
||||
// save point
|
||||
rocksdb_transaction_put(txn, "foo1", 4, "hi1", 3, &err);
|
||||
rocksdb_transaction_set_savepoint(txn);
|
||||
CheckTxnGet(txn, roptions, "foo1", "hi1");
|
||||
rocksdb_transaction_put(txn, "foo2", 4, "hi2", 3, &err);
|
||||
CheckTxnGet(txn, roptions, "foo2", "hi2");
|
||||
|
||||
// rollback to savepoint
|
||||
rocksdb_transaction_rollback_to_savepoint(txn, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnGet(txn, roptions, "foo2", NULL);
|
||||
CheckTxnGet(txn, roptions, "foo1", "hi1");
|
||||
CheckTxnDBGet(txn_db, roptions, "foo1", NULL);
|
||||
CheckTxnDBGet(txn_db, roptions, "foo2", NULL);
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnDBGet(txn_db, roptions, "foo1", "hi1");
|
||||
CheckTxnDBGet(txn_db, roptions, "foo2", NULL);
|
||||
|
||||
// Column families.
|
||||
rocksdb_column_family_handle_t* cfh;
|
||||
cfh = rocksdb_transactiondb_create_column_family(txn_db, options,
|
||||
@@ -1473,6 +1508,105 @@ int main(int argc, char** argv) {
|
||||
rocksdb_transactiondb_options_destroy(txn_db_options);
|
||||
}
|
||||
|
||||
StartPhase("optimistic_transactions");
|
||||
{
|
||||
rocksdb_options_t* db_options = rocksdb_options_create();
|
||||
rocksdb_options_set_create_if_missing(db_options, 1);
|
||||
rocksdb_options_set_allow_concurrent_memtable_write(db_options, 1);
|
||||
otxn_db = rocksdb_optimistictransactiondb_open(db_options, dbname, &err);
|
||||
otxn_options = rocksdb_optimistictransaction_options_create();
|
||||
rocksdb_transaction_t* txn1 = rocksdb_optimistictransaction_begin(
|
||||
otxn_db, woptions, otxn_options, NULL);
|
||||
rocksdb_transaction_t* txn2 = rocksdb_optimistictransaction_begin(
|
||||
otxn_db, woptions, otxn_options, NULL);
|
||||
rocksdb_transaction_put(txn1, "key", 3, "value", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_put(txn2, "key1", 4, "value1", 6, &err);
|
||||
CheckNoError(err);
|
||||
CheckTxnGet(txn1, roptions, "key", "value");
|
||||
rocksdb_transaction_commit(txn1, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_commit(txn2, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_destroy(txn1);
|
||||
rocksdb_transaction_destroy(txn2);
|
||||
|
||||
// Check column family
|
||||
db = rocksdb_optimistictransactiondb_get_base_db(otxn_db);
|
||||
rocksdb_put(db, woptions, "key", 3, "value", 5, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_column_family_handle_t *cfh1, *cfh2;
|
||||
cfh1 = rocksdb_create_column_family(db, db_options, "txn_db_cf1", &err);
|
||||
cfh2 = rocksdb_create_column_family(db, db_options, "txn_db_cf2", &err);
|
||||
txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options,
|
||||
NULL);
|
||||
rocksdb_transaction_put_cf(txn, cfh1, "key_cf1", 7, "val_cf1", 7, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_put_cf(txn, cfh2, "key_cf2", 7, "val_cf2", 7, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_commit(txn, &err);
|
||||
CheckNoError(err);
|
||||
txn = rocksdb_optimistictransaction_begin(otxn_db, woptions, otxn_options,
|
||||
txn);
|
||||
CheckGetCF(db, roptions, cfh1, "key_cf1", "val_cf1");
|
||||
CheckTxnGetCF(txn, roptions, cfh1, "key_cf1", "val_cf1");
|
||||
|
||||
// Check iterator with column family
|
||||
rocksdb_transaction_put_cf(txn, cfh1, "key1_cf", 7, "val1_cf", 7, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_iterator_t* iter =
|
||||
rocksdb_transaction_create_iterator_cf(txn, roptions, cfh1);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
CheckIter(iter, "key1_cf", "val1_cf");
|
||||
rocksdb_iter_get_error(iter, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
rocksdb_transaction_destroy(txn);
|
||||
rocksdb_column_family_handle_destroy(cfh1);
|
||||
rocksdb_column_family_handle_destroy(cfh2);
|
||||
rocksdb_optimistictransactiondb_close_base_db(db);
|
||||
rocksdb_optimistictransactiondb_close(otxn_db);
|
||||
|
||||
// Check open optimistic transaction db with column families
|
||||
size_t cf_len;
|
||||
char** column_fams =
|
||||
rocksdb_list_column_families(db_options, dbname, &cf_len, &err);
|
||||
CheckNoError(err);
|
||||
CheckEqual("default", column_fams[0], 7);
|
||||
CheckEqual("txn_db_cf1", column_fams[1], 10);
|
||||
CheckEqual("txn_db_cf2", column_fams[2], 10);
|
||||
CheckCondition(cf_len == 3);
|
||||
rocksdb_list_column_families_destroy(column_fams, cf_len);
|
||||
|
||||
const char* cf_names[3] = {"default", "txn_db_cf1", "txn_db_cf2"};
|
||||
rocksdb_options_t* cf_options = rocksdb_options_create();
|
||||
const rocksdb_options_t* cf_opts[3] = {cf_options, cf_options, cf_options};
|
||||
|
||||
rocksdb_options_set_error_if_exists(cf_options, 0);
|
||||
rocksdb_column_family_handle_t* cf_handles[3];
|
||||
otxn_db = rocksdb_optimistictransactiondb_open_column_families(
|
||||
db_options, dbname, 3, cf_names, cf_opts, cf_handles, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_transaction_t* txn_cf = rocksdb_optimistictransaction_begin(
|
||||
otxn_db, woptions, otxn_options, NULL);
|
||||
CheckTxnGetCF(txn_cf, roptions, cf_handles[0], "key", "value");
|
||||
CheckTxnGetCF(txn_cf, roptions, cf_handles[1], "key_cf1", "val_cf1");
|
||||
CheckTxnGetCF(txn_cf, roptions, cf_handles[2], "key_cf2", "val_cf2");
|
||||
rocksdb_transaction_destroy(txn_cf);
|
||||
rocksdb_options_destroy(cf_options);
|
||||
rocksdb_column_family_handle_destroy(cf_handles[0]);
|
||||
rocksdb_column_family_handle_destroy(cf_handles[1]);
|
||||
rocksdb_column_family_handle_destroy(cf_handles[2]);
|
||||
rocksdb_optimistictransactiondb_close(otxn_db);
|
||||
rocksdb_destroy_db(db_options, dbname, &err);
|
||||
rocksdb_options_destroy(db_options);
|
||||
rocksdb_optimistictransaction_options_destroy(otxn_options);
|
||||
CheckNoError(err);
|
||||
}
|
||||
|
||||
// Simple sanity check that setting memtable rep works.
|
||||
StartPhase("memtable_reps");
|
||||
{
|
||||
|
||||
+229
-59
@@ -31,6 +31,7 @@
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
#include "table/merging_iterator.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/compression.h"
|
||||
|
||||
@@ -53,6 +54,9 @@ ColumnFamilyHandleImpl::~ColumnFamilyHandleImpl() {
|
||||
#endif // ROCKSDB_LITE
|
||||
// Job id == 0 means that this is not our background process, but rather
|
||||
// user thread
|
||||
// Need to hold some shared pointers owned by the initial_cf_options
|
||||
// before final cleaning up finishes.
|
||||
ColumnFamilyOptions initial_cf_options_copy = cfd_->initial_cf_options();
|
||||
JobContext job_context(0);
|
||||
mutex_->Lock();
|
||||
if (cfd_->Unref()) {
|
||||
@@ -123,6 +127,22 @@ Status CheckCompressionSupported(const ColumnFamilyOptions& cf_options) {
|
||||
" is not linked with the binary.");
|
||||
}
|
||||
}
|
||||
if (cf_options.compression_opts.zstd_max_train_bytes > 0) {
|
||||
if (!CompressionTypeSupported(CompressionType::kZSTD)) {
|
||||
// Dictionary trainer is available since v0.6.1, but ZSTD was marked
|
||||
// stable only since v0.8.0. For now we enable the feature in stable
|
||||
// versions only.
|
||||
return Status::InvalidArgument(
|
||||
"zstd dictionary trainer cannot be used because " +
|
||||
CompressionTypeToString(CompressionType::kZSTD) +
|
||||
" is not linked with the binary.");
|
||||
}
|
||||
if (cf_options.compression_opts.max_dict_bytes == 0) {
|
||||
return Status::InvalidArgument(
|
||||
"The dictionary size limit (`CompressionOptions::max_dict_bytes`) "
|
||||
"should be nonzero if we're using zstd's dictionary generator.");
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
@@ -328,12 +348,13 @@ void SuperVersionUnrefHandle(void* ptr) {
|
||||
// When latter happens, we are in ~ColumnFamilyData(), no get should happen as
|
||||
// well.
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv->Unref()) {
|
||||
sv->db_mutex->Lock();
|
||||
sv->Cleanup();
|
||||
sv->db_mutex->Unlock();
|
||||
delete sv;
|
||||
}
|
||||
bool was_last_ref __attribute__((__unused__));
|
||||
was_last_ref = sv->Unref();
|
||||
// Thread-local SuperVersions can't outlive ColumnFamilyData::super_version_.
|
||||
// This is important because we can't do SuperVersion cleanup here.
|
||||
// That would require locking DB mutex, which would deadlock because
|
||||
// SuperVersionUnrefHandle is called with locked ThreadLocalPtr mutex.
|
||||
assert(!was_last_ref);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
||||
@@ -365,11 +386,13 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
next_(nullptr),
|
||||
prev_(nullptr),
|
||||
log_number_(0),
|
||||
flush_reason_(FlushReason::kOthers),
|
||||
column_family_set_(column_family_set),
|
||||
pending_flush_(false),
|
||||
pending_compaction_(false),
|
||||
prev_compaction_needed_bytes_(0),
|
||||
allow_2pc_(db_options.allow_2pc) {
|
||||
allow_2pc_(db_options.allow_2pc),
|
||||
last_memtable_id_(0) {
|
||||
Ref();
|
||||
|
||||
// Convert user defined table properties collector factories to internal ones.
|
||||
@@ -452,7 +475,7 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
local_sv_.reset();
|
||||
super_version_->db_mutex->Lock();
|
||||
|
||||
bool is_last_reference __attribute__((unused));
|
||||
bool is_last_reference __attribute__((__unused__));
|
||||
is_last_reference = super_version_->Unref();
|
||||
assert(is_last_reference);
|
||||
super_version_->Cleanup();
|
||||
@@ -463,7 +486,8 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
if (dummy_versions_ != nullptr) {
|
||||
// List must be empty
|
||||
assert(dummy_versions_->TEST_Next() == dummy_versions_);
|
||||
bool deleted __attribute__((unused)) = dummy_versions_->Unref();
|
||||
bool deleted __attribute__((__unused__));
|
||||
deleted = dummy_versions_->Unref();
|
||||
assert(deleted);
|
||||
}
|
||||
|
||||
@@ -613,58 +637,97 @@ int GetL0ThresholdSpeedupCompaction(int level0_file_num_compaction_trigger,
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
std::pair<WriteStallCondition, ColumnFamilyData::WriteStallCause>
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
if (num_unflushed_memtables >= mutable_cf_options.max_write_buffer_number) {
|
||||
return {WriteStallCondition::kStopped, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
num_l0_files >= mutable_cf_options.level0_stop_writes_trigger) {
|
||||
return {WriteStallCondition::kStopped, WriteStallCause::kL0FileCountLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
|
||||
num_compaction_needed_bytes >=
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit) {
|
||||
return {WriteStallCondition::kStopped,
|
||||
WriteStallCause::kPendingCompactionBytes};
|
||||
} else if (mutable_cf_options.max_write_buffer_number > 3 &&
|
||||
num_unflushed_memtables >=
|
||||
mutable_cf_options.max_write_buffer_number - 1) {
|
||||
return {WriteStallCondition::kDelayed, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
num_l0_files >=
|
||||
mutable_cf_options.level0_slowdown_writes_trigger) {
|
||||
return {WriteStallCondition::kDelayed, WriteStallCause::kL0FileCountLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
|
||||
num_compaction_needed_bytes >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) {
|
||||
return {WriteStallCondition::kDelayed,
|
||||
WriteStallCause::kPendingCompactionBytes};
|
||||
}
|
||||
return {WriteStallCondition::kNormal, WriteStallCause::kNone};
|
||||
}
|
||||
|
||||
WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
auto write_stall_condition = WriteStallCondition::kNormal;
|
||||
if (current_ != nullptr) {
|
||||
auto* vstorage = current_->storage_info();
|
||||
auto write_controller = column_family_set_->write_controller_;
|
||||
uint64_t compaction_needed_bytes =
|
||||
vstorage->estimated_compaction_needed_bytes();
|
||||
|
||||
auto write_stall_condition_and_cause = GetWriteStallConditionAndCause(
|
||||
imm()->NumNotFlushed(), vstorage->l0_delay_trigger_count(),
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options);
|
||||
write_stall_condition = write_stall_condition_and_cause.first;
|
||||
auto write_stall_cause = write_stall_condition_and_cause.second;
|
||||
|
||||
bool was_stopped = write_controller->IsStopped();
|
||||
bool needed_delay = write_controller->NeedsDelay();
|
||||
|
||||
if (imm()->NumNotFlushed() >= mutable_cf_options.max_write_buffer_number) {
|
||||
if (write_stall_condition == WriteStallCondition::kStopped &&
|
||||
write_stall_cause == WriteStallCause::kMemtableLimit) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_COMPACTION, 1);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_STOPS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
"[%s] Stopping writes because we have %d immutable memtables "
|
||||
"(waiting for flush), max_write_buffer_number is set to %d",
|
||||
name_.c_str(), imm()->NumNotFlushed(),
|
||||
mutable_cf_options.max_write_buffer_number);
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kStopped &&
|
||||
write_stall_cause == WriteStallCause::kL0FileCountLimit) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(InternalStats::LEVEL0_NUM_FILES_TOTAL, 1);
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LEVEL0_NUM_FILES_WITH_COMPACTION, 1);
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
}
|
||||
ROCKS_LOG_WARN(ioptions_.info_log,
|
||||
"[%s] Stopping writes because we have %d level-0 files",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit > 0 &&
|
||||
compaction_needed_bytes >=
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kStopped &&
|
||||
write_stall_cause == WriteStallCause::kPendingCompactionBytes) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::HARD_PENDING_COMPACTION_BYTES_LIMIT, 1);
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_STOPS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
"[%s] Stopping writes because of estimated pending compaction "
|
||||
"bytes %" PRIu64,
|
||||
name_.c_str(), compaction_needed_bytes);
|
||||
} else if (mutable_cf_options.max_write_buffer_number > 3 &&
|
||||
imm()->NumNotFlushed() >=
|
||||
mutable_cf_options.max_write_buffer_number - 1) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kDelayed &&
|
||||
write_stall_cause == WriteStallCause::kMemtableLimit) {
|
||||
write_controller_token_ =
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_SLOWDOWN, 1);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_SLOWDOWNS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
"[%s] Stalling writes because we have %d immutable memtables "
|
||||
@@ -673,10 +736,8 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
name_.c_str(), imm()->NumNotFlushed(),
|
||||
mutable_cf_options.max_write_buffer_number,
|
||||
write_controller->delayed_write_rate());
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_slowdown_writes_trigger) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kDelayed &&
|
||||
write_stall_cause == WriteStallCause::kL0FileCountLimit) {
|
||||
// L0 is the last two files from stopping.
|
||||
bool near_stop = vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger - 2;
|
||||
@@ -684,20 +745,19 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
SetupDelay(write_controller, compaction_needed_bytes,
|
||||
prev_compaction_needed_bytes_, was_stopped || near_stop,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::LEVEL0_SLOWDOWN_TOTAL, 1);
|
||||
internal_stats_->AddCFStats(InternalStats::L0_FILE_COUNT_LIMIT_SLOWDOWNS,
|
||||
1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LEVEL0_SLOWDOWN_WITH_COMPACTION, 1);
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, 1);
|
||||
}
|
||||
ROCKS_LOG_WARN(ioptions_.info_log,
|
||||
"[%s] Stalling writes because we have %d level-0 files "
|
||||
"rate %" PRIu64,
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count(),
|
||||
write_controller->delayed_write_rate());
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
|
||||
vstorage->estimated_compaction_needed_bytes() >=
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit) {
|
||||
} else if (write_stall_condition == WriteStallCondition::kDelayed &&
|
||||
write_stall_cause == WriteStallCause::kPendingCompactionBytes) {
|
||||
// If the distance to hard limit is less than 1/4 of the gap between soft
|
||||
// and
|
||||
// hard bytes limit, we think it is near stop and speed up the slowdown.
|
||||
@@ -714,7 +774,7 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
prev_compaction_needed_bytes_, was_stopped || near_stop,
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::SOFT_PENDING_COMPACTION_BYTES_LIMIT, 1);
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
"[%s] Stalling writes because of estimated pending compaction "
|
||||
@@ -722,6 +782,7 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes(),
|
||||
write_controller->delayed_write_rate());
|
||||
} else {
|
||||
assert(write_stall_condition == WriteStallCondition::kNormal);
|
||||
if (vstorage->l0_delay_trigger_count() >=
|
||||
GetL0ThresholdSpeedupCompaction(
|
||||
mutable_cf_options.level0_file_num_compaction_trigger,
|
||||
@@ -769,6 +830,7 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
}
|
||||
prev_compaction_needed_bytes_ = compaction_needed_bytes;
|
||||
}
|
||||
return write_stall_condition;
|
||||
}
|
||||
|
||||
const EnvOptions* ColumnFamilyData::soptions() const {
|
||||
@@ -787,6 +849,10 @@ uint64_t ColumnFamilyData::GetTotalSstFilesSize() const {
|
||||
return VersionSet::GetTotalSstFilesSize(dummy_versions_);
|
||||
}
|
||||
|
||||
uint64_t ColumnFamilyData::GetLiveSstFilesSize() const {
|
||||
return current_->GetSstFilesSize();
|
||||
}
|
||||
|
||||
MemTable* ColumnFamilyData::ConstructNewMemtable(
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
|
||||
return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
|
||||
@@ -823,6 +889,68 @@ bool ColumnFamilyData::RangeOverlapWithCompaction(
|
||||
smallest_user_key, largest_user_key, level);
|
||||
}
|
||||
|
||||
Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
const autovector<Range>& ranges, SuperVersion* super_version,
|
||||
bool* overlap) {
|
||||
assert(overlap != nullptr);
|
||||
*overlap = false;
|
||||
// Create an InternalIterator over all unflushed memtables
|
||||
Arena arena;
|
||||
ReadOptions read_opts;
|
||||
read_opts.total_order_seek = true;
|
||||
MergeIteratorBuilder merge_iter_builder(&internal_comparator_, &arena);
|
||||
merge_iter_builder.AddIterator(
|
||||
super_version->mem->NewIterator(read_opts, &arena));
|
||||
super_version->imm->AddIterators(read_opts, &merge_iter_builder);
|
||||
ScopedArenaIterator memtable_iter(merge_iter_builder.Finish());
|
||||
|
||||
std::vector<InternalIterator*> memtable_range_del_iters;
|
||||
auto* active_range_del_iter =
|
||||
super_version->mem->NewRangeTombstoneIterator(read_opts);
|
||||
if (active_range_del_iter != nullptr) {
|
||||
memtable_range_del_iters.push_back(active_range_del_iter);
|
||||
}
|
||||
super_version->imm->AddRangeTombstoneIterators(read_opts,
|
||||
&memtable_range_del_iters);
|
||||
RangeDelAggregator range_del_agg(internal_comparator_, {} /* snapshots */,
|
||||
false /* collapse_deletions */);
|
||||
Status status;
|
||||
{
|
||||
std::unique_ptr<InternalIterator> memtable_range_del_iter(
|
||||
NewMergingIterator(&internal_comparator_,
|
||||
memtable_range_del_iters.empty()
|
||||
? nullptr
|
||||
: &memtable_range_del_iters[0],
|
||||
static_cast<int>(memtable_range_del_iters.size())));
|
||||
status = range_del_agg.AddTombstones(std::move(memtable_range_del_iter));
|
||||
}
|
||||
for (size_t i = 0; i < ranges.size() && status.ok() && !*overlap; ++i) {
|
||||
auto* vstorage = super_version->current->storage_info();
|
||||
auto* ucmp = vstorage->InternalComparator()->user_comparator();
|
||||
InternalKey range_start(ranges[i].start, kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
memtable_iter->Seek(range_start.Encode());
|
||||
status = memtable_iter->status();
|
||||
ParsedInternalKey seek_result;
|
||||
if (status.ok()) {
|
||||
if (memtable_iter->Valid() &&
|
||||
!ParseInternalKey(memtable_iter->key(), &seek_result)) {
|
||||
status = Status::Corruption("DB have corrupted keys");
|
||||
}
|
||||
}
|
||||
if (status.ok()) {
|
||||
if (memtable_iter->Valid() &&
|
||||
ucmp->Compare(seek_result.user_key, ranges[i].limit) <= 0) {
|
||||
*overlap = true;
|
||||
} else if (range_del_agg.IsRangeOverlapped(ranges[i].start,
|
||||
ranges[i].limit)) {
|
||||
*overlap = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
const int ColumnFamilyData::kCompactAllLevels = -1;
|
||||
const int ColumnFamilyData::kCompactToBaseLevel = -2;
|
||||
|
||||
@@ -841,10 +969,13 @@ Compaction* ColumnFamilyData::CompactRange(
|
||||
|
||||
SuperVersion* ColumnFamilyData::GetReferencedSuperVersion(
|
||||
InstrumentedMutex* db_mutex) {
|
||||
SuperVersion* sv = nullptr;
|
||||
sv = GetThreadLocalSuperVersion(db_mutex);
|
||||
SuperVersion* sv = GetThreadLocalSuperVersion(db_mutex);
|
||||
sv->Ref();
|
||||
if (!ReturnThreadLocalSuperVersion(sv)) {
|
||||
// This Unref() corresponds to the Ref() in GetThreadLocalSuperVersion()
|
||||
// when the thread-local pointer was populated. So, the Ref() earlier in
|
||||
// this function still prevents the returned SuperVersion* from being
|
||||
// deleted out from under the caller.
|
||||
sv->Unref();
|
||||
}
|
||||
return sv;
|
||||
@@ -852,7 +983,6 @@ SuperVersion* ColumnFamilyData::GetReferencedSuperVersion(
|
||||
|
||||
SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(
|
||||
InstrumentedMutex* db_mutex) {
|
||||
SuperVersion* sv = nullptr;
|
||||
// The SuperVersion is cached in thread local storage to avoid acquiring
|
||||
// mutex when SuperVersion does not change since the last use. When a new
|
||||
// SuperVersion is installed, the compaction or flush thread cleans up
|
||||
@@ -871,7 +1001,7 @@ SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(
|
||||
// should only keep kSVInUse before ReturnThreadLocalSuperVersion call
|
||||
// (if no Scrape happens).
|
||||
assert(ptr != SuperVersion::kSVInUse);
|
||||
sv = static_cast<SuperVersion*>(ptr);
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv == SuperVersion::kSVObsolete ||
|
||||
sv->version_number != super_version_number_.load()) {
|
||||
RecordTick(ioptions_.statistics, NUMBER_SUPERVERSION_ACQUIRES);
|
||||
@@ -914,15 +1044,16 @@ bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SuperVersion* ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersion* new_superversion, InstrumentedMutex* db_mutex) {
|
||||
void ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex) {
|
||||
db_mutex->AssertHeld();
|
||||
return InstallSuperVersion(new_superversion, db_mutex, mutable_cf_options_);
|
||||
return InstallSuperVersion(sv_context, db_mutex, mutable_cf_options_);
|
||||
}
|
||||
|
||||
SuperVersion* ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersion* new_superversion, InstrumentedMutex* db_mutex,
|
||||
void ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
SuperVersion* new_superversion = sv_context->new_superversion.release();
|
||||
new_superversion->db_mutex = db_mutex;
|
||||
new_superversion->mutable_cf_options = mutable_cf_options;
|
||||
new_superversion->Init(mem_, imm_.current(), current_);
|
||||
@@ -930,16 +1061,31 @@ SuperVersion* ColumnFamilyData::InstallSuperVersion(
|
||||
super_version_ = new_superversion;
|
||||
++super_version_number_;
|
||||
super_version_->version_number = super_version_number_;
|
||||
// Reset SuperVersions cached in thread local storage
|
||||
ResetThreadLocalSuperVersions();
|
||||
super_version_->write_stall_condition =
|
||||
RecalculateWriteStallConditions(mutable_cf_options);
|
||||
|
||||
RecalculateWriteStallConditions(mutable_cf_options);
|
||||
if (old_superversion != nullptr) {
|
||||
// Reset SuperVersions cached in thread local storage.
|
||||
// This should be done before old_superversion->Unref(). That's to ensure
|
||||
// that local_sv_ never holds the last reference to SuperVersion, since
|
||||
// it has no means to safely do SuperVersion cleanup.
|
||||
ResetThreadLocalSuperVersions();
|
||||
|
||||
if (old_superversion != nullptr && old_superversion->Unref()) {
|
||||
old_superversion->Cleanup();
|
||||
return old_superversion; // will let caller delete outside of mutex
|
||||
if (old_superversion->mutable_cf_options.write_buffer_size !=
|
||||
mutable_cf_options.write_buffer_size) {
|
||||
mem_->UpdateWriteBufferSize(mutable_cf_options.write_buffer_size);
|
||||
}
|
||||
if (old_superversion->write_stall_condition !=
|
||||
new_superversion->write_stall_condition) {
|
||||
sv_context->PushWriteStallNotification(
|
||||
old_superversion->write_stall_condition,
|
||||
new_superversion->write_stall_condition, GetName(), ioptions());
|
||||
}
|
||||
if (old_superversion->Unref()) {
|
||||
old_superversion->Cleanup();
|
||||
sv_context->superversions_to_free.push_back(old_superversion);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ColumnFamilyData::ResetThreadLocalSuperVersions() {
|
||||
@@ -951,10 +1097,12 @@ void ColumnFamilyData::ResetThreadLocalSuperVersions() {
|
||||
continue;
|
||||
}
|
||||
auto sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv->Unref()) {
|
||||
sv->Cleanup();
|
||||
delete sv;
|
||||
}
|
||||
bool was_last_ref __attribute__((__unused__));
|
||||
was_last_ref = sv->Unref();
|
||||
// sv couldn't have been the last reference because
|
||||
// ResetThreadLocalSuperVersions() is called before
|
||||
// unref'ing super_version_.
|
||||
assert(!was_last_ref);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -972,6 +1120,24 @@ Status ColumnFamilyData::SetOptions(
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// REQUIRES: DB mutex held
|
||||
Env::WriteLifeTimeHint ColumnFamilyData::CalculateSSTWriteHint(int level) {
|
||||
if (initial_cf_options_.compaction_style != kCompactionStyleLevel) {
|
||||
return Env::WLTH_NOT_SET;
|
||||
}
|
||||
if (level == 0) {
|
||||
return Env::WLTH_MEDIUM;
|
||||
}
|
||||
int base_level = current_->storage_info()->base_level();
|
||||
|
||||
// L1: medium, L2: long, ...
|
||||
if (level - base_level >= 2) {
|
||||
return Env::WLTH_EXTREME;
|
||||
}
|
||||
return static_cast<Env::WriteLifeTimeHint>(level - base_level +
|
||||
static_cast<int>(Env::WLTH_MEDIUM));
|
||||
}
|
||||
|
||||
ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
const ImmutableDBOptions* db_options,
|
||||
const EnvOptions& env_options,
|
||||
@@ -998,10 +1164,14 @@ ColumnFamilySet::~ColumnFamilySet() {
|
||||
while (column_family_data_.size() > 0) {
|
||||
// cfd destructor will delete itself from column_family_data_
|
||||
auto cfd = column_family_data_.begin()->second;
|
||||
cfd->Unref();
|
||||
bool last_ref __attribute__((__unused__));
|
||||
last_ref = cfd->Unref();
|
||||
assert(last_ref);
|
||||
delete cfd;
|
||||
}
|
||||
dummy_cfd_->Unref();
|
||||
bool dummy_last_ref __attribute__((__unused__));
|
||||
dummy_last_ref = dummy_cfd_->Unref();
|
||||
assert(dummy_last_ref);
|
||||
delete dummy_cfd_;
|
||||
}
|
||||
|
||||
|
||||
+55
-11
@@ -30,6 +30,7 @@ namespace rocksdb {
|
||||
|
||||
class Version;
|
||||
class VersionSet;
|
||||
class VersionStorageInfo;
|
||||
class MemTable;
|
||||
class MemTableListVersion;
|
||||
class CompactionPicker;
|
||||
@@ -41,6 +42,7 @@ class DBImpl;
|
||||
class LogBuffer;
|
||||
class InstrumentedMutex;
|
||||
class InstrumentedMutexLock;
|
||||
struct SuperVersionContext;
|
||||
|
||||
extern const double kIncSlowdownRatio;
|
||||
|
||||
@@ -76,7 +78,7 @@ class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
|
||||
class ColumnFamilyHandleInternal : public ColumnFamilyHandleImpl {
|
||||
public:
|
||||
ColumnFamilyHandleInternal()
|
||||
: ColumnFamilyHandleImpl(nullptr, nullptr, nullptr) {}
|
||||
: ColumnFamilyHandleImpl(nullptr, nullptr, nullptr), internal_cfd_(nullptr) {}
|
||||
|
||||
void SetCFD(ColumnFamilyData* _cfd) { internal_cfd_ = _cfd; }
|
||||
virtual ColumnFamilyData* cfd() const override { return internal_cfd_; }
|
||||
@@ -95,6 +97,7 @@ struct SuperVersion {
|
||||
MutableCFOptions mutable_cf_options;
|
||||
// Version number of the current SuperVersion
|
||||
uint64_t version_number;
|
||||
WriteStallCondition write_stall_condition;
|
||||
|
||||
InstrumentedMutex* db_mutex;
|
||||
|
||||
@@ -192,7 +195,7 @@ class ColumnFamilyData {
|
||||
// *) delete all memory associated with that column family
|
||||
// *) delete all the files associated with that column family
|
||||
void SetDropped();
|
||||
bool IsDropped() const { return dropped_; }
|
||||
bool IsDropped() const { return dropped_.load(std::memory_order_relaxed); }
|
||||
|
||||
// thread-safe
|
||||
int NumberLevels() const { return ioptions_.num_levels; }
|
||||
@@ -200,6 +203,10 @@ class ColumnFamilyData {
|
||||
void SetLogNumber(uint64_t log_number) { log_number_ = log_number; }
|
||||
uint64_t GetLogNumber() const { return log_number_; }
|
||||
|
||||
void SetFlushReason(FlushReason flush_reason) {
|
||||
flush_reason_ = flush_reason;
|
||||
}
|
||||
FlushReason GetFlushReason() const { return flush_reason_; }
|
||||
// thread-safe
|
||||
const EnvOptions* soptions() const;
|
||||
const ImmutableCFOptions* ioptions() const { return &ioptions_; }
|
||||
@@ -237,7 +244,12 @@ class ColumnFamilyData {
|
||||
void SetCurrent(Version* _current);
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) { mem_ = new_mem; }
|
||||
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) {
|
||||
uint64_t memtable_id = last_memtable_id_.fetch_add(1) + 1;
|
||||
new_mem->SetID(memtable_id);
|
||||
mem_ = new_mem;
|
||||
}
|
||||
|
||||
// calculate the oldest log needed for the durability of this column family
|
||||
uint64_t OldestLogToKeep();
|
||||
@@ -263,8 +275,18 @@ class ColumnFamilyData {
|
||||
const Slice& largest_user_key,
|
||||
int level) const;
|
||||
|
||||
// Check if the passed ranges overlap with any unflushed memtables
|
||||
// (immutable or mutable).
|
||||
//
|
||||
// @param super_version A referenced SuperVersion that will be held for the
|
||||
// duration of this function.
|
||||
//
|
||||
// Thread-safe
|
||||
Status RangesOverlapWithMemtables(const autovector<Range>& ranges,
|
||||
SuperVersion* super_version, bool* overlap);
|
||||
|
||||
// A flag to tell a manual compaction is to compact all levels together
|
||||
// instad of for specific level.
|
||||
// instead of a specific level.
|
||||
static const int kCompactAllLevels;
|
||||
// A flag to tell a manual compaction's output is base level.
|
||||
static const int kCompactToBaseLevel;
|
||||
@@ -311,11 +333,11 @@ class ColumnFamilyData {
|
||||
// As argument takes a pointer to allocated SuperVersion to enable
|
||||
// the clients to allocate SuperVersion outside of mutex.
|
||||
// IMPORTANT: Only call this from DBImpl::InstallSuperVersion()
|
||||
SuperVersion* InstallSuperVersion(SuperVersion* new_superversion,
|
||||
InstrumentedMutex* db_mutex,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
SuperVersion* InstallSuperVersion(SuperVersion* new_superversion,
|
||||
InstrumentedMutex* db_mutex);
|
||||
void InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
void InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex);
|
||||
|
||||
void ResetThreadLocalSuperVersions();
|
||||
|
||||
@@ -325,18 +347,35 @@ class ColumnFamilyData {
|
||||
bool pending_flush() { return pending_flush_; }
|
||||
bool pending_compaction() { return pending_compaction_; }
|
||||
|
||||
enum class WriteStallCause {
|
||||
kNone,
|
||||
kMemtableLimit,
|
||||
kL0FileCountLimit,
|
||||
kPendingCompactionBytes,
|
||||
};
|
||||
static std::pair<WriteStallCondition, WriteStallCause>
|
||||
GetWriteStallConditionAndCause(int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Recalculate some small conditions, which are changed only during
|
||||
// compaction, adding new memtable and/or
|
||||
// recalculation of compaction score. These values are used in
|
||||
// DBImpl::MakeRoomForWrite function to decide, if it need to make
|
||||
// a write stall
|
||||
void RecalculateWriteStallConditions(
|
||||
WriteStallCondition RecalculateWriteStallConditions(
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
void set_initialized() { initialized_.store(true); }
|
||||
|
||||
bool initialized() const { return initialized_.load(); }
|
||||
|
||||
const ColumnFamilyOptions& initial_cf_options() {
|
||||
return initial_cf_options_;
|
||||
}
|
||||
|
||||
Env::WriteLifeTimeHint CalculateSSTWriteHint(int level);
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
@@ -354,7 +393,7 @@ class ColumnFamilyData {
|
||||
|
||||
std::atomic<int> refs_; // outstanding references to ColumnFamilyData
|
||||
std::atomic<bool> initialized_;
|
||||
bool dropped_; // true if client dropped it
|
||||
std::atomic<bool> dropped_; // true if client dropped it
|
||||
|
||||
const InternalKeyComparator internal_comparator_;
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
|
||||
@@ -396,6 +435,8 @@ class ColumnFamilyData {
|
||||
// recovered from
|
||||
uint64_t log_number_;
|
||||
|
||||
std::atomic<FlushReason> flush_reason_;
|
||||
|
||||
// An object that keeps all the compaction stats
|
||||
// and picks the next compaction
|
||||
std::unique_ptr<CompactionPicker> compaction_picker_;
|
||||
@@ -415,6 +456,9 @@ class ColumnFamilyData {
|
||||
|
||||
// if the database was opened with 2pc enabled
|
||||
bool allow_2pc_;
|
||||
|
||||
// Memtable id to track flush.
|
||||
std::atomic<uint64_t> last_memtable_id_;
|
||||
};
|
||||
|
||||
// ColumnFamilySet has interesting thread-safety requirements
|
||||
|
||||
+33
-140
@@ -933,62 +933,62 @@ TEST_F(ColumnFamilyTest, FlushTest) {
|
||||
TEST_F(ColumnFamilyTest, LogDeletionTest) {
|
||||
db_options_.max_total_wal_size = std::numeric_limits<uint64_t>::max();
|
||||
column_family_options_.arena_block_size = 4 * 1024;
|
||||
column_family_options_.write_buffer_size = 100000; // 100KB
|
||||
column_family_options_.write_buffer_size = 128000; // 128KB
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two", "three", "four"});
|
||||
// Each bracket is one log file. if number is in (), it means
|
||||
// we don't need it anymore (it's been flushed)
|
||||
// []
|
||||
AssertCountLiveLogFiles(0);
|
||||
PutRandomData(0, 1, 100);
|
||||
PutRandomData(0, 1, 128);
|
||||
// [0]
|
||||
PutRandomData(1, 1, 100);
|
||||
PutRandomData(1, 1, 128);
|
||||
// [0, 1]
|
||||
PutRandomData(1, 1000, 100);
|
||||
PutRandomData(1, 1000, 128);
|
||||
WaitForFlush(1);
|
||||
// [0, (1)] [1]
|
||||
AssertCountLiveLogFiles(2);
|
||||
PutRandomData(0, 1, 100);
|
||||
PutRandomData(0, 1, 128);
|
||||
// [0, (1)] [0, 1]
|
||||
AssertCountLiveLogFiles(2);
|
||||
PutRandomData(2, 1, 100);
|
||||
PutRandomData(2, 1, 128);
|
||||
// [0, (1)] [0, 1, 2]
|
||||
PutRandomData(2, 1000, 100);
|
||||
PutRandomData(2, 1000, 128);
|
||||
WaitForFlush(2);
|
||||
// [0, (1)] [0, 1, (2)] [2]
|
||||
AssertCountLiveLogFiles(3);
|
||||
PutRandomData(2, 1000, 100);
|
||||
PutRandomData(2, 1000, 128);
|
||||
WaitForFlush(2);
|
||||
// [0, (1)] [0, 1, (2)] [(2)] [2]
|
||||
AssertCountLiveLogFiles(4);
|
||||
PutRandomData(3, 1, 100);
|
||||
PutRandomData(3, 1, 128);
|
||||
// [0, (1)] [0, 1, (2)] [(2)] [2, 3]
|
||||
PutRandomData(1, 1, 100);
|
||||
PutRandomData(1, 1, 128);
|
||||
// [0, (1)] [0, 1, (2)] [(2)] [1, 2, 3]
|
||||
AssertCountLiveLogFiles(4);
|
||||
PutRandomData(1, 1000, 100);
|
||||
PutRandomData(1, 1000, 128);
|
||||
WaitForFlush(1);
|
||||
// [0, (1)] [0, (1), (2)] [(2)] [(1), 2, 3] [1]
|
||||
AssertCountLiveLogFiles(5);
|
||||
PutRandomData(0, 1000, 100);
|
||||
PutRandomData(0, 1000, 128);
|
||||
WaitForFlush(0);
|
||||
// [(0), (1)] [(0), (1), (2)] [(2)] [(1), 2, 3] [1, (0)] [0]
|
||||
// delete obsolete logs -->
|
||||
// [(1), 2, 3] [1, (0)] [0]
|
||||
AssertCountLiveLogFiles(3);
|
||||
PutRandomData(0, 1000, 100);
|
||||
PutRandomData(0, 1000, 128);
|
||||
WaitForFlush(0);
|
||||
// [(1), 2, 3] [1, (0)], [(0)] [0]
|
||||
AssertCountLiveLogFiles(4);
|
||||
PutRandomData(1, 1000, 100);
|
||||
PutRandomData(1, 1000, 128);
|
||||
WaitForFlush(1);
|
||||
// [(1), 2, 3] [(1), (0)] [(0)] [0, (1)] [1]
|
||||
AssertCountLiveLogFiles(5);
|
||||
PutRandomData(2, 1000, 100);
|
||||
PutRandomData(2, 1000, 128);
|
||||
WaitForFlush(2);
|
||||
// [(1), (2), 3] [(1), (0)] [(0)] [0, (1)] [1, (2)], [2]
|
||||
AssertCountLiveLogFiles(6);
|
||||
PutRandomData(3, 1000, 100);
|
||||
PutRandomData(3, 1000, 128);
|
||||
WaitForFlush(3);
|
||||
// [(1), (2), (3)] [(1), (0)] [(0)] [0, (1)] [1, (2)], [2, (3)] [3]
|
||||
// delete obsolete logs -->
|
||||
@@ -1168,13 +1168,14 @@ TEST_F(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class TestComparator : public Comparator {
|
||||
int Compare(const rocksdb::Slice& a, const rocksdb::Slice& b) const override {
|
||||
int Compare(const rocksdb::Slice& /*a*/,
|
||||
const rocksdb::Slice& /*b*/) const override {
|
||||
return 0;
|
||||
}
|
||||
const char* Name() const override { return "Test"; }
|
||||
void FindShortestSeparator(std::string* start,
|
||||
const rocksdb::Slice& limit) const override {}
|
||||
void FindShortSuccessor(std::string* key) const override {}
|
||||
void FindShortestSeparator(std::string* /*start*/,
|
||||
const rocksdb::Slice& /*limit*/) const override {}
|
||||
void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
static TestComparator third_comparator;
|
||||
@@ -1868,126 +1869,6 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
}
|
||||
}
|
||||
|
||||
// This test checks for automatic getting a conflict if there is a
|
||||
// manual which has not yet been scheduled.
|
||||
// The manual compaction waits in NotScheduled
|
||||
// We generate more files and then trigger an automatic compaction
|
||||
// This will wait because there is an unscheduled manual compaction.
|
||||
// Once the conflict is hit, the manual compaction starts and ends
|
||||
// Then another automatic will start and end.
|
||||
TEST_F(ColumnFamilyTest, SameCFManualAutomaticConflict) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ColumnFamilyOptions default_cf, one;
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.max_background_compactions = 3;
|
||||
|
||||
default_cf.compaction_style = kCompactionStyleLevel;
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.max_compaction_bytes = default_cf.target_file_size_base * 1100;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
one.compaction_style = kCompactionStyleUniversal;
|
||||
|
||||
one.num_levels = 1;
|
||||
// trigger compaction if there are >= 4 files
|
||||
one.level0_file_num_compaction_trigger = 4;
|
||||
one.write_buffer_size = 120000;
|
||||
|
||||
Reopen({default_cf, one});
|
||||
// make sure all background compaction jobs can be scheduled
|
||||
auto stop_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
|
||||
// SETUP column family "one" -- universal style
|
||||
for (int i = 0; i < one.level0_file_num_compaction_trigger - 2; ++i) {
|
||||
PutRandomData(1, 10, 12000, true);
|
||||
PutRandomData(1, 1, 10, true);
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(ToString(i + 1), 1);
|
||||
}
|
||||
bool cf_1_1 = true;
|
||||
bool cf_1_2 = true;
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BackgroundCompaction()::Conflict",
|
||||
"ColumnFamilyTest::ManualAutoCon:7"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:9",
|
||||
"ColumnFamilyTest::ManualAutoCon:8"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:2",
|
||||
"ColumnFamilyTest::ManualAutoCon:6"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:4",
|
||||
"ColumnFamilyTest::ManualAutoCon:5"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:1",
|
||||
"ColumnFamilyTest::ManualAutoCon:2"},
|
||||
{"ColumnFamilyTest::ManualAutoCon:1",
|
||||
"ColumnFamilyTest::ManualAutoCon:3"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) {
|
||||
if (cf_1_1) {
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:4");
|
||||
cf_1_1 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:3");
|
||||
} else if (cf_1_2) {
|
||||
cf_1_2 = false;
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:2");
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::RunManualCompaction:NotScheduled", [&](void* arg) {
|
||||
InstrumentedMutex* mutex = static_cast<InstrumentedMutex*>(arg);
|
||||
mutex->Unlock();
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:9");
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:7");
|
||||
mutex->Lock();
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
rocksdb::port::Thread threads([&] {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.exclusive_manual_compaction = false;
|
||||
ASSERT_OK(
|
||||
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr));
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:6");
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:8");
|
||||
WaitForFlush(1);
|
||||
|
||||
// Add more L0 files and force automatic compaction
|
||||
for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) {
|
||||
PutRandomData(1, 10, 12000, true);
|
||||
PutRandomData(1, 1, 10, true);
|
||||
WaitForFlush(1);
|
||||
AssertFilesPerLevel(ToString(one.level0_file_num_compaction_trigger + i),
|
||||
1);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:5");
|
||||
// Add more L0 files and force automatic compaction
|
||||
for (int i = 0; i < one.level0_file_num_compaction_trigger; ++i) {
|
||||
PutRandomData(1, 10, 12000, true);
|
||||
PutRandomData(1, 1, 10, true);
|
||||
WaitForFlush(1);
|
||||
}
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::ManualAutoCon:1");
|
||||
|
||||
threads.join();
|
||||
WaitForCompaction();
|
||||
// VERIFY compaction "one"
|
||||
ASSERT_LE(NumTableFilesAtLevel(0, 1), 3);
|
||||
|
||||
// Compare against saved keys
|
||||
std::set<std::string>::iterator key_iter = keys_.begin();
|
||||
while (key_iter != keys_.end()) {
|
||||
ASSERT_NE("NOT_FOUND", Get(1, *key_iter));
|
||||
key_iter++;
|
||||
}
|
||||
}
|
||||
|
||||
// In this test, we generate enough files to trigger automatic compactions.
|
||||
// The automatic compaction waits in NonTrivial:AfterRun
|
||||
// We generate more files and then trigger an automatic compaction
|
||||
@@ -2910,6 +2791,18 @@ TEST_F(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) {
|
||||
ASSERT_EQ(1, dbfull()->TEST_BGCompactionsAllowed());
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, CreateAndDestoryOptions) {
|
||||
std::unique_ptr<ColumnFamilyOptions> cfo(new ColumnFamilyOptions());
|
||||
ColumnFamilyHandle* cfh;
|
||||
Open();
|
||||
ASSERT_OK(db_->CreateColumnFamily(*(cfo.get()), "yoyo", &cfh));
|
||||
cfo.reset();
|
||||
ASSERT_OK(db_->Put(WriteOptions(), cfh, "foo", "bar"));
|
||||
ASSERT_OK(db_->Flush(FlushOptions(), cfh));
|
||||
ASSERT_OK(db_->DropColumnFamily(cfh));
|
||||
ASSERT_OK(db_->DestroyColumnFamilyHandle(cfh));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
SpecialEnv env(Env::Default());
|
||||
|
||||
@@ -37,8 +37,7 @@ class FlushedFileCollector : public EventListener {
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() {}
|
||||
|
||||
virtual void OnFlushCompleted(
|
||||
DB* db, const FlushJobInfo& info) override {
|
||||
virtual void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
@@ -257,9 +256,9 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
|
||||
TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
class FilterWithGet : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
if (db_ == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@ extern bool SaveValue(void* arg, const ParsedInternalKey& parsed_key,
|
||||
|
||||
CompactedDBImpl::CompactedDBImpl(
|
||||
const DBOptions& options, const std::string& dbname)
|
||||
: DBImpl(options, dbname) {
|
||||
: DBImpl(options, dbname), cfd_(nullptr), version_(nullptr),
|
||||
user_comparator_(nullptr) {
|
||||
}
|
||||
|
||||
CompactedDBImpl::~CompactedDBImpl() {
|
||||
@@ -93,6 +94,7 @@ std::vector<Status> CompactedDBImpl::MultiGet(const ReadOptions& options,
|
||||
}
|
||||
|
||||
Status CompactedDBImpl::Init(const Options& options) {
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
mutex_.Lock();
|
||||
ColumnFamilyDescriptor cf(kDefaultColumnFamilyName,
|
||||
ColumnFamilyOptions(options));
|
||||
@@ -100,9 +102,10 @@ Status CompactedDBImpl::Init(const Options& options) {
|
||||
if (s.ok()) {
|
||||
cfd_ = reinterpret_cast<ColumnFamilyHandleImpl*>(
|
||||
DefaultColumnFamily())->cfd();
|
||||
delete cfd_->InstallSuperVersion(new SuperVersion(), &mutex_);
|
||||
cfd_->InstallSuperVersion(&sv_context, &mutex_);
|
||||
}
|
||||
mutex_.Unlock();
|
||||
sv_context.Clean();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
+23
-22
@@ -32,55 +32,56 @@ class CompactedDBImpl : public DBImpl {
|
||||
override;
|
||||
|
||||
using DBImpl::Put;
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) override {
|
||||
virtual Status Put(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/, const Slice& /*value*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
using DBImpl::Merge;
|
||||
virtual Status Merge(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) override {
|
||||
virtual Status Merge(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/, const Slice& /*value*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
using DBImpl::Delete;
|
||||
virtual Status Delete(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key) override {
|
||||
virtual Status Delete(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
virtual Status Write(const WriteOptions& options,
|
||||
WriteBatch* updates) override {
|
||||
virtual Status Write(const WriteOptions& /*options*/,
|
||||
WriteBatch* /*updates*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
using DBImpl::CompactRange;
|
||||
virtual Status CompactRange(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) override {
|
||||
virtual Status CompactRange(const CompactRangeOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice* /*begin*/,
|
||||
const Slice* /*end*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
|
||||
virtual Status DisableFileDeletions() override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
virtual Status EnableFileDeletions(bool force) override {
|
||||
virtual Status EnableFileDeletions(bool /*force*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable = true) override {
|
||||
uint64_t* /*manifest_file_size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
using DBImpl::Flush;
|
||||
virtual Status Flush(const FlushOptions& options,
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
virtual Status Flush(const FlushOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
using DB::IngestExternalFile;
|
||||
virtual Status IngestExternalFile(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& external_files,
|
||||
const IngestExternalFileOptions& ingestion_options) override {
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*external_files*/,
|
||||
const IngestExternalFileOptions& /*ingestion_options*/) override {
|
||||
return Status::NotSupported("Not supported in compacted db mode.");
|
||||
}
|
||||
|
||||
|
||||
+35
-44
@@ -86,35 +86,23 @@ void Compaction::GetBoundaryKeys(
|
||||
bool Compaction::IsBottommostLevel(
|
||||
int output_level, VersionStorageInfo* vstorage,
|
||||
const std::vector<CompactionInputFiles>& inputs) {
|
||||
if (inputs[0].level == 0 &&
|
||||
inputs[0].files.back() != vstorage->LevelFiles(0).back()) {
|
||||
return false;
|
||||
int output_l0_idx;
|
||||
if (output_level == 0) {
|
||||
output_l0_idx = 0;
|
||||
for (const auto* file : vstorage->LevelFiles(0)) {
|
||||
if (inputs[0].files.back() == file) {
|
||||
break;
|
||||
}
|
||||
++output_l0_idx;
|
||||
}
|
||||
assert(static_cast<size_t>(output_l0_idx) < vstorage->LevelFiles(0).size());
|
||||
} else {
|
||||
output_l0_idx = -1;
|
||||
}
|
||||
|
||||
Slice smallest_key, largest_key;
|
||||
GetBoundaryKeys(vstorage, inputs, &smallest_key, &largest_key);
|
||||
|
||||
// Checks whether there are files living beyond the output_level.
|
||||
// If lower levels have files, it checks for overlap between files
|
||||
// if the compaction process and those files.
|
||||
// Bottomlevel optimizations can be made if there are no files in
|
||||
// lower levels or if there is no overlap with the files in
|
||||
// the lower levels.
|
||||
for (int i = output_level + 1; i < vstorage->num_levels(); i++) {
|
||||
// It is not the bottommost level if there are files in higher
|
||||
// levels when the output level is 0 or if there are files in
|
||||
// higher levels which overlap with files to be compacted.
|
||||
// output_level == 0 means that we want it to be considered
|
||||
// s the bottommost level only if the last file on the level
|
||||
// is a part of the files to be compacted - this is verified by
|
||||
// the first if condition in this function
|
||||
if (vstorage->NumLevelFiles(i) > 0 &&
|
||||
(output_level == 0 ||
|
||||
vstorage->OverlapInLevel(i, &smallest_key, &largest_key))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return !vstorage->RangeMightExistAfterSortedRun(smallest_key, largest_key,
|
||||
output_level, output_l0_idx);
|
||||
}
|
||||
|
||||
// test function to validate the functionality of IsBottommostLevel()
|
||||
@@ -241,7 +229,7 @@ bool Compaction::IsTrivialMove() const {
|
||||
|
||||
// Used in universal compaction, where trivial move can be done if the
|
||||
// input files are non overlapping
|
||||
if ((immutable_cf_options_.compaction_options_universal.allow_trivial_move) &&
|
||||
if ((mutable_cf_options_.compaction_options_universal.allow_trivial_move) &&
|
||||
(output_level_ != 0)) {
|
||||
return is_trivial_move_;
|
||||
}
|
||||
@@ -284,10 +272,10 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
assert(input_version_ != nullptr);
|
||||
assert(level_ptrs != nullptr);
|
||||
assert(level_ptrs->size() == static_cast<size_t>(number_levels_));
|
||||
if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
if (output_level_ == 0) {
|
||||
return false;
|
||||
}
|
||||
if (bottommost_level_) {
|
||||
return true;
|
||||
} else if (output_level_ != 0 &&
|
||||
cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
// Maybe use binary search to find right entry instead of linear search?
|
||||
const Comparator* user_cmp = cfd_->user_comparator();
|
||||
for (int lvl = output_level_ + 1; lvl < number_levels_; lvl++) {
|
||||
@@ -307,9 +295,8 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return bottommost_level_;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Mark (or clear) each file that is being compacted
|
||||
@@ -418,20 +405,23 @@ void Compaction::Summary(char* output, int len) {
|
||||
uint64_t Compaction::OutputFilePreallocationSize() const {
|
||||
uint64_t preallocation_size = 0;
|
||||
|
||||
if (max_output_file_size_ != port::kMaxUint64 &&
|
||||
(cfd_->ioptions()->compaction_style == kCompactionStyleLevel ||
|
||||
output_level() > 0)) {
|
||||
preallocation_size = max_output_file_size_;
|
||||
} else {
|
||||
for (const auto& level_files : inputs_) {
|
||||
for (const auto& file : level_files.files) {
|
||||
preallocation_size += file->fd.GetFileSize();
|
||||
}
|
||||
for (const auto& level_files : inputs_) {
|
||||
for (const auto& file : level_files.files) {
|
||||
preallocation_size += file->fd.GetFileSize();
|
||||
}
|
||||
}
|
||||
|
||||
if (max_output_file_size_ != port::kMaxUint64 &&
|
||||
(immutable_cf_options_.compaction_style == kCompactionStyleLevel ||
|
||||
output_level() > 0)) {
|
||||
preallocation_size = std::min(max_output_file_size_, preallocation_size);
|
||||
}
|
||||
|
||||
// Over-estimate slightly so we don't end up just barely crossing
|
||||
// the threshold
|
||||
return preallocation_size + (preallocation_size / 10);
|
||||
// No point to prellocate more than 1GB.
|
||||
return std::min(uint64_t{1073741824},
|
||||
preallocation_size + (preallocation_size / 10));
|
||||
}
|
||||
|
||||
std::unique_ptr<CompactionFilter> Compaction::CreateCompactionFilter() const {
|
||||
@@ -456,7 +446,8 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
return false;
|
||||
}
|
||||
if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
return start_level_ == 0 && output_level_ > 0 && !IsOutputLevelEmpty();
|
||||
return (start_level_ == 0 || is_manual_compaction_) && output_level_ > 0 &&
|
||||
!IsOutputLevelEmpty();
|
||||
} else if (cfd_->ioptions()->compaction_style == kCompactionStyleUniversal) {
|
||||
return number_levels_ > 1 && output_level_ > 0;
|
||||
} else {
|
||||
|
||||
+153
-111
@@ -4,75 +4,59 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/compaction_iterator.h"
|
||||
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "table/internal_iterator.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
CompactionEventListener::CompactionListenerValueType fromInternalValueType(
|
||||
ValueType vt) {
|
||||
switch (vt) {
|
||||
case kTypeDeletion:
|
||||
return CompactionEventListener::CompactionListenerValueType::kDelete;
|
||||
case kTypeValue:
|
||||
return CompactionEventListener::CompactionListenerValueType::kValue;
|
||||
case kTypeMerge:
|
||||
return CompactionEventListener::CompactionListenerValueType::
|
||||
kMergeOperand;
|
||||
case kTypeSingleDeletion:
|
||||
return CompactionEventListener::CompactionListenerValueType::
|
||||
kSingleDelete;
|
||||
case kTypeRangeDeletion:
|
||||
return CompactionEventListener::CompactionListenerValueType::kRangeDelete;
|
||||
default:
|
||||
assert(false);
|
||||
return CompactionEventListener::CompactionListenerValueType::kInvalid;
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool expect_valid_internal_key, RangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction, const CompactionFilter* compaction_filter,
|
||||
CompactionEventListener* compaction_listener,
|
||||
const std::atomic<bool>* shutting_down)
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots,
|
||||
earliest_write_conflict_snapshot, env, expect_valid_internal_key,
|
||||
range_del_agg,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
expect_valid_internal_key, range_del_agg,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new CompactionProxy(compaction) : nullptr),
|
||||
compaction_filter, compaction_listener, shutting_down) {}
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
SequenceNumber /*last_sequence*/, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool expect_valid_internal_key, RangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter,
|
||||
CompactionEventListener* compaction_listener,
|
||||
const std::atomic<bool>* shutting_down)
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum)
|
||||
: input_(input),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
snapshots_(snapshots),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
env_(env),
|
||||
expect_valid_internal_key_(expect_valid_internal_key),
|
||||
range_del_agg_(range_del_agg),
|
||||
compaction_(std::move(compaction)),
|
||||
compaction_filter_(compaction_filter),
|
||||
#ifndef ROCKSDB_LITE
|
||||
compaction_listener_(compaction_listener),
|
||||
#endif // ROCKSDB_LITE
|
||||
shutting_down_(shutting_down),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
ignore_snapshots_(false),
|
||||
merge_out_iter_(merge_helper_) {
|
||||
current_user_key_sequence_(0),
|
||||
current_user_key_snapshot_(0),
|
||||
merge_out_iter_(merge_helper_),
|
||||
current_key_committed_(false) {
|
||||
assert(compaction_filter_ == nullptr || compaction_ != nullptr);
|
||||
bottommost_level_ =
|
||||
compaction_ == nullptr ? false : compaction_->bottommost_level();
|
||||
@@ -129,8 +113,8 @@ void CompactionIterator::Next() {
|
||||
if (merge_out_iter_.Valid()) {
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
bool valid_key __attribute__((__unused__)) =
|
||||
ParseInternalKey(key_, &ikey_);
|
||||
bool valid_key __attribute__((__unused__));
|
||||
valid_key = ParseInternalKey(key_, &ikey_);
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to be valid.
|
||||
assert(valid_key);
|
||||
@@ -164,6 +148,64 @@ void CompactionIterator::Next() {
|
||||
PrepareOutput();
|
||||
}
|
||||
|
||||
void CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
Slice* skip_until) {
|
||||
if (compaction_filter_ != nullptr &&
|
||||
(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex) &&
|
||||
(visible_at_tip_ || ignore_snapshots_ ||
|
||||
ikey_.sequence > latest_snapshot_ ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
latest_snapshot_))))) {
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
// replace the entry with a deletion marker.
|
||||
CompactionFilter::Decision filter;
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
CompactionFilter::ValueType value_type =
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
: CompactionFilter::ValueType::kBlobIndex;
|
||||
// Hack: pass internal key to BlobIndexCompactionFilter since it needs
|
||||
// to get sequence number.
|
||||
Slice& filter_key = ikey_.type == kTypeValue ? ikey_.user_key : key_;
|
||||
{
|
||||
StopWatchNano timer(env_, true);
|
||||
filter = compaction_filter_->FilterV2(
|
||||
compaction_->level(), filter_key, value_type, value_,
|
||||
&compaction_filter_value_, compaction_filter_skip_until_.rep());
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr ? timer.ElapsedNanos() : 0;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
|
||||
0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
filter = CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
|
||||
// no value associated with delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (filter == CompactionFilter::Decision::kChangeValue) {
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
*need_skip = true;
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
*skip_until = compaction_filter_skip_until_.Encode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionIterator::NextFromInput() {
|
||||
at_next_ = false;
|
||||
valid_ = false;
|
||||
@@ -218,70 +260,16 @@ void CompactionIterator::NextFromInput() {
|
||||
has_outputted_key_ = false;
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
current_key_committed_ =
|
||||
(snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->IsInSnapshot(ikey_.sequence, kMaxSequenceNumber));
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (compaction_listener_) {
|
||||
compaction_listener_->OnCompaction(compaction_->level(), ikey_.user_key,
|
||||
fromInternalValueType(ikey_.type),
|
||||
value_, ikey_.sequence, true);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// apply the compaction filter to the first occurrence of the user key
|
||||
if (compaction_filter_ != nullptr && ikey_.type == kTypeValue &&
|
||||
(visible_at_tip_ || ikey_.sequence > latest_snapshot_ ||
|
||||
ignore_snapshots_)) {
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
// replace the entry with a deletion marker.
|
||||
CompactionFilter::Decision filter;
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
{
|
||||
StopWatchNano timer(env_, true);
|
||||
filter = compaction_filter_->FilterV2(
|
||||
compaction_->level(), ikey_.user_key,
|
||||
CompactionFilter::ValueType::kValue, value_,
|
||||
&compaction_filter_value_, compaction_filter_skip_until_.rep());
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr ? timer.ElapsedNanos() : 0;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(),
|
||||
ikey_.user_key) <= 0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
filter = CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
|
||||
// no value associated with delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (filter == CompactionFilter::Decision::kChangeValue) {
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
need_skip = true;
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
skip_until = compaction_filter_skip_until_.Encode();
|
||||
}
|
||||
// Apply the compaction filter to the first committed version of the user
|
||||
// key.
|
||||
if (current_key_committed_) {
|
||||
InvokeFilterIfNeeded(&need_skip, &skip_until);
|
||||
}
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (compaction_listener_) {
|
||||
compaction_listener_->OnCompaction(compaction_->level(), ikey_.user_key,
|
||||
fromInternalValueType(ikey_.type),
|
||||
value_, ikey_.sequence, false);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Update the current key to reflect the new sequence number/type without
|
||||
// copying the user key.
|
||||
// TODO(rven): Compaction filter does not process keys in this path
|
||||
@@ -290,13 +278,33 @@ void CompactionIterator::NextFromInput() {
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
key_ = current_key_.GetInternalKey();
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
|
||||
// Note that newer version of a key is ordered before older versions. If a
|
||||
// newer version of a key is committed, so as the older version. No need
|
||||
// to query snapshot_checker_ in that case.
|
||||
if (UNLIKELY(!current_key_committed_)) {
|
||||
assert(snapshot_checker_ != nullptr);
|
||||
current_key_committed_ =
|
||||
snapshot_checker_->IsInSnapshot(ikey_.sequence, kMaxSequenceNumber);
|
||||
// Apply the compaction filter to the first committed version of the
|
||||
// user key.
|
||||
if (current_key_committed_) {
|
||||
InvokeFilterIfNeeded(&need_skip, &skip_until);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (UNLIKELY(!current_key_committed_)) {
|
||||
assert(snapshot_checker_ != nullptr);
|
||||
valid_ = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If there are no snapshots, then this kv affect visibility at tip.
|
||||
// Otherwise, search though all existing snapshots to find the earliest
|
||||
// snapshot that is affected by this kv.
|
||||
SequenceNumber last_sequence __attribute__((__unused__)) =
|
||||
current_user_key_sequence_;
|
||||
SequenceNumber last_sequence __attribute__((__unused__));
|
||||
last_sequence = current_user_key_sequence_;
|
||||
current_user_key_sequence_ = ikey_.sequence;
|
||||
SequenceNumber last_snapshot = current_user_key_snapshot_;
|
||||
SequenceNumber prev_snapshot = 0; // 0 means no previous snapshot
|
||||
@@ -361,7 +369,10 @@ void CompactionIterator::NextFromInput() {
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot) {
|
||||
if (prev_snapshot == 0 || next_ikey.sequence > prev_snapshot ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(!snapshot_checker_->IsInSnapshot(next_ikey.sequence,
|
||||
prev_snapshot)))) {
|
||||
if (next_ikey.type == kTypeSingleDeletion) {
|
||||
// We encountered two SingleDeletes in a row. This could be due to
|
||||
// unexpected user input.
|
||||
@@ -372,8 +383,12 @@ void CompactionIterator::NextFromInput() {
|
||||
// input_->Next().
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
++iter_stats_.num_single_del_mismatch;
|
||||
} else if ((ikey_.sequence <= earliest_write_conflict_snapshot_) ||
|
||||
has_outputted_key_) {
|
||||
} else if (has_outputted_key_ ||
|
||||
(ikey_.sequence <= earliest_write_conflict_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(
|
||||
ikey_.sequence,
|
||||
earliest_write_conflict_snapshot_))))) {
|
||||
// Found a matching value, we can drop the single delete and the
|
||||
// value. It is safe to drop both records since we've already
|
||||
// outputted a key in this snapshot, or there is no earlier
|
||||
@@ -421,6 +436,9 @@ void CompactionIterator::NextFromInput() {
|
||||
// comparison, so the value of has_current_user_key does not matter.
|
||||
has_current_user_key_ = false;
|
||||
if (compaction_ != nullptr && ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
// Key doesn't exist outside of this range.
|
||||
@@ -454,6 +472,10 @@ void CompactionIterator::NextFromInput() {
|
||||
input_->Next();
|
||||
} else if (compaction_ != nullptr && ikey_.type == kTypeDeletion &&
|
||||
ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->IsInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
// TODO(noetzli): This is the only place where we use compaction_
|
||||
@@ -470,6 +492,11 @@ void CompactionIterator::NextFromInput() {
|
||||
//
|
||||
// Note: Dropping this Delete will not affect TransactionDB
|
||||
// write-conflict checking since it is earlier than any snapshot.
|
||||
//
|
||||
// It seems that we can also drop deletion later than earliest snapshot
|
||||
// given that:
|
||||
// (1) The deletion is earlier than earliest_write_conflict_snapshot, and
|
||||
// (2) No value exist earlier than the deletion.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
@@ -499,8 +526,8 @@ void CompactionIterator::NextFromInput() {
|
||||
// These will be correctly set below.
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
bool valid_key __attribute__((__unused__)) =
|
||||
ParseInternalKey(key_, &ikey_);
|
||||
bool valid_key __attribute__((__unused__));
|
||||
valid_key = ParseInternalKey(key_, &ikey_);
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to valid.
|
||||
assert(valid_key);
|
||||
@@ -550,11 +577,18 @@ void CompactionIterator::PrepareOutput() {
|
||||
// and the earliest snapshot is larger than this seqno
|
||||
// and the userkey differs from the last userkey in compaction
|
||||
// then we can squash the seqno to zero.
|
||||
|
||||
//
|
||||
// This is safe for TransactionDB write-conflict checking since transactions
|
||||
// only care about sequence number larger than any active snapshots.
|
||||
if ((compaction_ != nullptr && !compaction_->allow_ingest_behind()) &&
|
||||
//
|
||||
// Can we do the same for levels above bottom level as long as
|
||||
// KeyNotExistsBeyondOutputLevel() return true?
|
||||
if ((compaction_ != nullptr &&
|
||||
!compaction_->allow_ingest_behind()) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
bottommost_level_ && valid_ && ikey_.sequence <= earliest_snapshot_ &&
|
||||
(snapshot_checker_ == nullptr || LIKELY(snapshot_checker_->IsInSnapshot(
|
||||
ikey_.sequence, earliest_snapshot_))) &&
|
||||
ikey_.type != kTypeMerge &&
|
||||
!cmp_->Equal(compaction_->GetLargestUserKey(), ikey_.user_key)) {
|
||||
assert(ikey_.type != kTypeDeletion && ikey_.type != kTypeSingleDeletion);
|
||||
@@ -566,10 +600,11 @@ void CompactionIterator::PrepareOutput() {
|
||||
inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
SequenceNumber in, SequenceNumber* prev_snapshot) {
|
||||
assert(snapshots_->size());
|
||||
SequenceNumber prev __attribute__((__unused__)) = kMaxSequenceNumber;
|
||||
SequenceNumber prev = kMaxSequenceNumber;
|
||||
for (const auto cur : *snapshots_) {
|
||||
assert(prev == kMaxSequenceNumber || prev <= cur);
|
||||
if (cur >= in) {
|
||||
if (cur >= in && (snapshot_checker_ == nullptr ||
|
||||
snapshot_checker_->IsInSnapshot(in, cur))) {
|
||||
*prev_snapshot = prev == kMaxSequenceNumber ? 0 : prev;
|
||||
return cur;
|
||||
}
|
||||
@@ -580,4 +615,11 @@ inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
return kMaxSequenceNumber;
|
||||
}
|
||||
|
||||
// used in 2 places - prevents deletion markers to be dropped if they may be
|
||||
// needed and disables seqnum zero-out in PrepareOutput for recent keys.
|
||||
inline bool CompactionIterator::ikeyNotNeededForIncrementalSnapshot() {
|
||||
return (!compaction_->preserve_deletes()) ||
|
||||
(ikey_.sequence < preserve_deletes_seqnum_);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
+27
-12
@@ -14,13 +14,12 @@
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class CompactionEventListener;
|
||||
|
||||
class CompactionIterator {
|
||||
public:
|
||||
// A wrapper around Compaction. Has a much smaller interface, only what
|
||||
@@ -31,7 +30,7 @@ class CompactionIterator {
|
||||
: compaction_(compaction) {}
|
||||
|
||||
virtual ~CompactionProxy() = default;
|
||||
virtual int level(size_t compaction_input_level = 0) const {
|
||||
virtual int level(size_t /*compaction_input_level*/ = 0) const {
|
||||
return compaction_->level();
|
||||
}
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
@@ -48,6 +47,9 @@ class CompactionIterator {
|
||||
virtual bool allow_ingest_behind() const {
|
||||
return compaction_->immutable_cf_options()->allow_ingest_behind;
|
||||
}
|
||||
virtual bool preserve_deletes() const {
|
||||
return compaction_->immutable_cf_options()->preserve_deletes;
|
||||
}
|
||||
|
||||
protected:
|
||||
CompactionProxy() = default;
|
||||
@@ -59,25 +61,27 @@ class CompactionIterator {
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool expect_valid_internal_key,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
CompactionEventListener* compaction_listener = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr);
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0);
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool expect_valid_internal_key,
|
||||
RangeDelAggregator* range_del_agg,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
CompactionEventListener* compaction_listener = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr);
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -111,6 +115,9 @@ class CompactionIterator {
|
||||
// compression.
|
||||
void PrepareOutput();
|
||||
|
||||
// Invoke compaction filter if needed.
|
||||
void InvokeFilterIfNeeded(bool* need_skip, Slice* skip_until);
|
||||
|
||||
// Given a sequence number, return the sequence number of the
|
||||
// earliest snapshot that this sequence number is visible in.
|
||||
// The snapshots themselves are arranged in ascending order of
|
||||
@@ -120,20 +127,24 @@ class CompactionIterator {
|
||||
inline SequenceNumber findEarliestVisibleSnapshot(
|
||||
SequenceNumber in, SequenceNumber* prev_snapshot);
|
||||
|
||||
// Checks whether the currently seen ikey_ is needed for
|
||||
// incremental (differential) snapshot and hence can't be dropped
|
||||
// or seqnum be zero-ed out even if all other conditions for it are met.
|
||||
inline bool ikeyNotNeededForIncrementalSnapshot();
|
||||
|
||||
InternalIterator* input_;
|
||||
const Comparator* cmp_;
|
||||
MergeHelper* merge_helper_;
|
||||
const std::vector<SequenceNumber>* snapshots_;
|
||||
const SequenceNumber earliest_write_conflict_snapshot_;
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
Env* env_;
|
||||
bool expect_valid_internal_key_;
|
||||
RangeDelAggregator* range_del_agg_;
|
||||
std::unique_ptr<CompactionProxy> compaction_;
|
||||
const CompactionFilter* compaction_filter_;
|
||||
#ifndef ROCKSDB_LITE
|
||||
CompactionEventListener* compaction_listener_;
|
||||
#endif // ROCKSDB_LITE
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
bool bottommost_level_;
|
||||
bool valid_ = false;
|
||||
bool visible_at_tip_;
|
||||
@@ -189,6 +200,10 @@ class CompactionIterator {
|
||||
std::vector<size_t> level_ptrs_;
|
||||
CompactionIterationStats iter_stats_;
|
||||
|
||||
// Used to avoid purging uncommitted values. The application can specify
|
||||
// uncommitted values by providing a SnapshotChecker object.
|
||||
bool current_key_committed_;
|
||||
|
||||
bool IsShuttingDown() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
return shutting_down_ && shutting_down_->load(std::memory_order_relaxed);
|
||||
|
||||
+437
-51
@@ -9,23 +9,25 @@
|
||||
#include <vector>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// Expects no merging attempts.
|
||||
class NoMergingMergeOp : public MergeOperator {
|
||||
public:
|
||||
bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
|
||||
MergeOperationOutput* /*merge_out*/) const override {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
bool PartialMergeMulti(const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
bool PartialMergeMulti(const Slice& /*key*/,
|
||||
const std::deque<Slice>& /*operand_list*/,
|
||||
std::string* /*new_value*/,
|
||||
Logger* /*logger*/) const override {
|
||||
ADD_FAILURE();
|
||||
return false;
|
||||
}
|
||||
@@ -39,9 +41,9 @@ class NoMergingMergeOp : public MergeOperator {
|
||||
// Always returns Decition::kRemove.
|
||||
class StallingFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType t,
|
||||
const Slice& existing_value, std::string* new_value,
|
||||
std::string* skip_until) const override {
|
||||
Decision FilterV2(int /*level*/, const Slice& key, ValueType /*type*/,
|
||||
const Slice& /*existing_value*/, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
int k = std::atoi(key.ToString().c_str());
|
||||
last_seen.store(k);
|
||||
while (k >= stall_at.load()) {
|
||||
@@ -72,6 +74,18 @@ class StallingFilter : public CompactionFilter {
|
||||
mutable std::atomic<int> last_seen{0};
|
||||
};
|
||||
|
||||
// Compaction filter that filter out all keys.
|
||||
class FilterAllKeysCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
Decision FilterV2(int /*level*/, const Slice& /*key*/, ValueType /*type*/,
|
||||
const Slice& /*existing_value*/, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
return Decision::kRemove;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "AllKeysCompactionFilter"; }
|
||||
};
|
||||
|
||||
class LoggingForwardVectorIterator : public InternalIterator {
|
||||
public:
|
||||
struct Action {
|
||||
@@ -112,7 +126,7 @@ class LoggingForwardVectorIterator : public InternalIterator {
|
||||
keys_.begin();
|
||||
}
|
||||
|
||||
virtual void SeekForPrev(const Slice& target) override { assert(false); }
|
||||
virtual void SeekForPrev(const Slice& /*target*/) override { assert(false); }
|
||||
|
||||
virtual void Next() override {
|
||||
assert(Valid());
|
||||
@@ -144,71 +158,149 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
public:
|
||||
FakeCompaction() = default;
|
||||
|
||||
virtual int level(size_t compaction_input_level) const { return 0; }
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const {
|
||||
return key_not_exists_beyond_output_level;
|
||||
virtual int level(size_t /*compaction_input_level*/) const override {
|
||||
return 0;
|
||||
}
|
||||
virtual bool bottommost_level() const { return false; }
|
||||
virtual int number_levels() const { return 1; }
|
||||
virtual Slice GetLargestUserKey() const {
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& /*user_key*/,
|
||||
std::vector<size_t>* /*level_ptrs*/) const override {
|
||||
return is_bottommost_level || key_not_exists_beyond_output_level;
|
||||
}
|
||||
virtual bool bottommost_level() const override { return is_bottommost_level; }
|
||||
virtual int number_levels() const override { return 1; }
|
||||
virtual Slice GetLargestUserKey() const override {
|
||||
return "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
}
|
||||
virtual bool allow_ingest_behind() const { return false; }
|
||||
virtual bool allow_ingest_behind() const override { return false; }
|
||||
|
||||
virtual bool preserve_deletes() const override { return false; }
|
||||
|
||||
bool key_not_exists_beyond_output_level = false;
|
||||
|
||||
bool is_bottommost_level = false;
|
||||
};
|
||||
|
||||
class CompactionIteratorTest : public testing::Test {
|
||||
// A simplifed snapshot checker which assumes each snapshot has a global
|
||||
// last visible sequence.
|
||||
class TestSnapshotChecker : public SnapshotChecker {
|
||||
public:
|
||||
explicit TestSnapshotChecker(
|
||||
SequenceNumber last_committed_sequence,
|
||||
const std::unordered_map<SequenceNumber, SequenceNumber>& snapshots = {})
|
||||
: last_committed_sequence_(last_committed_sequence),
|
||||
snapshots_(snapshots) {}
|
||||
|
||||
bool IsInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot_seq) const override {
|
||||
if (snapshot_seq == kMaxSequenceNumber) {
|
||||
return seq <= last_committed_sequence_;
|
||||
}
|
||||
assert(snapshots_.count(snapshot_seq) > 0);
|
||||
return seq <= snapshots_.at(snapshot_seq);
|
||||
}
|
||||
|
||||
private:
|
||||
SequenceNumber last_committed_sequence_;
|
||||
// A map of valid snapshot to last visible sequence to the snapshot.
|
||||
std::unordered_map<SequenceNumber, SequenceNumber> snapshots_;
|
||||
};
|
||||
|
||||
// Test param:
|
||||
// bool: whether to pass snapshot_checker to compaction iterator.
|
||||
class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
public:
|
||||
CompactionIteratorTest()
|
||||
: cmp_(BytewiseComparator()), icmp_(cmp_), snapshots_({}) {}
|
||||
|
||||
void InitIterators(const std::vector<std::string>& ks,
|
||||
const std::vector<std::string>& vs,
|
||||
const std::vector<std::string>& range_del_ks,
|
||||
const std::vector<std::string>& range_del_vs,
|
||||
SequenceNumber last_sequence,
|
||||
MergeOperator* merge_op = nullptr,
|
||||
CompactionFilter* filter = nullptr) {
|
||||
void InitIterators(
|
||||
const std::vector<std::string>& ks, const std::vector<std::string>& vs,
|
||||
const std::vector<std::string>& range_del_ks,
|
||||
const std::vector<std::string>& range_del_vs,
|
||||
SequenceNumber last_sequence,
|
||||
SequenceNumber last_committed_sequence = kMaxSequenceNumber,
|
||||
MergeOperator* merge_op = nullptr, CompactionFilter* filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
std::unique_ptr<InternalIterator> range_del_iter(
|
||||
new test::VectorIterator(range_del_ks, range_del_vs));
|
||||
range_del_agg_.reset(new RangeDelAggregator(icmp_, snapshots_));
|
||||
ASSERT_OK(range_del_agg_->AddTombstones(std::move(range_del_iter)));
|
||||
|
||||
std::unique_ptr<CompactionIterator::CompactionProxy> compaction;
|
||||
if (filter) {
|
||||
if (filter || bottommost_level) {
|
||||
compaction_proxy_ = new FakeCompaction();
|
||||
compaction_proxy_->is_bottommost_level = bottommost_level;
|
||||
compaction.reset(compaction_proxy_);
|
||||
}
|
||||
bool use_snapshot_checker = UseSnapshotChecker() || GetParam();
|
||||
if (use_snapshot_checker || last_committed_sequence < kMaxSequenceNumber) {
|
||||
snapshot_checker_.reset(
|
||||
new TestSnapshotChecker(last_committed_sequence, snapshot_map_));
|
||||
}
|
||||
merge_helper_.reset(
|
||||
new MergeHelper(Env::Default(), cmp_, merge_op, filter, nullptr, false,
|
||||
0 /*latest_snapshot*/, snapshot_checker_.get(),
|
||||
0 /*level*/, nullptr /*statistics*/, &shutting_down_));
|
||||
|
||||
merge_helper_.reset(new MergeHelper(Env::Default(), cmp_, merge_op, filter,
|
||||
nullptr, false, 0, 0, nullptr,
|
||||
&shutting_down_));
|
||||
iter_.reset(new LoggingForwardVectorIterator(ks, vs));
|
||||
iter_->SeekToFirst();
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
iter_.get(), cmp_, merge_helper_.get(), last_sequence, &snapshots_,
|
||||
kMaxSequenceNumber, Env::Default(), false, range_del_agg_.get(),
|
||||
std::move(compaction), filter, nullptr, &shutting_down_));
|
||||
earliest_write_conflict_snapshot, snapshot_checker_.get(),
|
||||
Env::Default(), false, range_del_agg_.get(), std::move(compaction),
|
||||
filter, &shutting_down_));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot) { snapshots_.push_back(snapshot); }
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
SequenceNumber last_visible_seq = kMaxSequenceNumber) {
|
||||
snapshots_.push_back(snapshot);
|
||||
snapshot_map_[snapshot] = last_visible_seq;
|
||||
}
|
||||
|
||||
virtual bool UseSnapshotChecker() const { return false; }
|
||||
|
||||
void RunTest(
|
||||
const std::vector<std::string>& input_keys,
|
||||
const std::vector<std::string>& input_values,
|
||||
const std::vector<std::string>& expected_keys,
|
||||
const std::vector<std::string>& expected_values,
|
||||
SequenceNumber last_committed_seq = kMaxSequenceNumber,
|
||||
MergeOperator* merge_operator = nullptr,
|
||||
CompactionFilter* compaction_filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
InitIterators(input_keys, input_values, {}, {}, kMaxSequenceNumber,
|
||||
last_committed_seq, merge_operator, compaction_filter,
|
||||
bottommost_level, earliest_write_conflict_snapshot);
|
||||
c_iter_->SeekToFirst();
|
||||
for (size_t i = 0; i < expected_keys.size(); i++) {
|
||||
std::string info = "i = " + ToString(i);
|
||||
ASSERT_TRUE(c_iter_->Valid()) << info;
|
||||
ASSERT_OK(c_iter_->status()) << info;
|
||||
ASSERT_EQ(expected_keys[i], c_iter_->key().ToString()) << info;
|
||||
ASSERT_EQ(expected_values[i], c_iter_->value().ToString()) << info;
|
||||
c_iter_->Next();
|
||||
}
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
const Comparator* cmp_;
|
||||
const InternalKeyComparator icmp_;
|
||||
std::vector<SequenceNumber> snapshots_;
|
||||
// A map of valid snapshot to last visible sequence to the snapshot.
|
||||
std::unordered_map<SequenceNumber, SequenceNumber> snapshot_map_;
|
||||
std::unique_ptr<MergeHelper> merge_helper_;
|
||||
std::unique_ptr<LoggingForwardVectorIterator> iter_;
|
||||
std::unique_ptr<CompactionIterator> c_iter_;
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg_;
|
||||
std::unique_ptr<SnapshotChecker> snapshot_checker_;
|
||||
std::atomic<bool> shutting_down_{false};
|
||||
FakeCompaction* compaction_proxy_;
|
||||
};
|
||||
|
||||
// It is possible that the output of the compaction iterator is empty even if
|
||||
// the input is not.
|
||||
TEST_F(CompactionIteratorTest, EmptyResult) {
|
||||
TEST_P(CompactionIteratorTest, EmptyResult) {
|
||||
InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 3, kTypeValue)},
|
||||
{"", "val"}, {}, {}, 5);
|
||||
@@ -218,7 +310,7 @@ TEST_F(CompactionIteratorTest, EmptyResult) {
|
||||
|
||||
// If there is a corruption after a single deletion, the corrupted key should
|
||||
// be preserved.
|
||||
TEST_F(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
|
||||
TEST_P(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
|
||||
InitIterators({test::KeyStr("a", 5, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 3, kTypeValue, true),
|
||||
test::KeyStr("b", 10, kTypeValue)},
|
||||
@@ -237,7 +329,7 @@ TEST_F(CompactionIteratorTest, CorruptionAfterSingleDeletion) {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, SimpleRangeDeletion) {
|
||||
TEST_P(CompactionIteratorTest, SimpleRangeDeletion) {
|
||||
InitIterators({test::KeyStr("morning", 5, kTypeValue),
|
||||
test::KeyStr("morning", 2, kTypeValue),
|
||||
test::KeyStr("night", 3, kTypeValue)},
|
||||
@@ -253,7 +345,7 @@ TEST_F(CompactionIteratorTest, SimpleRangeDeletion) {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
TEST_P(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
AddSnapshot(10);
|
||||
std::vector<std::string> ks1;
|
||||
ks1.push_back(test::KeyStr("ma", 28, kTypeRangeDeletion));
|
||||
@@ -274,11 +366,11 @@ TEST_F(CompactionIteratorTest, RangeDeletionWithSnapshots) {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
TEST_P(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
class Filter : public CompactionFilter {
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType t,
|
||||
virtual Decision FilterV2(int /*level*/, const Slice& key, ValueType t,
|
||||
const Slice& existing_value,
|
||||
std::string* new_value,
|
||||
std::string* /*new_value*/,
|
||||
std::string* skip_until) const override {
|
||||
std::string k = key.ToString();
|
||||
std::string v = existing_value.ToString();
|
||||
@@ -349,7 +441,7 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
test::KeyStr("j", 99, kTypeValue)},
|
||||
{"av50", "am45", "bv60", "bv40", "cv35", "dm70", "em71", "fm65", "fm30",
|
||||
"fv25", "gv90", "hv91", "im95", "jv99"},
|
||||
{}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
{}, {}, kMaxSequenceNumber, kMaxSequenceNumber, &merge_op, &filter);
|
||||
|
||||
// Compaction should output just "a", "e" and "h" keys.
|
||||
c_iter_->SeekToFirst();
|
||||
@@ -384,13 +476,14 @@ TEST_F(CompactionIteratorTest, CompactionFilterSkipUntil) {
|
||||
ASSERT_EQ(expected_actions, iter_->log);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, ShuttingDownInFilter) {
|
||||
TEST_P(CompactionIteratorTest, ShuttingDownInFilter) {
|
||||
NoMergingMergeOp merge_op;
|
||||
StallingFilter filter;
|
||||
InitIterators(
|
||||
{test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeValue),
|
||||
test::KeyStr("3", 3, kTypeValue), test::KeyStr("4", 4, kTypeValue)},
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, kMaxSequenceNumber,
|
||||
&merge_op, &filter);
|
||||
// Don't leave tombstones (kTypeDeletion) for filtered keys.
|
||||
compaction_proxy_->key_not_exists_beyond_output_level = true;
|
||||
|
||||
@@ -421,13 +514,14 @@ TEST_F(CompactionIteratorTest, ShuttingDownInFilter) {
|
||||
|
||||
// Same as ShuttingDownInFilter, but shutdown happens during filter call for
|
||||
// a merge operand, not for a value.
|
||||
TEST_F(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
TEST_P(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
NoMergingMergeOp merge_op;
|
||||
StallingFilter filter;
|
||||
InitIterators(
|
||||
{test::KeyStr("1", 1, kTypeValue), test::KeyStr("2", 2, kTypeMerge),
|
||||
test::KeyStr("3", 3, kTypeMerge), test::KeyStr("4", 4, kTypeValue)},
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, &merge_op, &filter);
|
||||
{"v1", "v2", "v3", "v4"}, {}, {}, kMaxSequenceNumber, kMaxSequenceNumber,
|
||||
&merge_op, &filter);
|
||||
compaction_proxy_->key_not_exists_beyond_output_level = true;
|
||||
|
||||
std::atomic<bool> seek_done{false};
|
||||
@@ -455,12 +549,12 @@ TEST_F(CompactionIteratorTest, ShuttingDownInMerge) {
|
||||
EXPECT_EQ(2, filter.last_seen.load());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorTest, SingleMergeOperand) {
|
||||
TEST_P(CompactionIteratorTest, SingleMergeOperand) {
|
||||
class Filter : public CompactionFilter {
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType t,
|
||||
virtual Decision FilterV2(int /*level*/, const Slice& key, ValueType t,
|
||||
const Slice& existing_value,
|
||||
std::string* new_value,
|
||||
std::string* skip_until) const override {
|
||||
std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
std::string k = key.ToString();
|
||||
std::string v = existing_value.ToString();
|
||||
|
||||
@@ -511,7 +605,7 @@ TEST_F(CompactionIteratorTest, SingleMergeOperand) {
|
||||
bool PartialMergeMulti(const Slice& key,
|
||||
const std::deque<Slice>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
Logger* /*logger*/) const override {
|
||||
std::string string_key = key.ToString();
|
||||
EXPECT_TRUE(string_key == "a" || string_key == "b");
|
||||
|
||||
@@ -547,7 +641,7 @@ TEST_F(CompactionIteratorTest, SingleMergeOperand) {
|
||||
// c should invoke FullMerge due to kTypeValue at the beginning.
|
||||
test::KeyStr("c", 90, kTypeMerge), test::KeyStr("c", 80, kTypeValue)},
|
||||
{"av1", "bv2", "bv1", "cv2", "cv1"}, {}, {}, kMaxSequenceNumber,
|
||||
&merge_op, &filter);
|
||||
kMaxSequenceNumber, &merge_op, &filter);
|
||||
|
||||
c_iter_->SeekToFirst();
|
||||
ASSERT_TRUE(c_iter_->Valid());
|
||||
@@ -560,6 +654,298 @@ TEST_F(CompactionIteratorTest, SingleMergeOperand) {
|
||||
ASSERT_EQ("cv1cv2", c_iter_->value().ToString());
|
||||
}
|
||||
|
||||
// In bottommost level, values earlier than earliest snapshot can be output
|
||||
// with sequence = 0.
|
||||
TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue)},
|
||||
{"v1", "v2"},
|
||||
{test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue)},
|
||||
{"v1", "v2"}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// In bottommost level, deletions earlier than earliest snapshot can be removed
|
||||
// permanently.
|
||||
TEST_P(CompactionIteratorTest, RemoveDeletionAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeDeletion),
|
||||
test::KeyStr("b", 2, kTypeDeletion)},
|
||||
{"", ""}, {test::KeyStr("b", 2, kTypeDeletion)}, {""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// In bottommost level, single deletions earlier than earliest snapshot can be
|
||||
// removed permanently.
|
||||
TEST_P(CompactionIteratorTest, RemoveSingleDeletionAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeSingleDeletion),
|
||||
test::KeyStr("b", 2, kTypeSingleDeletion)},
|
||||
{"", ""}, {test::KeyStr("b", 2, kTypeSingleDeletion)}, {""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionIteratorTestInstance, CompactionIteratorTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
// Tests how CompactionIterator work together with SnapshotChecker.
|
||||
class CompactionIteratorWithSnapshotCheckerTest
|
||||
: public CompactionIteratorTest {
|
||||
public:
|
||||
bool UseSnapshotChecker() const override { return true; }
|
||||
};
|
||||
|
||||
// Uncommitted keys (keys with seq > last_committed_seq) should be output as-is
|
||||
// while committed version of these keys should get compacted as usual.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_Value) {
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 3, kTypeValue), test::KeyStr("foo", 2, kTypeValue),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 3, kTypeValue), test::KeyStr("foo", 2, kTypeValue)},
|
||||
{"v3", "v2"}, 2 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_Deletion) {
|
||||
RunTest({test::KeyStr("foo", 2, kTypeDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("foo", 2, kTypeDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"", "v1"}, 1 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_Merge) {
|
||||
auto merge_op = MergeOperators::CreateStringAppendOperator();
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeMerge),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeValue)},
|
||||
{"v3", "v1,v2"}, 2 /*last_committed_seq*/, merge_op.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_SingleDelete) {
|
||||
RunTest({test::KeyStr("foo", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("foo", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"", "v1"}, 1 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
PreserveUncommittedKeys_BlobIndex) {
|
||||
RunTest({test::KeyStr("foo", 3, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 2, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 1, kTypeBlobIndex)},
|
||||
{"v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 3, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 2, kTypeBlobIndex)},
|
||||
{"v3", "v2"}, 2 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
// Test compaction iterator dedup keys visible to the same snapshot.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Value) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 3, kTypeValue),
|
||||
test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 3, kTypeValue),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "v3", "v1"}, 3 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Deletion) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 4, kTypeValue),
|
||||
test::KeyStr("foo", 3, kTypeDeletion),
|
||||
test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "", "v2", "v1"},
|
||||
{test::KeyStr("foo", 4, kTypeValue),
|
||||
test::KeyStr("foo", 3, kTypeDeletion),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "", "v1"}, 3 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_Merge) {
|
||||
AddSnapshot(2, 1);
|
||||
AddSnapshot(4, 3);
|
||||
auto merge_op = MergeOperators::CreateStringAppendOperator();
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 5, kTypeMerge), test::KeyStr("foo", 4, kTypeMerge),
|
||||
test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 2, kTypeMerge),
|
||||
test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v5", "v4", "v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 5, kTypeMerge), test::KeyStr("foo", 4, kTypeMerge),
|
||||
test::KeyStr("foo", 3, kTypeMerge), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v5", "v4", "v2,v3", "v1"}, 4 /*last_committed_seq*/, merge_op.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
DedupSameSnapshot_SingleDeletion) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest(
|
||||
{test::KeyStr("foo", 4, kTypeValue),
|
||||
test::KeyStr("foo", 3, kTypeSingleDeletion),
|
||||
test::KeyStr("foo", 2, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "", "v2", "v1"},
|
||||
{test::KeyStr("foo", 4, kTypeValue), test::KeyStr("foo", 1, kTypeValue)},
|
||||
{"v4", "v1"}, 3 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, DedupSameSnapshot_BlobIndex) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest({test::KeyStr("foo", 4, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 3, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 2, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 1, kTypeBlobIndex)},
|
||||
{"v4", "v3", "v2", "v1"},
|
||||
{test::KeyStr("foo", 4, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 3, kTypeBlobIndex),
|
||||
test::KeyStr("foo", 1, kTypeBlobIndex)},
|
||||
{"v4", "v3", "v1"}, 3 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
// At bottom level, sequence numbers can be zero out, and deletions can be
|
||||
// removed, but only when they are visible to earliest snapshot.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotZeroOutSequenceIfNotVisibleToEarliestSnapshot) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue),
|
||||
test::KeyStr("c", 3, kTypeValue)},
|
||||
{"v1", "v2", "v3"},
|
||||
{test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue),
|
||||
test::KeyStr("c", 3, kTypeValue)},
|
||||
{"v1", "v2", "v3"}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveDeletionIfNotVisibleToEarliestSnapshot) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest(
|
||||
{test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 2, kTypeDeletion),
|
||||
test::KeyStr("c", 3, kTypeDeletion)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 2, kTypeDeletion),
|
||||
test::KeyStr("c", 3, kTypeDeletion)},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveSingleDeletionIfNotVisibleToEarliestSnapshot) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeSingleDeletion),
|
||||
test::KeyStr("b", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("c", 3, kTypeSingleDeletion)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("c", 3, kTypeSingleDeletion)},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// Single delete should not cancel out values that not visible to the
|
||||
// same set of snapshots
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
SingleDeleteAcrossSnapshotBoundary) {
|
||||
AddSnapshot(2, 1);
|
||||
RunTest({test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", "v1"}, 2 /*last_committed_seq*/);
|
||||
}
|
||||
|
||||
// Single delete should be kept in case it is not visible to the
|
||||
// earliest write conflict snapshot. If a single delete is kept for this reason,
|
||||
// corresponding value can be trimmed to save space.
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
KeepSingleDeletionForWriteConflictChecking) {
|
||||
AddSnapshot(2, 0);
|
||||
RunTest({test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", ""}, 2 /*last_committed_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, false /*bottommost_level*/,
|
||||
2 /*earliest_write_conflict_snapshot*/);
|
||||
}
|
||||
|
||||
// Compaction filter should keep uncommitted key as-is, and
|
||||
// * Convert the latest velue to deletion, and/or
|
||||
// * if latest value is a merge, apply filter to all suequent merges.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Value) {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
new FilterAllKeysCompactionFilter());
|
||||
RunTest(
|
||||
{test::KeyStr("a", 2, kTypeValue), test::KeyStr("a", 1, kTypeValue),
|
||||
test::KeyStr("b", 3, kTypeValue), test::KeyStr("c", 1, kTypeValue)},
|
||||
{"v2", "v1", "v3", "v4"},
|
||||
{test::KeyStr("a", 2, kTypeValue), test::KeyStr("a", 1, kTypeDeletion),
|
||||
test::KeyStr("b", 3, kTypeValue), test::KeyStr("c", 1, kTypeDeletion)},
|
||||
{"v2", "", "v3", ""}, 1 /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, compaction_filter.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Deletion) {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
new FilterAllKeysCompactionFilter());
|
||||
RunTest(
|
||||
{test::KeyStr("a", 2, kTypeDeletion), test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", "v1"},
|
||||
{test::KeyStr("a", 2, kTypeDeletion),
|
||||
test::KeyStr("a", 1, kTypeDeletion)},
|
||||
{"", ""}, 1 /*last_committed_seq*/, nullptr /*merge_operator*/,
|
||||
compaction_filter.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
CompactionFilter_PartialMerge) {
|
||||
std::shared_ptr<MergeOperator> merge_op =
|
||||
MergeOperators::CreateStringAppendOperator();
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
new FilterAllKeysCompactionFilter());
|
||||
RunTest({test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 2, kTypeMerge),
|
||||
test::KeyStr("a", 1, kTypeMerge)},
|
||||
{"v3", "v2", "v1"}, {test::KeyStr("a", 3, kTypeMerge)}, {"v3"},
|
||||
2 /*last_committed_seq*/, merge_op.get(), compaction_filter.get());
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_FullMerge) {
|
||||
std::shared_ptr<MergeOperator> merge_op =
|
||||
MergeOperators::CreateStringAppendOperator();
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
new FilterAllKeysCompactionFilter());
|
||||
RunTest(
|
||||
{test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 2, kTypeMerge),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"v3", "v2", "v1"},
|
||||
{test::KeyStr("a", 3, kTypeMerge), test::KeyStr("a", 1, kTypeDeletion)},
|
||||
{"v3", ""}, 2 /*last_committed_seq*/, merge_op.get(),
|
||||
compaction_filter.get());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+98
-63
@@ -38,7 +38,6 @@
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "port/likely.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -264,15 +263,16 @@ void CompactionJob::AggregateStatistics() {
|
||||
|
||||
CompactionJob::CompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const EnvOptions& env_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
const EnvOptions env_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, Status* db_bg_error,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats, const std::string& dbname,
|
||||
CompactionJobStats* compaction_job_stats)
|
||||
const SnapshotChecker* snapshot_checker, std::shared_ptr<Cache> table_cache,
|
||||
EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats)
|
||||
: job_id_(job_id),
|
||||
compact_(new CompactionState(compaction)),
|
||||
compaction_job_stats_(compaction_job_stats),
|
||||
@@ -281,8 +281,11 @@ CompactionJob::CompactionJob(
|
||||
db_options_(db_options),
|
||||
env_options_(env_options),
|
||||
env_(db_options.env),
|
||||
env_optiosn_for_read_(
|
||||
env_->OptimizeForCompactionTableRead(env_options, db_options_)),
|
||||
versions_(versions),
|
||||
shutting_down_(shutting_down),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
log_buffer_(log_buffer),
|
||||
db_directory_(db_directory),
|
||||
output_directory_(output_directory),
|
||||
@@ -291,10 +294,13 @@ CompactionJob::CompactionJob(
|
||||
db_bg_error_(db_bg_error),
|
||||
existing_snapshots_(std::move(existing_snapshots)),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
table_cache_(std::move(table_cache)),
|
||||
event_logger_(event_logger),
|
||||
bottommost_level_(false),
|
||||
paranoid_file_checks_(paranoid_file_checks),
|
||||
measure_io_stats_(measure_io_stats) {
|
||||
measure_io_stats_(measure_io_stats),
|
||||
write_hint_(Env::WLTH_NOT_SET) {
|
||||
assert(log_buffer_ != nullptr);
|
||||
const auto* cfd = compact_->compaction->column_family_data();
|
||||
ThreadStatusUtil::SetColumnFamily(cfd, cfd->ioptions()->env,
|
||||
@@ -365,6 +371,8 @@ void CompactionJob::Prepare() {
|
||||
assert(c->column_family_data()->current()->storage_info()
|
||||
->NumLevelFiles(compact_->compaction->level()) > 0);
|
||||
|
||||
write_hint_ = c->column_family_data()->CalculateSSTWriteHint(
|
||||
c->output_level());
|
||||
// Is this compaction producing files at the bottommost level?
|
||||
bottommost_level_ = c->bottommost_level();
|
||||
|
||||
@@ -617,7 +625,8 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
"[%s] compacted to: %s, MB/sec: %.1f rd, %.1f wr, level %d, "
|
||||
"files in(%d, %d) out(%d) "
|
||||
"MB in(%.1f, %.1f) out(%.1f), read-write-amplify(%.1f) "
|
||||
"write-amplify(%.1f) %s, records in: %d, records dropped: %d\n",
|
||||
"write-amplify(%.1f) %s, records in: %" PRIu64
|
||||
", records dropped: %" PRIu64 " output_compression: %s\n",
|
||||
cfd->GetName().c_str(), vstorage->LevelSummary(&tmp), bytes_read_per_sec,
|
||||
bytes_written_per_sec, compact_->compaction->output_level(),
|
||||
stats.num_input_files_in_non_output_levels,
|
||||
@@ -626,20 +635,23 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
stats.bytes_read_output_level / 1048576.0,
|
||||
stats.bytes_written / 1048576.0, read_write_amp, write_amp,
|
||||
status.ToString().c_str(), stats.num_input_records,
|
||||
stats.num_dropped_records);
|
||||
stats.num_dropped_records,
|
||||
CompressionTypeToString(compact_->compaction->output_compression())
|
||||
.c_str());
|
||||
|
||||
UpdateCompactionJobStats(stats);
|
||||
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_);
|
||||
stream << "job" << job_id_
|
||||
<< "event" << "compaction_finished"
|
||||
stream << "job" << job_id_ << "event"
|
||||
<< "compaction_finished"
|
||||
<< "compaction_time_micros" << compaction_stats_.micros
|
||||
<< "output_level" << compact_->compaction->output_level()
|
||||
<< "num_output_files" << compact_->NumOutputFiles()
|
||||
<< "total_output_size" << compact_->total_bytes
|
||||
<< "num_input_records" << compact_->num_input_records
|
||||
<< "num_output_records" << compact_->num_output_records
|
||||
<< "num_subcompactions" << compact_->sub_compact_states.size();
|
||||
<< "total_output_size" << compact_->total_bytes << "num_input_records"
|
||||
<< compact_->num_input_records << "num_output_records"
|
||||
<< compact_->num_output_records << "num_subcompactions"
|
||||
<< compact_->sub_compact_states.size() << "output_compression"
|
||||
<< CompressionTypeToString(compact_->compaction->output_compression());
|
||||
|
||||
if (compaction_job_stats_ != nullptr) {
|
||||
stream << "num_single_delete_mismatches"
|
||||
@@ -674,7 +686,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
std::unique_ptr<RangeDelAggregator> range_del_agg(
|
||||
new RangeDelAggregator(cfd->internal_comparator(), existing_snapshots_));
|
||||
std::unique_ptr<InternalIterator> input(versions_->MakeInputIterator(
|
||||
sub_compact->compaction, range_del_agg.get()));
|
||||
sub_compact->compaction, range_del_agg.get(), env_optiosn_for_read_));
|
||||
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_PROCESS_KV);
|
||||
@@ -699,23 +711,28 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
sub_compact->compaction->mutable_cf_options();
|
||||
|
||||
// To build compression dictionary, we sample the first output file, assuming
|
||||
// it'll reach the maximum length, and then use the dictionary for compressing
|
||||
// subsequent output files. The dictionary may be less than max_dict_bytes if
|
||||
// the first output file's length is less than the maximum.
|
||||
// it'll reach the maximum length. We optionally pass these samples through
|
||||
// zstd's dictionary trainer, or just use them directly. Then, the dictionary
|
||||
// is used for compressing subsequent output files in the same subcompaction.
|
||||
const bool kUseZstdTrainer =
|
||||
cfd->ioptions()->compression_opts.zstd_max_train_bytes > 0;
|
||||
const size_t kSampleBytes =
|
||||
kUseZstdTrainer ? cfd->ioptions()->compression_opts.zstd_max_train_bytes
|
||||
: cfd->ioptions()->compression_opts.max_dict_bytes;
|
||||
const int kSampleLenShift = 6; // 2^6 = 64-byte samples
|
||||
std::set<size_t> sample_begin_offsets;
|
||||
if (bottommost_level_ &&
|
||||
cfd->ioptions()->compression_opts.max_dict_bytes > 0) {
|
||||
const size_t kMaxSamples =
|
||||
cfd->ioptions()->compression_opts.max_dict_bytes >> kSampleLenShift;
|
||||
const size_t kOutFileLen = mutable_cf_options->MaxFileSizeForLevel(
|
||||
compact_->compaction->output_level());
|
||||
if (bottommost_level_ && kSampleBytes > 0) {
|
||||
const size_t kMaxSamples = kSampleBytes >> kSampleLenShift;
|
||||
const size_t kOutFileLen = static_cast<size_t>(
|
||||
mutable_cf_options->MaxFileSizeForLevel(
|
||||
compact_->compaction->output_level()));
|
||||
if (kOutFileLen != port::kMaxSizet) {
|
||||
const size_t kOutFileNumSamples = kOutFileLen >> kSampleLenShift;
|
||||
Random64 generator{versions_->NewFileNumber()};
|
||||
for (size_t i = 0; i < kMaxSamples; ++i) {
|
||||
sample_begin_offsets.insert(generator.Uniform(kOutFileNumSamples)
|
||||
<< kSampleLenShift);
|
||||
sample_begin_offsets.insert(
|
||||
static_cast<size_t>(generator.Uniform(kOutFileNumSamples))
|
||||
<< kSampleLenShift);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -732,8 +749,8 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
compaction_filter, db_options_.info_log.get(),
|
||||
false /* internal key corruption is expected */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
compact_->compaction->level(), db_options_.statistics.get(),
|
||||
shutting_down_);
|
||||
snapshot_checker_, compact_->compaction->level(),
|
||||
db_options_.statistics.get(), shutting_down_);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
|
||||
|
||||
@@ -747,23 +764,13 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
input->SeekToFirst();
|
||||
}
|
||||
|
||||
// we allow only 1 compaction event listener. Used by blob storage
|
||||
CompactionEventListener* comp_event_listener = nullptr;
|
||||
#ifndef ROCKSDB_LITE
|
||||
for (auto& celitr : cfd->ioptions()->listeners) {
|
||||
comp_event_listener = celitr->GetCompactionEventListener();
|
||||
if (comp_event_listener != nullptr) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status status;
|
||||
sub_compact->c_iter.reset(new CompactionIterator(
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_, env_, false,
|
||||
range_del_agg.get(), sub_compact->compaction, compaction_filter,
|
||||
comp_event_listener, shutting_down_));
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, false, range_del_agg.get(),
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
preserve_deletes_seqnum_));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
if (c_iter->Valid() &&
|
||||
@@ -776,11 +783,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
const auto& c_iter_stats = c_iter->iter_stats();
|
||||
auto sample_begin_offset_iter = sample_begin_offsets.cbegin();
|
||||
// data_begin_offset and compression_dict are only valid while generating
|
||||
// data_begin_offset and dict_sample_data are only valid while generating
|
||||
// dictionary from the first output file.
|
||||
size_t data_begin_offset = 0;
|
||||
std::string compression_dict;
|
||||
compression_dict.reserve(cfd->ioptions()->compression_opts.max_dict_bytes);
|
||||
std::string dict_sample_data;
|
||||
dict_sample_data.reserve(kSampleBytes);
|
||||
|
||||
while (status.ok() && !cfd->IsDropped() && c_iter->Valid()) {
|
||||
// Invariant: c_iter.status() is guaranteed to be OK if c_iter->Valid()
|
||||
@@ -852,7 +859,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
data_elmt_copy_len =
|
||||
data_end_offset - (data_begin_offset + data_elmt_copy_offset);
|
||||
}
|
||||
compression_dict.append(&data_elmt.data()[data_elmt_copy_offset],
|
||||
dict_sample_data.append(&data_elmt.data()[data_elmt_copy_offset],
|
||||
data_elmt_copy_len);
|
||||
if (sample_end_offset > data_end_offset) {
|
||||
// Didn't finish sample. Try to finish it with the next data_elmt.
|
||||
@@ -907,9 +914,15 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
RecordDroppedKeys(range_del_out_stats,
|
||||
&sub_compact->compaction_job_stats);
|
||||
if (sub_compact->outputs.size() == 1) {
|
||||
// Use dictionary from first output file for compression of subsequent
|
||||
// files.
|
||||
sub_compact->compression_dict = std::move(compression_dict);
|
||||
// Use samples from first output file to create dictionary for
|
||||
// compression of subsequent files.
|
||||
if (kUseZstdTrainer) {
|
||||
sub_compact->compression_dict = ZSTD_TrainDictionary(
|
||||
dict_sample_data, kSampleLenShift,
|
||||
cfd->ioptions()->compression_opts.max_dict_bytes);
|
||||
} else {
|
||||
sub_compact->compression_dict = std::move(dict_sample_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1040,6 +1053,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
auto meta = &sub_compact->current_output()->meta;
|
||||
if (s.ok()) {
|
||||
Slice lower_bound_guard, upper_bound_guard;
|
||||
std::string smallest_user_key;
|
||||
const Slice *lower_bound, *upper_bound;
|
||||
if (sub_compact->outputs.size() == 1) {
|
||||
// For the first output table, include range tombstones before the min key
|
||||
@@ -1049,7 +1063,8 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// For subsequent output tables, only include range tombstones from min
|
||||
// key onwards since the previous file was extended to contain range
|
||||
// tombstones falling before min key.
|
||||
lower_bound_guard = meta->smallest.user_key();
|
||||
smallest_user_key = meta->smallest.user_key().ToString(false /*hex*/);
|
||||
lower_bound_guard = Slice(smallest_user_key);
|
||||
lower_bound = &lower_bound_guard;
|
||||
} else {
|
||||
lower_bound = nullptr;
|
||||
@@ -1067,16 +1082,18 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
range_del_agg->AddToBuilder(sub_compact->builder.get(), lower_bound,
|
||||
upper_bound, meta, range_del_out_stats,
|
||||
bottommost_level_);
|
||||
meta->marked_for_compaction = sub_compact->builder->NeedCompact();
|
||||
}
|
||||
const uint64_t current_entries = sub_compact->builder->NumEntries();
|
||||
meta->marked_for_compaction = sub_compact->builder->NeedCompact();
|
||||
if (s.ok()) {
|
||||
s = sub_compact->builder->Finish();
|
||||
} else {
|
||||
sub_compact->builder->Abandon();
|
||||
}
|
||||
const uint64_t current_bytes = sub_compact->builder->FileSize();
|
||||
meta->fd.file_size = current_bytes;
|
||||
if (s.ok()) {
|
||||
meta->fd.file_size = current_bytes;
|
||||
}
|
||||
sub_compact->current_output()->finished = true;
|
||||
sub_compact->total_bytes += current_bytes;
|
||||
|
||||
@@ -1121,7 +1138,8 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
nullptr /* range_del_agg */, nullptr,
|
||||
cfd->internal_stats()->GetFileReadHist(
|
||||
compact_->compaction->output_level()),
|
||||
false);
|
||||
false, nullptr /* arena */, false /* skip_filters */,
|
||||
compact_->compaction->output_level());
|
||||
s = iter->status();
|
||||
|
||||
if (s.ok() && paranoid_file_checks_) {
|
||||
@@ -1144,17 +1162,24 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
meta->marked_for_compaction ? " (need compaction)" : "");
|
||||
}
|
||||
}
|
||||
std::string fname = TableFileName(db_options_.db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
std::string fname;
|
||||
FileDescriptor output_fd;
|
||||
if (meta != nullptr) {
|
||||
fname = TableFileName(db_options_.db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
output_fd = meta->fd;
|
||||
} else {
|
||||
fname = "(nil)";
|
||||
}
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname,
|
||||
job_id_, meta->fd, tp, TableFileCreationReason::kCompaction, s);
|
||||
job_id_, output_fd, tp, TableFileCreationReason::kCompaction, s);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Report new file to SstFileManagerImpl
|
||||
auto sfm =
|
||||
static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
|
||||
if (sfm && meta->fd.GetPathId() == 0) {
|
||||
if (sfm && meta != nullptr && meta->fd.GetPathId() == 0) {
|
||||
auto fn = TableFileName(cfd->ioptions()->db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
sfm->OnAddFile(fn);
|
||||
@@ -1252,11 +1277,12 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
#endif // !ROCKSDB_LITE
|
||||
// Make the output file
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
EnvOptions opt_env_opts =
|
||||
env_->OptimizeForCompactionTableWrite(env_options_, db_options_);
|
||||
#ifndef NDEBUG
|
||||
bool syncpoint_arg = env_options_.use_direct_writes;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionJob::OpenCompactionOutputFile",
|
||||
&opt_env_opts.use_direct_writes);
|
||||
Status s = NewWritableFile(env_, fname, &writable_file, opt_env_opts);
|
||||
&syncpoint_arg);
|
||||
#endif
|
||||
Status s = NewWritableFile(env_, fname, &writable_file, env_options_);
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(
|
||||
db_options_.info_log,
|
||||
@@ -1279,6 +1305,7 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
|
||||
sub_compact->outputs.push_back(out);
|
||||
writable_file->SetIOPriority(Env::IO_LOW);
|
||||
writable_file->SetWriteLifeTimeHint(write_hint_);
|
||||
writable_file->SetPreallocationBlockSize(static_cast<size_t>(
|
||||
sub_compact->compaction->OutputFilePreallocationSize()));
|
||||
sub_compact->outfile.reset(new WritableFileWriter(
|
||||
@@ -1294,7 +1321,15 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->compaction->MaxInputFileCreationTime();
|
||||
if (output_file_creation_time == 0) {
|
||||
int64_t _current_time = 0;
|
||||
db_options_.env->GetCurrentTime(&_current_time); // ignore error
|
||||
auto status = db_options_.env->GetCurrentTime(&_current_time);
|
||||
// Safe to proceed even if GetCurrentTime fails. So, log and proceed.
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_WARN(
|
||||
db_options_.info_log,
|
||||
"Failed to get current time to populate creation_time property. "
|
||||
"Status: %s",
|
||||
status.ToString().c_str());
|
||||
}
|
||||
output_file_creation_time = static_cast<uint64_t>(_current_time);
|
||||
}
|
||||
|
||||
|
||||
+14
-4
@@ -45,24 +45,28 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class MemTable;
|
||||
class SnapshotChecker;
|
||||
class TableCache;
|
||||
class Version;
|
||||
class VersionEdit;
|
||||
class VersionSet;
|
||||
class Arena;
|
||||
|
||||
class CompactionJob {
|
||||
public:
|
||||
CompactionJob(int job_id, Compaction* compaction,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const EnvOptions& env_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
const EnvOptions env_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_directory,
|
||||
Statistics* stats, InstrumentedMutex* db_mutex,
|
||||
Status* db_bg_error,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname,
|
||||
@@ -127,11 +131,14 @@ class CompactionJob {
|
||||
// DBImpl state
|
||||
const std::string& dbname_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const EnvOptions& env_options_;
|
||||
const EnvOptions env_options_;
|
||||
|
||||
Env* env_;
|
||||
// env_option optimized for compaction table reads
|
||||
EnvOptions env_optiosn_for_read_;
|
||||
VersionSet* versions_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
LogBuffer* log_buffer_;
|
||||
Directory* db_directory_;
|
||||
Directory* output_directory_;
|
||||
@@ -149,6 +156,8 @@ class CompactionJob {
|
||||
// should make sure not to remove evidence that a write occurred.
|
||||
SequenceNumber earliest_write_conflict_snapshot_;
|
||||
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
EventLogger* event_logger_;
|
||||
@@ -160,6 +169,7 @@ class CompactionJob {
|
||||
std::vector<Slice> boundaries_;
|
||||
// Stores the approx size of keys covered in the range of each subcompaction
|
||||
std::vector<uint64_t> sizes_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -426,7 +426,7 @@ class CompactionJobStatsChecker : public EventListener {
|
||||
// Once a compaction completed, this function will verify the returned
|
||||
// CompactionJobInfo with the oldest CompactionJobInfo added earlier
|
||||
// in "expected_stats_" which has not yet being used for verification.
|
||||
virtual void OnCompactionCompleted(DB *db, const CompactionJobInfo& ci) {
|
||||
virtual void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) {
|
||||
if (verify_next_comp_io_stats_) {
|
||||
ASSERT_GT(ci.stats.file_write_nanos, 0);
|
||||
ASSERT_GT(ci.stats.file_range_sync_nanos, 0);
|
||||
|
||||
@@ -76,6 +76,7 @@ class CompactionJobTest : public testing::Test {
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_)),
|
||||
shutting_down_(false),
|
||||
preserve_deletes_seqnum_(0),
|
||||
mock_table_factory_(new mock::MockTableFactory()) {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
@@ -142,7 +143,8 @@ class CompactionJobTest : public testing::Test {
|
||||
}
|
||||
|
||||
void SetLastSequence(const SequenceNumber sequence_number) {
|
||||
versions_->SetLastToBeWrittenSequence(sequence_number + 1);
|
||||
versions_->SetLastAllocatedSequence(sequence_number + 1);
|
||||
versions_->SetLastPublishedSequence(sequence_number + 1);
|
||||
versions_->SetLastSequence(sequence_number + 1);
|
||||
}
|
||||
|
||||
@@ -250,12 +252,15 @@ class CompactionJobTest : public testing::Test {
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
mutex_.Lock();
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, env_options_, versions_.get(),
|
||||
&shutting_down_, &log_buffer, nullptr, nullptr, nullptr, &mutex_,
|
||||
&bg_error_, snapshots, earliest_write_conflict_snapshot, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_);
|
||||
|
||||
// TODO(yiwu) add a mock snapshot checker and add test for it.
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
CompactionJob compaction_job(0, &compaction, db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_, &log_buffer,
|
||||
nullptr, nullptr, nullptr, &mutex_, &bg_error_,
|
||||
snapshots, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, table_cache_, &event_logger,
|
||||
false, false, dbname_, &compaction_job_stats_);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
compaction_job.Prepare();
|
||||
@@ -291,6 +296,7 @@ class CompactionJobTest : public testing::Test {
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic<bool> shutting_down_;
|
||||
SequenceNumber preserve_deletes_seqnum_;
|
||||
std::shared_ptr<mock::MockTableFactory> mock_table_factory_;
|
||||
CompactionJobStats compaction_job_stats_;
|
||||
ColumnFamilyData* cfd_;
|
||||
|
||||
+89
-50
@@ -41,7 +41,7 @@ bool FindIntraL0Compaction(const std::vector<FileMetaData*>& level_files,
|
||||
size_t min_files_to_compact,
|
||||
uint64_t max_compact_bytes_per_del_file,
|
||||
CompactionInputFiles* comp_inputs) {
|
||||
size_t compact_bytes = level_files[0]->fd.file_size;
|
||||
size_t compact_bytes = static_cast<size_t>(level_files[0]->fd.file_size);
|
||||
size_t compact_bytes_per_del_file = port::kMaxSizet;
|
||||
// compaction range will be [0, span_len).
|
||||
size_t span_len;
|
||||
@@ -89,7 +89,7 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
// If bottommost_compression is set and we are compacting to the
|
||||
// bottommost level then we should use it.
|
||||
if (ioptions.bottommost_compression != kDisableCompressionOption &&
|
||||
level > base_level && level >= (vstorage->num_non_empty_levels() - 1)) {
|
||||
level >= (vstorage->num_non_empty_levels() - 1)) {
|
||||
return ioptions.bottommost_compression;
|
||||
}
|
||||
// If the user has specified a different compression level for each level,
|
||||
@@ -199,7 +199,7 @@ void CompactionPicker::GetRange(const std::vector<CompactionInputFiles>& inputs,
|
||||
assert(initialized);
|
||||
}
|
||||
|
||||
bool CompactionPicker::ExpandInputsToCleanCut(const std::string& cf_name,
|
||||
bool CompactionPicker::ExpandInputsToCleanCut(const std::string& /*cf_name*/,
|
||||
VersionStorageInfo* vstorage,
|
||||
CompactionInputFiles* inputs) {
|
||||
// This isn't good compaction
|
||||
@@ -292,25 +292,16 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
|
||||
uint32_t output_path_id) {
|
||||
assert(input_files.size());
|
||||
// This compaction output should not overlap with a running compaction as
|
||||
// `SanitizeCompactionInputFiles` should've checked earlier and db mutex
|
||||
// shouldn't have been released since.
|
||||
assert(!FilesRangeOverlapWithCompaction(input_files, output_level));
|
||||
|
||||
// TODO(rven ): we might be able to run concurrent level 0 compaction
|
||||
// if the key ranges of the two compactions do not overlap, but for now
|
||||
// we do not allow it.
|
||||
if ((input_files[0].level == 0) && !level0_compactions_in_progress_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
// This compaction output could overlap with a running compaction
|
||||
if (FilesRangeOverlapWithCompaction(input_files, output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
auto c =
|
||||
new Compaction(vstorage, ioptions_, mutable_cf_options, input_files,
|
||||
output_level, compact_options.output_file_size_limit,
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id,
|
||||
compact_options.compression, /* grandparents */ {}, true);
|
||||
|
||||
// If it's level 0 compaction, make sure we don't execute any other level 0
|
||||
// compactions in parallel
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
}
|
||||
@@ -318,7 +309,7 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
Status CompactionPicker::GetCompactionInputsFromFileNumbers(
|
||||
std::vector<CompactionInputFiles>* input_files,
|
||||
std::unordered_set<uint64_t>* input_set, const VersionStorageInfo* vstorage,
|
||||
const CompactionOptions& compact_options) const {
|
||||
const CompactionOptions& /*compact_options*/) const {
|
||||
if (input_set->size() == 0U) {
|
||||
return Status::InvalidArgument(
|
||||
"Compaction must include at least one file.");
|
||||
@@ -396,7 +387,10 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
assert(output_level_inputs->empty());
|
||||
const int input_level = inputs->level;
|
||||
const int output_level = output_level_inputs->level;
|
||||
assert(input_level != output_level);
|
||||
if (input_level == output_level) {
|
||||
// no possibility of conflict
|
||||
return true;
|
||||
}
|
||||
|
||||
// For now, we only support merging two levels, start level and output level.
|
||||
// We need to assert other levels are empty.
|
||||
@@ -730,10 +724,6 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
auto& levels = cf_meta.levels;
|
||||
auto comparator = icmp_->user_comparator();
|
||||
|
||||
// TODO(yhchiang): If there is any input files of L1 or up and there
|
||||
// is at least one L0 files. All L0 files older than the L0 file needs
|
||||
// to be included. Otherwise, it is a false conditoin
|
||||
|
||||
// TODO(yhchiang): add is_adjustable to CompactionOptions
|
||||
|
||||
// the smallest and largest key of the current compaction input
|
||||
@@ -794,6 +784,8 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
}
|
||||
last_included++;
|
||||
}
|
||||
} else if (output_level > 0) {
|
||||
last_included = static_cast<int>(current_files.size() - 1);
|
||||
}
|
||||
|
||||
// include all files between the first and the last compaction input files.
|
||||
@@ -949,6 +941,9 @@ void CompactionPicker::UnregisterCompaction(Compaction* c) {
|
||||
|
||||
bool LevelCompactionPicker::NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const {
|
||||
if (!vstorage->BottommostFilesMarkedForCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
if (!vstorage->FilesMarkedForCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -1139,7 +1134,28 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
is_manual_ = true;
|
||||
parent_index_ = base_index_ = -1;
|
||||
PickFilesMarkedForCompaction();
|
||||
if (!start_level_inputs_.empty()) {
|
||||
if (start_level_inputs_.empty()) {
|
||||
size_t i;
|
||||
for (i = 0; i < vstorage_->BottommostFilesMarkedForCompaction().size();
|
||||
++i) {
|
||||
auto& level_and_file =
|
||||
vstorage_->BottommostFilesMarkedForCompaction()[i];
|
||||
assert(!level_and_file.second->being_compacted);
|
||||
start_level_inputs_.level = output_level_ = start_level_ =
|
||||
level_and_file.first;
|
||||
start_level_inputs_.files = {level_and_file.second};
|
||||
if (compaction_picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
|
||||
&start_level_inputs_)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == vstorage_->BottommostFilesMarkedForCompaction().size()) {
|
||||
start_level_inputs_.clear();
|
||||
} else {
|
||||
assert(!start_level_inputs_.empty());
|
||||
compaction_reason_ = CompactionReason::kBottommostFiles;
|
||||
}
|
||||
} else {
|
||||
compaction_reason_ = CompactionReason::kFilesMarkedForCompaction;
|
||||
}
|
||||
}
|
||||
@@ -1279,6 +1295,8 @@ uint32_t LevelCompactionBuilder::GetPathId(
|
||||
uint64_t level_size;
|
||||
int cur_level = 0;
|
||||
|
||||
// max_bytes_for_level_base denotes L1 size.
|
||||
// We estimate L0 size to be the same as L1.
|
||||
level_size = mutable_cf_options.max_bytes_for_level_base;
|
||||
|
||||
// Last path is the fallback
|
||||
@@ -1289,8 +1307,21 @@ uint32_t LevelCompactionBuilder::GetPathId(
|
||||
return p;
|
||||
} else {
|
||||
current_path_size -= level_size;
|
||||
level_size = static_cast<uint64_t>(
|
||||
level_size * mutable_cf_options.max_bytes_for_level_multiplier);
|
||||
if (cur_level > 0) {
|
||||
if (ioptions.level_compaction_dynamic_level_bytes) {
|
||||
// Currently, level_compaction_dynamic_level_bytes is ignored when
|
||||
// multiple db paths are specified. https://github.com/facebook/
|
||||
// rocksdb/blob/master/db/column_family.cc.
|
||||
// Still, adding this check to avoid accidentally using
|
||||
// max_bytes_for_level_multiplier_additional
|
||||
level_size = static_cast<uint64_t>(
|
||||
level_size * mutable_cf_options.max_bytes_for_level_multiplier);
|
||||
} else {
|
||||
level_size = static_cast<uint64_t>(
|
||||
level_size * mutable_cf_options.max_bytes_for_level_multiplier *
|
||||
mutable_cf_options.MaxBytesMultiplerAdditional(cur_level));
|
||||
}
|
||||
}
|
||||
cur_level++;
|
||||
continue;
|
||||
}
|
||||
@@ -1421,7 +1452,7 @@ uint64_t GetTotalFilesSize(
|
||||
Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer) {
|
||||
assert(ioptions_.compaction_options_fifo.ttl > 0);
|
||||
assert(mutable_cf_options.compaction_options_fifo.ttl > 0);
|
||||
|
||||
const int kLevel0 = 0;
|
||||
const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(kLevel0);
|
||||
@@ -1442,19 +1473,22 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
inputs.emplace_back();
|
||||
inputs[0].level = 0;
|
||||
|
||||
for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
|
||||
auto f = *ritr;
|
||||
if (f->fd.table_reader != nullptr &&
|
||||
f->fd.table_reader->GetTableProperties() != nullptr) {
|
||||
auto creation_time =
|
||||
f->fd.table_reader->GetTableProperties()->creation_time;
|
||||
if (creation_time == 0 ||
|
||||
creation_time >=
|
||||
(current_time - ioptions_.compaction_options_fifo.ttl)) {
|
||||
break;
|
||||
// avoid underflow
|
||||
if (current_time > mutable_cf_options.compaction_options_fifo.ttl) {
|
||||
for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
|
||||
auto f = *ritr;
|
||||
if (f->fd.table_reader != nullptr &&
|
||||
f->fd.table_reader->GetTableProperties() != nullptr) {
|
||||
auto creation_time =
|
||||
f->fd.table_reader->GetTableProperties()->creation_time;
|
||||
if (creation_time == 0 ||
|
||||
creation_time >= (current_time -
|
||||
mutable_cf_options.compaction_options_fifo.ttl)) {
|
||||
break;
|
||||
}
|
||||
total_size -= f->compensated_file_size;
|
||||
inputs[0].files.push_back(f);
|
||||
}
|
||||
total_size -= f->compensated_file_size;
|
||||
inputs[0].files.push_back(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1463,7 +1497,8 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
// 2. there are a few files older than ttl, but deleting them will not bring
|
||||
// the total size to be less than max_table_files_size threshold.
|
||||
if (inputs[0].files.empty() ||
|
||||
total_size > ioptions_.compaction_options_fifo.max_table_files_size) {
|
||||
total_size >
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1489,10 +1524,11 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(kLevel0);
|
||||
uint64_t total_size = GetTotalFilesSize(level_files);
|
||||
|
||||
if (total_size <= ioptions_.compaction_options_fifo.max_table_files_size ||
|
||||
if (total_size <=
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size ||
|
||||
level_files.size() == 0) {
|
||||
// total size not exceeded
|
||||
if (ioptions_.compaction_options_fifo.allow_compaction &&
|
||||
if (mutable_cf_options.compaction_options_fifo.allow_compaction &&
|
||||
level_files.size() > 0) {
|
||||
CompactionInputFiles comp_inputs;
|
||||
if (FindIntraL0Compaction(
|
||||
@@ -1512,11 +1548,12 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
}
|
||||
}
|
||||
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: nothing to do. Total size %" PRIu64
|
||||
", max size %" PRIu64 "\n",
|
||||
cf_name.c_str(), total_size,
|
||||
ioptions_.compaction_options_fifo.max_table_files_size);
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: nothing to do. Total size %" PRIu64
|
||||
", max size %" PRIu64 "\n",
|
||||
cf_name.c_str(), total_size,
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1543,7 +1580,8 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with size %s for deletion",
|
||||
cf_name.c_str(), f->fd.GetNumber(), tmp_fsize);
|
||||
if (total_size <= ioptions_.compaction_options_fifo.max_table_files_size) {
|
||||
if (total_size <=
|
||||
mutable_cf_options.compaction_options_fifo.max_table_files_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1561,7 +1599,7 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
assert(vstorage->num_levels() == 1);
|
||||
|
||||
Compaction* c = nullptr;
|
||||
if (ioptions_.compaction_options_fifo.ttl > 0) {
|
||||
if (mutable_cf_options.compaction_options_fifo.ttl > 0) {
|
||||
c = PickTTLCompaction(cf_name, mutable_cf_options, vstorage, log_buffer);
|
||||
}
|
||||
if (c == nullptr) {
|
||||
@@ -1574,8 +1612,9 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
Compaction* FIFOCompactionPicker::CompactRange(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level, int output_level,
|
||||
uint32_t output_path_id, const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end, bool* manual_conflict) {
|
||||
uint32_t /*output_path_id*/, const InternalKey* /*begin*/,
|
||||
const InternalKey* /*end*/, InternalKey** compaction_end,
|
||||
bool* /*manual_conflict*/) {
|
||||
assert(input_level == 0);
|
||||
assert(output_level == 0);
|
||||
*compaction_end = nullptr;
|
||||
|
||||
+18
-12
@@ -88,6 +88,10 @@ class CompactionPicker {
|
||||
|
||||
// Takes a list of CompactionInputFiles and returns a (manual) Compaction
|
||||
// object.
|
||||
//
|
||||
// Caller must provide a set of input files that has been passed through
|
||||
// `SanitizeCompactionInputFiles` earlier. The lock should not be released
|
||||
// between that call and this one.
|
||||
Compaction* CompactFiles(const CompactionOptions& compact_options,
|
||||
const std::vector<CompactionInputFiles>& input_files,
|
||||
int output_level, VersionStorageInfo* vstorage,
|
||||
@@ -263,27 +267,29 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
virtual ~NullCompactionPicker() {}
|
||||
|
||||
// Always return "nullptr"
|
||||
Compaction* PickCompaction(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override {
|
||||
Compaction* PickCompaction(const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
VersionStorageInfo* /*vstorage*/,
|
||||
LogBuffer* /*log_buffer*/) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Always return "nullptr"
|
||||
Compaction* CompactRange(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, int input_level,
|
||||
int output_level, uint32_t output_path_id,
|
||||
const InternalKey* begin, const InternalKey* end,
|
||||
InternalKey** compaction_end,
|
||||
bool* manual_conflict) override {
|
||||
Compaction* CompactRange(const std::string& /*cf_name*/,
|
||||
const MutableCFOptions& /*mutable_cf_options*/,
|
||||
VersionStorageInfo* /*vstorage*/,
|
||||
int /*input_level*/, int /*output_level*/,
|
||||
uint32_t /*output_path_id*/,
|
||||
const InternalKey* /*begin*/,
|
||||
const InternalKey* /*end*/,
|
||||
InternalKey** /*compaction_end*/,
|
||||
bool* /*manual_conflict*/) override {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Always returns false.
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override {
|
||||
const VersionStorageInfo* /*vstorage*/) const override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -20,7 +20,9 @@ namespace rocksdb {
|
||||
class CountingLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
virtual void Logv(const char* format, va_list ap) override { log_count++; }
|
||||
virtual void Logv(const char* /*format*/, va_list /*ap*/) override {
|
||||
log_count++;
|
||||
}
|
||||
size_t log_count;
|
||||
};
|
||||
|
||||
@@ -175,6 +177,8 @@ TEST_F(CompactionPickerTest, Level1Trigger) {
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, Level1Trigger2) {
|
||||
mutable_cf_options_.target_file_size_base = 10000000000;
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
Add(1, 66U, "150", "200", 1000000001U);
|
||||
Add(1, 88U, "201", "300", 1000000000U);
|
||||
@@ -191,13 +195,14 @@ TEST_F(CompactionPickerTest, Level1Trigger2) {
|
||||
ASSERT_EQ(66U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(6U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(uint64_t{1073741824}, compaction->OutputFilePreallocationSize());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, LevelMaxScore) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.target_file_size_base = 10000000;
|
||||
mutable_cf_options_.target_file_size_multiplier = 10;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024;
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
Add(0, 1U, "150", "200", 1000000U);
|
||||
// Level 1 score 1.2
|
||||
Add(1, 66U, "150", "200", 6000000U);
|
||||
@@ -218,6 +223,9 @@ TEST_F(CompactionPickerTest, LevelMaxScore) {
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(7U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(mutable_cf_options_.target_file_size_base +
|
||||
mutable_cf_options_.target_file_size_base / 10,
|
||||
compaction->OutputFilePreallocationSize());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NeedsCompactionLevel) {
|
||||
@@ -437,7 +445,7 @@ TEST_F(CompactionPickerTest, CompactionUniversalIngestBehindReservedLevel) {
|
||||
TEST_F(CompactionPickerTest, CannotTrivialMoveUniversal) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
ioptions_.compaction_options_universal.allow_trivial_move = true;
|
||||
mutable_cf_options_.compaction_options_universal.allow_trivial_move = true;
|
||||
NewVersionStorage(1, kCompactionStyleUniversal);
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
// must return false when there's no files.
|
||||
@@ -468,7 +476,7 @@ TEST_F(CompactionPickerTest, CannotTrivialMoveUniversal) {
|
||||
TEST_F(CompactionPickerTest, AllowsTrivialMoveUniversal) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
ioptions_.compaction_options_universal.allow_trivial_move = true;
|
||||
mutable_cf_options_.compaction_options_universal.allow_trivial_move = true;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(3, kCompactionStyleUniversal);
|
||||
@@ -496,7 +504,7 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
const uint64_t kMaxSize = kFileSize * kFileCount / 2;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
ioptions_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
UpdateVersionStorageInfo();
|
||||
// must return false when there's no files.
|
||||
@@ -521,9 +529,10 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
ioptions_.compaction_pri = kMinOverlappingRatio;
|
||||
mutable_cf_options_.target_file_size_base = 10000000;
|
||||
mutable_cf_options_.target_file_size_base = 100000000000;
|
||||
mutable_cf_options_.target_file_size_multiplier = 10;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 10 * 1024 * 1024;
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
|
||||
Add(2, 6U, "150", "179", 50000000U);
|
||||
Add(2, 7U, "180", "220", 50000000U);
|
||||
@@ -543,6 +552,8 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
// Pick file 8 because it overlaps with 0 files on level 3.
|
||||
ASSERT_EQ(8U, compaction->input(0, 0)->fd.GetNumber());
|
||||
// Compaction input size * 1.1
|
||||
ASSERT_GE(uint64_t{55000000}, compaction->OutputFilePreallocationSize());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping2) {
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace {
|
||||
// and the index of the file in that level
|
||||
|
||||
struct InputFileInfo {
|
||||
InputFileInfo() : f(nullptr) {}
|
||||
InputFileInfo() : f(nullptr), level(0), index(0) {}
|
||||
|
||||
FileMetaData* f;
|
||||
size_t level;
|
||||
@@ -204,7 +204,8 @@ void UniversalCompactionPicker::SortedRun::DumpSizeInfo(
|
||||
|
||||
std::vector<UniversalCompactionPicker::SortedRun>
|
||||
UniversalCompactionPicker::CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions) {
|
||||
const VersionStorageInfo& vstorage, const ImmutableCFOptions& /*ioptions*/,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
std::vector<UniversalCompactionPicker::SortedRun> ret;
|
||||
for (FileMetaData* f : vstorage.LevelFiles(0)) {
|
||||
ret.emplace_back(0, f, f->fd.GetFileSize(), f->compensated_file_size,
|
||||
@@ -218,7 +219,8 @@ UniversalCompactionPicker::CalculateSortedRuns(
|
||||
for (FileMetaData* f : vstorage.LevelFiles(level)) {
|
||||
total_compensated_size += f->compensated_file_size;
|
||||
total_size += f->fd.GetFileSize();
|
||||
if (ioptions.compaction_options_universal.allow_trivial_move == true) {
|
||||
if (mutable_cf_options.compaction_options_universal.allow_trivial_move ==
|
||||
true) {
|
||||
if (f->being_compacted) {
|
||||
being_compacted = f->being_compacted;
|
||||
}
|
||||
@@ -227,7 +229,8 @@ UniversalCompactionPicker::CalculateSortedRuns(
|
||||
// non-zero level, all the files should share the same being_compacted
|
||||
// value.
|
||||
// This assumption is only valid when
|
||||
// ioptions.compaction_options_universal.allow_trivial_move is false
|
||||
// mutable_cf_options.compaction_options_universal.allow_trivial_move is
|
||||
// false
|
||||
assert(is_first || f->being_compacted == being_compacted);
|
||||
}
|
||||
if (is_first) {
|
||||
@@ -245,14 +248,13 @@ UniversalCompactionPicker::CalculateSortedRuns(
|
||||
|
||||
// Universal style of compaction. Pick files that are contiguous in
|
||||
// time-range to compact.
|
||||
//
|
||||
Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
VersionStorageInfo* vstorage, LogBuffer* log_buffer) {
|
||||
const int kLevel0 = 0;
|
||||
double score = vstorage->CompactionScore(kLevel0);
|
||||
std::vector<SortedRun> sorted_runs =
|
||||
CalculateSortedRuns(*vstorage, ioptions_);
|
||||
CalculateSortedRuns(*vstorage, ioptions_, mutable_cf_options);
|
||||
|
||||
if (sorted_runs.size() == 0 ||
|
||||
sorted_runs.size() <
|
||||
@@ -279,7 +281,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
} else {
|
||||
// Size amplification is within limits. Try reducing read
|
||||
// amplification while maintaining file size ratios.
|
||||
unsigned int ratio = ioptions_.compaction_options_universal.size_ratio;
|
||||
unsigned int ratio =
|
||||
mutable_cf_options.compaction_options_universal.size_ratio;
|
||||
|
||||
if ((c = PickCompactionToReduceSortedRuns(
|
||||
cf_name, mutable_cf_options, vstorage, score, ratio, UINT_MAX,
|
||||
@@ -327,7 +330,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (ioptions_.compaction_options_universal.allow_trivial_move == true) {
|
||||
if (mutable_cf_options.compaction_options_universal.allow_trivial_move ==
|
||||
true) {
|
||||
c->set_is_trivial_move(IsInputFilesNonOverlapping(c));
|
||||
}
|
||||
|
||||
@@ -381,7 +385,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
}
|
||||
|
||||
uint32_t UniversalCompactionPicker::GetPathId(
|
||||
const ImmutableCFOptions& ioptions, uint64_t file_size) {
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, uint64_t file_size) {
|
||||
// Two conditions need to be satisfied:
|
||||
// (1) the target path needs to be able to hold the file's size
|
||||
// (2) Total size left in this and previous paths need to be not
|
||||
@@ -398,8 +403,8 @@ uint32_t UniversalCompactionPicker::GetPathId(
|
||||
// that case. We need to improve it.
|
||||
uint64_t accumulated_size = 0;
|
||||
uint64_t future_size =
|
||||
file_size * (100 - ioptions.compaction_options_universal.size_ratio) /
|
||||
100;
|
||||
file_size *
|
||||
(100 - mutable_cf_options.compaction_options_universal.size_ratio) / 100;
|
||||
uint32_t p = 0;
|
||||
assert(!ioptions.db_paths.empty());
|
||||
for (; p < ioptions.db_paths.size() - 1; p++) {
|
||||
@@ -423,9 +428,9 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns(
|
||||
unsigned int max_number_of_files_to_compact,
|
||||
const std::vector<SortedRun>& sorted_runs, LogBuffer* log_buffer) {
|
||||
unsigned int min_merge_width =
|
||||
ioptions_.compaction_options_universal.min_merge_width;
|
||||
mutable_cf_options.compaction_options_universal.min_merge_width;
|
||||
unsigned int max_merge_width =
|
||||
ioptions_.compaction_options_universal.max_merge_width;
|
||||
mutable_cf_options.compaction_options_universal.max_merge_width;
|
||||
|
||||
const SortedRun* sr = nullptr;
|
||||
bool done = false;
|
||||
@@ -492,7 +497,7 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns(
|
||||
if (sz < static_cast<double>(succeeding_sr->size)) {
|
||||
break;
|
||||
}
|
||||
if (ioptions_.compaction_options_universal.stop_style ==
|
||||
if (mutable_cf_options.compaction_options_universal.stop_style ==
|
||||
kCompactionStopStyleSimilarSize) {
|
||||
// Similar-size stopping rule: also check the last picked file isn't
|
||||
// far larger than the next candidate file.
|
||||
@@ -535,7 +540,7 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns(
|
||||
// size ratio of compression.
|
||||
bool enable_compression = true;
|
||||
int ratio_to_compress =
|
||||
ioptions_.compaction_options_universal.compression_size_percent;
|
||||
mutable_cf_options.compaction_options_universal.compression_size_percent;
|
||||
if (ratio_to_compress >= 0) {
|
||||
uint64_t total_size = 0;
|
||||
for (auto& sorted_run : sorted_runs) {
|
||||
@@ -556,7 +561,8 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns(
|
||||
for (unsigned int i = 0; i < first_index_after; i++) {
|
||||
estimated_total_size += sorted_runs[i].size;
|
||||
}
|
||||
uint32_t path_id = GetPathId(ioptions_, estimated_total_size);
|
||||
uint32_t path_id =
|
||||
GetPathId(ioptions_, mutable_cf_options, estimated_total_size);
|
||||
int start_level = sorted_runs[start_index].level;
|
||||
int output_level;
|
||||
if (first_index_after == sorted_runs.size()) {
|
||||
@@ -597,9 +603,9 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSortedRuns(
|
||||
|
||||
CompactionReason compaction_reason;
|
||||
if (max_number_of_files_to_compact == UINT_MAX) {
|
||||
compaction_reason = CompactionReason::kUniversalSortedRunNum;
|
||||
} else {
|
||||
compaction_reason = CompactionReason::kUniversalSizeRatio;
|
||||
} else {
|
||||
compaction_reason = CompactionReason::kUniversalSortedRunNum;
|
||||
}
|
||||
return new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, std::move(inputs), output_level,
|
||||
@@ -621,8 +627,8 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSizeAmp(
|
||||
VersionStorageInfo* vstorage, double score,
|
||||
const std::vector<SortedRun>& sorted_runs, LogBuffer* log_buffer) {
|
||||
// percentage flexibility while reducing size amplification
|
||||
uint64_t ratio =
|
||||
ioptions_.compaction_options_universal.max_size_amplification_percent;
|
||||
uint64_t ratio = mutable_cf_options.compaction_options_universal
|
||||
.max_size_amplification_percent;
|
||||
|
||||
unsigned int candidate_count = 0;
|
||||
uint64_t candidate_size = 0;
|
||||
@@ -700,7 +706,8 @@ Compaction* UniversalCompactionPicker::PickCompactionToReduceSizeAmp(
|
||||
for (size_t loop = start_index; loop < sorted_runs.size(); loop++) {
|
||||
estimated_total_size += sorted_runs[loop].size;
|
||||
}
|
||||
uint32_t path_id = GetPathId(ioptions_, estimated_total_size);
|
||||
uint32_t path_id =
|
||||
GetPathId(ioptions_, mutable_cf_options, estimated_total_size);
|
||||
int start_level = sorted_runs[start_index].level;
|
||||
|
||||
std::vector<CompactionInputFiles> inputs(vstorage->num_levels());
|
||||
|
||||
@@ -80,11 +80,13 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
bool IsInputFilesNonOverlapping(Compaction* c);
|
||||
|
||||
static std::vector<SortedRun> CalculateSortedRuns(
|
||||
const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions);
|
||||
const VersionStorageInfo& vstorage, const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// Pick a path ID to place a newly generated file, with its estimated file
|
||||
// size.
|
||||
static uint32_t GetPathId(const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
uint64_t file_size);
|
||||
};
|
||||
} // namespace rocksdb
|
||||
|
||||
@@ -188,10 +188,10 @@ class DoubleComparator : public Comparator {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
virtual void FindShortestSeparator(std::string* start,
|
||||
const Slice& limit) const override {}
|
||||
virtual void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
|
||||
virtual void FindShortSuccessor(std::string* key) const override {}
|
||||
virtual void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
class HashComparator : public Comparator {
|
||||
@@ -211,10 +211,10 @@ class HashComparator : public Comparator {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
virtual void FindShortestSeparator(std::string* start,
|
||||
const Slice& limit) const override {}
|
||||
virtual void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
|
||||
virtual void FindShortSuccessor(std::string* key) const override {}
|
||||
virtual void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
|
||||
class TwoStrComparator : public Comparator {
|
||||
@@ -243,10 +243,10 @@ class TwoStrComparator : public Comparator {
|
||||
}
|
||||
return a2.compare(b2);
|
||||
}
|
||||
virtual void FindShortestSeparator(std::string* start,
|
||||
const Slice& limit) const override {}
|
||||
virtual void FindShortestSeparator(std::string* /*start*/,
|
||||
const Slice& /*limit*/) const override {}
|
||||
|
||||
virtual void FindShortSuccessor(std::string* key) const override {}
|
||||
virtual void FindShortSuccessor(std::string* /*key*/) const override {}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
|
||||
+10
-2
@@ -19,9 +19,17 @@ void CancelAllBackgroundWork(DB* db, bool wait) {
|
||||
}
|
||||
|
||||
Status DeleteFilesInRange(DB* db, ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) {
|
||||
const Slice* begin, const Slice* end,
|
||||
bool include_end) {
|
||||
RangePtr range(begin, end);
|
||||
return DeleteFilesInRanges(db, column_family, &range, 1, include_end);
|
||||
}
|
||||
|
||||
Status DeleteFilesInRanges(DB* db, ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
bool include_end) {
|
||||
return (static_cast_with_check<DBImpl, DB>(db->GetRootDB()))
|
||||
->DeleteFilesInRange(column_family, begin, end);
|
||||
->DeleteFilesInRanges(column_family, ranges, n, include_end);
|
||||
}
|
||||
|
||||
Status VerifySstFileChecksum(const Options& options,
|
||||
|
||||
@@ -241,7 +241,7 @@ TEST_F(CuckooTableDBTest, CompactionIntoMultipleFiles) {
|
||||
|
||||
// Write 28 values, each 10016 B ~ 10KB
|
||||
for (int idx = 0; idx < 28; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx)));
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx))));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
@@ -250,7 +250,7 @@ TEST_F(CuckooTableDBTest, CompactionIntoMultipleFiles) {
|
||||
true /* disallow trivial move */);
|
||||
ASSERT_EQ("0,2", FilesPerLevel());
|
||||
for (int idx = 0; idx < 28; ++idx) {
|
||||
ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx)));
|
||||
ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,14 +271,14 @@ TEST_F(CuckooTableDBTest, SameKeyInsertedInTwoDifferentFilesAndCompacted) {
|
||||
|
||||
// Generate one more file in level-0, and should trigger level-0 compaction
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + idx)));
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx))));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
||||
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
ASSERT_EQ(std::string(10000, 'a' + idx), Get(Key(idx)));
|
||||
ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+116
-7
@@ -9,6 +9,7 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "util/sync_point.h"
|
||||
#endif
|
||||
@@ -41,7 +42,7 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
|
||||
Close();
|
||||
|
||||
auto options = CurrentOptions();
|
||||
assert(options.env = env_);
|
||||
assert(options.env == env_);
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
ASSERT_EQ("v3", Get("foo"));
|
||||
ASSERT_EQ("v2", Get("bar"));
|
||||
@@ -570,19 +571,19 @@ TEST_F(DBBasicTest, CompactBetweenSnapshots) {
|
||||
|
||||
TEST_F(DBBasicTest, DBOpen_Options) {
|
||||
Options options = CurrentOptions();
|
||||
std::string dbname = test::TmpDir(env_) + "/db_options_test";
|
||||
ASSERT_OK(DestroyDB(dbname, options));
|
||||
Close();
|
||||
Destroy(options);
|
||||
|
||||
// Does not exist, and create_if_missing == false: error
|
||||
DB* db = nullptr;
|
||||
options.create_if_missing = false;
|
||||
Status s = DB::Open(options, dbname, &db);
|
||||
Status s = DB::Open(options, dbname_, &db);
|
||||
ASSERT_TRUE(strstr(s.ToString().c_str(), "does not exist") != nullptr);
|
||||
ASSERT_TRUE(db == nullptr);
|
||||
|
||||
// Does not exist, and create_if_missing == true: OK
|
||||
options.create_if_missing = true;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
s = DB::Open(options, dbname_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
@@ -592,14 +593,14 @@ TEST_F(DBBasicTest, DBOpen_Options) {
|
||||
// Does exist, and error_if_exists == true: error
|
||||
options.create_if_missing = false;
|
||||
options.error_if_exists = true;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
s = DB::Open(options, dbname_, &db);
|
||||
ASSERT_TRUE(strstr(s.ToString().c_str(), "exists") != nullptr);
|
||||
ASSERT_TRUE(db == nullptr);
|
||||
|
||||
// Does exist, and error_if_exists == false: OK
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
s = DB::Open(options, dbname_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
@@ -847,6 +848,114 @@ TEST_F(DBBasicTest, MmapAndBufferOptions) {
|
||||
}
|
||||
#endif
|
||||
|
||||
class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit TestEnv() : EnvWrapper(Env::Default()),
|
||||
close_count(0) { }
|
||||
|
||||
class TestLogger : public Logger {
|
||||
public:
|
||||
using Logger::Logv;
|
||||
TestLogger(TestEnv *env_ptr) : Logger() { env = env_ptr; }
|
||||
~TestLogger() {
|
||||
if (!closed_) {
|
||||
CloseHelper();
|
||||
}
|
||||
}
|
||||
virtual void Logv(const char* /*format*/, va_list /*ap*/) override{};
|
||||
|
||||
protected:
|
||||
virtual Status CloseImpl() override {
|
||||
return CloseHelper();
|
||||
}
|
||||
private:
|
||||
Status CloseHelper() {
|
||||
env->CloseCountInc();;
|
||||
return Status::IOError();
|
||||
}
|
||||
TestEnv *env;
|
||||
};
|
||||
|
||||
void CloseCountInc() { close_count++; }
|
||||
|
||||
int GetCloseCount() { return close_count; }
|
||||
|
||||
virtual Status NewLogger(const std::string& /*fname*/,
|
||||
shared_ptr<Logger>* result) {
|
||||
result->reset(new TestLogger(this));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
int close_count;
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTest, DBClose) {
|
||||
Options options = GetDefaultOptions();
|
||||
std::string dbname = test::TmpDir(env_) + "/db_close_test";
|
||||
ASSERT_OK(DestroyDB(dbname, options));
|
||||
|
||||
DB* db = nullptr;
|
||||
TestEnv* env = new TestEnv();
|
||||
options.create_if_missing = true;
|
||||
options.env = env;
|
||||
Status s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
s = db->Close();
|
||||
ASSERT_EQ(env->GetCloseCount(), 1);
|
||||
ASSERT_EQ(s, Status::IOError());
|
||||
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 1);
|
||||
|
||||
// Do not call DB::Close() and ensure our logger Close() still gets called
|
||||
s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 2);
|
||||
|
||||
// Provide our own logger and ensure DB::Close() does not close it
|
||||
options.info_log.reset(new TestEnv::TestLogger(env));
|
||||
options.create_if_missing = false;
|
||||
s = DB::Open(options, dbname, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_TRUE(db != nullptr);
|
||||
|
||||
s = db->Close();
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
delete db;
|
||||
ASSERT_EQ(env->GetCloseCount(), 2);
|
||||
options.info_log.reset();
|
||||
ASSERT_EQ(env->GetCloseCount(), 3);
|
||||
|
||||
delete options.env;
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DBCloseFlushError) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
|
||||
new FaultInjectionTestEnv(Env::Default()));
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.manual_wal_flush = true;
|
||||
options.write_buffer_size=100;
|
||||
options.env = fault_injection_env.get();
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("key1", "value1"));
|
||||
ASSERT_OK(Put("key2", "value2"));
|
||||
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
|
||||
ASSERT_OK(Put("key3", "value3"));
|
||||
fault_injection_env->SetFilesystemActive(false);
|
||||
Status s = dbfull()->Close();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
ASSERT_NE(s, Status::OK());
|
||||
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -0,0 +1,410 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// kTypeBlobIndex is a value type used by BlobDB only. The base rocksdb
|
||||
// should accept the value type on write, and report not supported value
|
||||
// for reads, unless caller request for it explicitly. The base rocksdb
|
||||
// doesn't understand format of actual blob index (the value).
|
||||
class DBBlobIndexTest : public DBTestBase {
|
||||
public:
|
||||
enum Tier {
|
||||
kMemtable = 0,
|
||||
kImmutableMemtables = 1,
|
||||
kL0SstFile = 2,
|
||||
kLnSstFile = 3,
|
||||
};
|
||||
const std::vector<Tier> kAllTiers = {Tier::kMemtable,
|
||||
Tier::kImmutableMemtables,
|
||||
Tier::kL0SstFile, Tier::kLnSstFile};
|
||||
|
||||
DBBlobIndexTest() : DBTestBase("/db_blob_index_test") {}
|
||||
|
||||
ColumnFamilyHandle* cfh() { return dbfull()->DefaultColumnFamily(); }
|
||||
|
||||
ColumnFamilyData* cfd() {
|
||||
return reinterpret_cast<ColumnFamilyHandleImpl*>(cfh())->cfd();
|
||||
}
|
||||
|
||||
Status PutBlobIndex(WriteBatch* batch, const Slice& key,
|
||||
const Slice& blob_index) {
|
||||
return WriteBatchInternal::PutBlobIndex(batch, cfd()->GetID(), key,
|
||||
blob_index);
|
||||
}
|
||||
|
||||
Status Write(WriteBatch* batch) {
|
||||
return dbfull()->Write(WriteOptions(), batch);
|
||||
}
|
||||
|
||||
std::string GetImpl(const Slice& key, bool* is_blob_index = nullptr,
|
||||
const Snapshot* snapshot = nullptr) {
|
||||
ReadOptions read_options;
|
||||
read_options.snapshot = snapshot;
|
||||
PinnableSlice value;
|
||||
auto s = dbfull()->GetImpl(read_options, cfh(), key, &value,
|
||||
nullptr /*value_found*/, nullptr /*callback*/,
|
||||
is_blob_index);
|
||||
if (s.IsNotFound()) {
|
||||
return "NOT_FOUND";
|
||||
}
|
||||
if (s.IsNotSupported()) {
|
||||
return "NOT_SUPPORTED";
|
||||
}
|
||||
if (!s.ok()) {
|
||||
return s.ToString();
|
||||
}
|
||||
return value.ToString();
|
||||
}
|
||||
|
||||
std::string GetBlobIndex(const Slice& key,
|
||||
const Snapshot* snapshot = nullptr) {
|
||||
bool is_blob_index = false;
|
||||
std::string value = GetImpl(key, &is_blob_index, snapshot);
|
||||
if (!is_blob_index) {
|
||||
return "NOT_BLOB";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter* GetBlobIterator() {
|
||||
return dbfull()->NewIteratorImpl(
|
||||
ReadOptions(), cfd(), dbfull()->GetLatestSequenceNumber(),
|
||||
nullptr /*read_callback*/, true /*allow_blob*/);
|
||||
}
|
||||
|
||||
Options GetTestOptions() {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
// Disable auto flushes.
|
||||
options.max_write_buffer_number = 10;
|
||||
options.min_write_buffer_number_to_merge = 10;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
return options;
|
||||
}
|
||||
|
||||
void MoveDataTo(Tier tier) {
|
||||
switch (tier) {
|
||||
case Tier::kMemtable:
|
||||
break;
|
||||
case Tier::kImmutableMemtables:
|
||||
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
|
||||
break;
|
||||
case Tier::kL0SstFile:
|
||||
ASSERT_OK(Flush());
|
||||
break;
|
||||
case Tier::kLnSstFile:
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("a", "dummy"));
|
||||
ASSERT_OK(Put("z", "dummy"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Should be able to write kTypeBlobIndex to memtables and SST files.
|
||||
TEST_F(DBBlobIndexTest, Write) {
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
std::string index = ToString(i);
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(PutBlobIndex(&batch, "key" + index, "blob" + index));
|
||||
ASSERT_OK(Write(&batch));
|
||||
}
|
||||
MoveDataTo(tier);
|
||||
for (int i = 1; i <= 5; i++) {
|
||||
std::string index = ToString(i);
|
||||
ASSERT_EQ("blob" + index, GetBlobIndex("key" + index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get should be able to return blob index if is_blob_index is provided,
|
||||
// otherwise return Status::NotSupported status.
|
||||
TEST_F(DBBlobIndexTest, Get) {
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(batch.Put("key", "value"));
|
||||
ASSERT_OK(PutBlobIndex(&batch, "blob_key", "blob_index"));
|
||||
ASSERT_OK(Write(&batch));
|
||||
MoveDataTo(tier);
|
||||
// Verify normal value
|
||||
bool is_blob_index = false;
|
||||
PinnableSlice value;
|
||||
ASSERT_EQ("value", Get("key"));
|
||||
ASSERT_EQ("value", GetImpl("key"));
|
||||
ASSERT_EQ("value", GetImpl("key", &is_blob_index));
|
||||
ASSERT_FALSE(is_blob_index);
|
||||
// Verify blob index
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsNotSupported());
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("blob_key"));
|
||||
ASSERT_EQ("blob_index", GetImpl("blob_key", &is_blob_index));
|
||||
ASSERT_TRUE(is_blob_index);
|
||||
}
|
||||
}
|
||||
|
||||
// Get should NOT return Status::NotSupported if blob index is updated with
|
||||
// a normal value.
|
||||
TEST_F(DBBlobIndexTest, Updated) {
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
WriteBatch batch;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(PutBlobIndex(&batch, "key" + ToString(i), "blob_index"));
|
||||
}
|
||||
ASSERT_OK(Write(&batch));
|
||||
// Avoid blob values from being purged.
|
||||
const Snapshot* snapshot = dbfull()->GetSnapshot();
|
||||
ASSERT_OK(Put("key1", "new_value"));
|
||||
ASSERT_OK(Merge("key2", "a"));
|
||||
ASSERT_OK(Merge("key2", "b"));
|
||||
ASSERT_OK(Merge("key2", "c"));
|
||||
ASSERT_OK(Delete("key3"));
|
||||
ASSERT_OK(SingleDelete("key4"));
|
||||
ASSERT_OK(Delete("key5"));
|
||||
ASSERT_OK(Merge("key5", "a"));
|
||||
ASSERT_OK(Merge("key5", "b"));
|
||||
ASSERT_OK(Merge("key5", "c"));
|
||||
ASSERT_OK(dbfull()->DeleteRange(WriteOptions(), cfh(), "key6", "key9"));
|
||||
MoveDataTo(tier);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_EQ("blob_index", GetBlobIndex("key" + ToString(i), snapshot));
|
||||
}
|
||||
ASSERT_EQ("new_value", Get("key1"));
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("key2"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key3"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key4"));
|
||||
ASSERT_EQ("a,b,c", GetImpl("key5"));
|
||||
for (int i = 6; i < 9; i++) {
|
||||
ASSERT_EQ("NOT_FOUND", Get("key" + ToString(i)));
|
||||
}
|
||||
ASSERT_EQ("blob_index", GetBlobIndex("key9"));
|
||||
dbfull()->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator should get blob value if allow_blob flag is set,
|
||||
// otherwise return Status::NotSupported status.
|
||||
TEST_F(DBBlobIndexTest, Iterate) {
|
||||
const std::vector<std::vector<ValueType>> data = {
|
||||
/*00*/ {kTypeValue},
|
||||
/*01*/ {kTypeBlobIndex},
|
||||
/*02*/ {kTypeValue},
|
||||
/*03*/ {kTypeBlobIndex, kTypeValue},
|
||||
/*04*/ {kTypeValue},
|
||||
/*05*/ {kTypeValue, kTypeBlobIndex},
|
||||
/*06*/ {kTypeValue},
|
||||
/*07*/ {kTypeDeletion, kTypeBlobIndex},
|
||||
/*08*/ {kTypeValue},
|
||||
/*09*/ {kTypeSingleDeletion, kTypeBlobIndex},
|
||||
/*10*/ {kTypeValue},
|
||||
/*11*/ {kTypeMerge, kTypeMerge, kTypeMerge, kTypeBlobIndex},
|
||||
/*12*/ {kTypeValue},
|
||||
/*13*/
|
||||
{kTypeMerge, kTypeMerge, kTypeMerge, kTypeDeletion, kTypeBlobIndex},
|
||||
/*14*/ {kTypeValue},
|
||||
/*15*/ {kTypeBlobIndex},
|
||||
/*16*/ {kTypeValue},
|
||||
};
|
||||
|
||||
auto get_key = [](int index) {
|
||||
char buf[20];
|
||||
snprintf(buf, sizeof(buf), "%02d", index);
|
||||
return "key" + std::string(buf);
|
||||
};
|
||||
|
||||
auto get_value = [&](int index, int version) {
|
||||
return get_key(index) + "_value" + ToString(version);
|
||||
};
|
||||
|
||||
auto check_iterator = [&](Iterator* iterator, Status::Code expected_status,
|
||||
const Slice& expected_value) {
|
||||
ASSERT_EQ(expected_status, iterator->status().code());
|
||||
if (expected_status == Status::kOk) {
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ(expected_value, iterator->value());
|
||||
} else {
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
}
|
||||
};
|
||||
|
||||
auto create_normal_iterator = [&]() -> Iterator* {
|
||||
return dbfull()->NewIterator(ReadOptions());
|
||||
};
|
||||
|
||||
auto create_blob_iterator = [&]() -> Iterator* { return GetBlobIterator(); };
|
||||
|
||||
auto check_is_blob = [&](bool is_blob) {
|
||||
return [is_blob](Iterator* iterator) {
|
||||
ASSERT_EQ(is_blob,
|
||||
reinterpret_cast<ArenaWrappedDBIter*>(iterator)->IsBlob());
|
||||
};
|
||||
};
|
||||
|
||||
auto verify = [&](int index, Status::Code expected_status,
|
||||
const Slice& forward_value, const Slice& backward_value,
|
||||
std::function<Iterator*()> create_iterator,
|
||||
std::function<void(Iterator*)> extra_check = nullptr) {
|
||||
// Seek
|
||||
auto* iterator = create_iterator();
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->Seek(get_key(index));
|
||||
check_iterator(iterator, expected_status, forward_value);
|
||||
if (extra_check) {
|
||||
extra_check(iterator);
|
||||
}
|
||||
delete iterator;
|
||||
|
||||
// Next
|
||||
iterator = create_iterator();
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->Seek(get_key(index - 1));
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
iterator->Next();
|
||||
check_iterator(iterator, expected_status, forward_value);
|
||||
if (extra_check) {
|
||||
extra_check(iterator);
|
||||
}
|
||||
delete iterator;
|
||||
|
||||
// SeekForPrev
|
||||
iterator = create_iterator();
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->SeekForPrev(get_key(index));
|
||||
check_iterator(iterator, expected_status, backward_value);
|
||||
if (extra_check) {
|
||||
extra_check(iterator);
|
||||
}
|
||||
delete iterator;
|
||||
|
||||
// Prev
|
||||
iterator = create_iterator();
|
||||
iterator->Seek(get_key(index + 1));
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
iterator->Prev();
|
||||
check_iterator(iterator, expected_status, backward_value);
|
||||
if (extra_check) {
|
||||
extra_check(iterator);
|
||||
}
|
||||
delete iterator;
|
||||
};
|
||||
|
||||
for (auto tier : {Tier::kMemtable} /*kAllTiers*/) {
|
||||
// Avoid values from being purged.
|
||||
std::vector<const Snapshot*> snapshots;
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
|
||||
// fill data
|
||||
for (int i = 0; i < static_cast<int>(data.size()); i++) {
|
||||
for (int j = static_cast<int>(data[i].size()) - 1; j >= 0; j--) {
|
||||
std::string key = get_key(i);
|
||||
std::string value = get_value(i, j);
|
||||
WriteBatch batch;
|
||||
switch (data[i][j]) {
|
||||
case kTypeValue:
|
||||
ASSERT_OK(Put(key, value));
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
ASSERT_OK(Delete(key));
|
||||
break;
|
||||
case kTypeSingleDeletion:
|
||||
ASSERT_OK(SingleDelete(key));
|
||||
break;
|
||||
case kTypeMerge:
|
||||
ASSERT_OK(Merge(key, value));
|
||||
break;
|
||||
case kTypeBlobIndex:
|
||||
ASSERT_OK(PutBlobIndex(&batch, key, value));
|
||||
ASSERT_OK(Write(&batch));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
};
|
||||
}
|
||||
snapshots.push_back(dbfull()->GetSnapshot());
|
||||
}
|
||||
ASSERT_OK(
|
||||
dbfull()->DeleteRange(WriteOptions(), cfh(), get_key(15), get_key(16)));
|
||||
snapshots.push_back(dbfull()->GetSnapshot());
|
||||
MoveDataTo(tier);
|
||||
|
||||
// Normal iterator
|
||||
verify(1, Status::kNotSupported, "", "", create_normal_iterator);
|
||||
verify(3, Status::kNotSupported, "", "", create_normal_iterator);
|
||||
verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
|
||||
create_normal_iterator);
|
||||
verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
|
||||
create_normal_iterator);
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_normal_iterator);
|
||||
verify(11, Status::kNotSupported, "", "", create_normal_iterator);
|
||||
verify(13, Status::kOk,
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
create_normal_iterator);
|
||||
verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
|
||||
create_normal_iterator);
|
||||
|
||||
// Iterator with blob support
|
||||
verify(1, Status::kOk, get_value(1, 0), get_value(1, 0),
|
||||
create_blob_iterator, check_is_blob(true));
|
||||
verify(3, Status::kOk, get_value(3, 0), get_value(3, 0),
|
||||
create_blob_iterator, check_is_blob(true));
|
||||
verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
verify(13, Status::kOk,
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(15, Status::kOk, get_value(16, 0), get_value(14, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
|
||||
for (auto* snapshot : snapshots) {
|
||||
dbfull()->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -47,7 +47,7 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
return options;
|
||||
}
|
||||
|
||||
void InitTable(const Options& options) {
|
||||
void InitTable(const Options& /*options*/) {
|
||||
std::string value(kValueSize, 'a');
|
||||
for (size_t i = 0; i < kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(ToString(i), value.c_str()));
|
||||
@@ -111,6 +111,31 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) {
|
||||
ReadOptions read_options;
|
||||
read_options.fill_cache = false;
|
||||
auto table_options = GetTableOptions();
|
||||
auto options = GetOptions(table_options);
|
||||
InitTable(options);
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
table_options.block_cache = cache;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
RecordCacheCounters(options);
|
||||
|
||||
std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
|
||||
Iterator* iter = nullptr;
|
||||
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(0));
|
||||
ASSERT_LT(0, cache->GetUsage());
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
ASSERT_EQ(0, cache->GetUsage());
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
|
||||
ReadOptions read_options;
|
||||
auto table_options = GetTableOptions();
|
||||
|
||||
+109
-81
@@ -24,11 +24,41 @@ class DBTestCompactionFilter : public DBTestBase {
|
||||
DBTestCompactionFilter() : DBTestBase("/db_compaction_filter_test") {}
|
||||
};
|
||||
|
||||
// Param variant of DBTestBase::ChangeCompactOptions
|
||||
class DBTestCompactionFilterWithCompactParam
|
||||
: public DBTestCompactionFilter,
|
||||
public ::testing::WithParamInterface<DBTestBase::OptionConfig> {
|
||||
public:
|
||||
DBTestCompactionFilterWithCompactParam() : DBTestCompactionFilter() {
|
||||
option_config_ = GetParam();
|
||||
Destroy(last_options_);
|
||||
auto options = CurrentOptions();
|
||||
if (option_config_ == kDefault || option_config_ == kUniversalCompaction ||
|
||||
option_config_ == kUniversalCompactionMultiLevel) {
|
||||
options.create_if_missing = true;
|
||||
}
|
||||
if (option_config_ == kLevelSubcompactions ||
|
||||
option_config_ == kUniversalSubcompactions) {
|
||||
assert(options.max_subcompactions > 1);
|
||||
}
|
||||
TryReopen(options);
|
||||
}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
DBTestCompactionFilterWithCompactOption,
|
||||
DBTestCompactionFilterWithCompactParam,
|
||||
::testing::Values(DBTestBase::OptionConfig::kDefault,
|
||||
DBTestBase::OptionConfig::kUniversalCompaction,
|
||||
DBTestBase::OptionConfig::kUniversalCompactionMultiLevel,
|
||||
DBTestBase::OptionConfig::kLevelSubcompactions,
|
||||
DBTestBase::OptionConfig::kUniversalSubcompactions));
|
||||
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value, bool* value_changed) const
|
||||
override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
cfilter_count++;
|
||||
return false;
|
||||
}
|
||||
@@ -38,9 +68,9 @@ class KeepFilter : public CompactionFilter {
|
||||
|
||||
class DeleteFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value, bool* value_changed) const
|
||||
override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
cfilter_count++;
|
||||
return true;
|
||||
}
|
||||
@@ -50,9 +80,9 @@ class DeleteFilter : public CompactionFilter {
|
||||
|
||||
class DeleteISFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
virtual bool Filter(int /*level*/, const Slice& key, const Slice& /*value*/,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
cfilter_count++;
|
||||
int i = std::stoi(key.ToString());
|
||||
if (i > 5 && i <= 105) {
|
||||
@@ -70,14 +100,16 @@ class DeleteISFilter : public CompactionFilter {
|
||||
// zero-padded to length 10.
|
||||
class SkipEvenFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual Decision FilterV2(int level, const Slice& key, ValueType value_type,
|
||||
const Slice& existing_value, std::string* new_value,
|
||||
virtual Decision FilterV2(int /*level*/, const Slice& key,
|
||||
ValueType /*value_type*/,
|
||||
const Slice& /*existing_value*/,
|
||||
std::string* /*new_value*/,
|
||||
std::string* skip_until) const override {
|
||||
cfilter_count++;
|
||||
int i = std::stoi(key.ToString());
|
||||
if (i / 10 % 2 == 0) {
|
||||
char key_str[100];
|
||||
snprintf(key_str, sizeof(key), "%010d", i / 10 * 10 + 10);
|
||||
snprintf(key_str, sizeof(key_str), "%010d", i / 10 * 10 + 10);
|
||||
*skip_until = key_str;
|
||||
++cfilter_skips;
|
||||
return Decision::kRemoveAndSkipUntil;
|
||||
@@ -93,9 +125,9 @@ class SkipEvenFilter : public CompactionFilter {
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
db_test->env_->addon_time_.fetch_add(1000);
|
||||
return true;
|
||||
}
|
||||
@@ -110,9 +142,9 @@ class ConditionalFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ConditionalFilter(const std::string* filtered_value)
|
||||
: filtered_value_(filtered_value) {}
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/, const Slice& value,
|
||||
std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
return value.ToString() == *filtered_value_;
|
||||
}
|
||||
|
||||
@@ -126,9 +158,9 @@ class ChangeFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ChangeFilter() {}
|
||||
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value, bool* value_changed) const
|
||||
override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
assert(new_value != nullptr);
|
||||
*new_value = NEW_VALUE;
|
||||
*value_changed = true;
|
||||
@@ -217,7 +249,7 @@ class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
|
||||
}
|
||||
|
||||
@@ -233,7 +265,7 @@ class ConditionalFilterFactory : public CompactionFilterFactory {
|
||||
: filtered_value_(filtered_value.ToString()) {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(
|
||||
new ConditionalFilter(&filtered_value_));
|
||||
}
|
||||
@@ -251,7 +283,7 @@ class ChangeFilterFactory : public CompactionFilterFactory {
|
||||
explicit ChangeFilterFactory() {}
|
||||
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(new ChangeFilter());
|
||||
}
|
||||
|
||||
@@ -308,7 +340,6 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ikey.sequence = -1;
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
total++;
|
||||
if (ikey.sequence != 0) {
|
||||
@@ -440,65 +471,63 @@ TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterWithValueChange) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 3;
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<ChangeFilterFactory>();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
TEST_P(DBTestCompactionFilterWithCompactParam,
|
||||
CompactionFilterWithValueChange) {
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 3;
|
||||
options.compaction_filter_factory = std::make_shared<ChangeFilterFactory>();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Write 100K+1 keys, these are written to a few files
|
||||
// in L0. We do this so that the current snapshot points
|
||||
// to the 100001 key.The compaction filter is not invoked
|
||||
// on keys that are visible via a snapshot because we
|
||||
// anyways cannot delete it.
|
||||
const std::string value(10, 'x');
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
}
|
||||
// Write 100K+1 keys, these are written to a few files
|
||||
// in L0. We do this so that the current snapshot points
|
||||
// to the 100001 key.The compaction filter is not invoked
|
||||
// on keys that are visible via a snapshot because we
|
||||
// anyways cannot delete it.
|
||||
const std::string value(10, 'x');
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
}
|
||||
|
||||
// push all files to lower levels
|
||||
ASSERT_OK(Flush(1));
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
}
|
||||
// push all files to lower levels
|
||||
ASSERT_OK(Flush(1));
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
// re-write all data again
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
}
|
||||
// re-write all data again
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
}
|
||||
|
||||
// push all files to lower levels. This should
|
||||
// invoke the compaction filter for all 100000 keys.
|
||||
ASSERT_OK(Flush(1));
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
}
|
||||
// push all files to lower levels. This should
|
||||
// invoke the compaction filter for all 100000 keys.
|
||||
ASSERT_OK(Flush(1));
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
// verify that all keys now have the new value that
|
||||
// was set by the compaction process.
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
std::string newvalue = Get(1, key);
|
||||
ASSERT_EQ(newvalue.compare(NEW_VALUE), 0);
|
||||
}
|
||||
} while (ChangeCompactOptions());
|
||||
// verify that all keys now have the new value that
|
||||
// was set by the compaction process.
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
std::string newvalue = Get(1, key);
|
||||
ASSERT_EQ(newvalue.compare(NEW_VALUE), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
|
||||
@@ -617,7 +646,6 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ikey.sequence = -1;
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
total++;
|
||||
if (ikey.sequence != 0) {
|
||||
@@ -739,7 +767,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_EQ(count, 6);
|
||||
read_options.snapshot = 0;
|
||||
read_options.snapshot = nullptr;
|
||||
std::unique_ptr<Iterator> iter1(db_->NewIterator(read_options));
|
||||
iter1->SeekToFirst();
|
||||
count = 0;
|
||||
|
||||
+653
-1
@@ -53,7 +53,7 @@ class FlushedFileCollector : public EventListener {
|
||||
FlushedFileCollector() {}
|
||||
~FlushedFileCollector() {}
|
||||
|
||||
virtual void OnFlushCompleted(DB* db, const FlushJobInfo& info) override {
|
||||
virtual void OnFlushCompleted(DB* /*db*/, const FlushJobInfo& info) override {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
flushed_files_.push_back(info.file_path);
|
||||
}
|
||||
@@ -218,6 +218,84 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, CompactionsPreserveDeletes) {
|
||||
// For each options type we test following
|
||||
// - Enable preserve_deletes
|
||||
// - write bunch of keys and deletes
|
||||
// - Set start_seqnum to the beginning; compact; check that keys are present
|
||||
// - rewind start_seqnum way forward; compact; check that keys are gone
|
||||
|
||||
for (int tid = 0; tid < 3; ++tid) {
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.preserve_deletes=true;
|
||||
options.num_levels = 2;
|
||||
|
||||
if (tid == 1) {
|
||||
options.skip_stats_update_on_db_open = true;
|
||||
} else if (tid == 2) {
|
||||
// third pass with universal compaction
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
}
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
// highlight the default; all deletes should be preserved
|
||||
SetPreserveDeletesSequenceNumber(0);
|
||||
|
||||
const int kTestSize = kCDTKeysPerBuffer;
|
||||
std::vector<std::string> values;
|
||||
for (int k = 0; k < kTestSize; ++k) {
|
||||
values.push_back(RandomString(&rnd, kCDTValueSize));
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
|
||||
for (int k = 0; k < kTestSize; ++k) {
|
||||
ASSERT_OK(Delete(Key(k)));
|
||||
}
|
||||
// to ensure we tackle all tombstones
|
||||
CompactRangeOptions cro;
|
||||
cro.change_level = true;
|
||||
cro.target_level = 2;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
|
||||
// check that normal user iterator doesn't see anything
|
||||
Iterator* db_iter = dbfull()->NewIterator(ReadOptions());
|
||||
int i = 0;
|
||||
for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
|
||||
i++;
|
||||
}
|
||||
ASSERT_EQ(i, 0);
|
||||
delete db_iter;
|
||||
|
||||
// check that iterator that sees internal keys sees tombstones
|
||||
ReadOptions ro;
|
||||
ro.iter_start_seqnum=1;
|
||||
db_iter = dbfull()->NewIterator(ro);
|
||||
i = 0;
|
||||
for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
|
||||
i++;
|
||||
}
|
||||
ASSERT_EQ(i, 4);
|
||||
delete db_iter;
|
||||
|
||||
// now all deletes should be gone
|
||||
SetPreserveDeletesSequenceNumber(100000000);
|
||||
dbfull()->CompactRange(cro, nullptr, nullptr);
|
||||
|
||||
db_iter = dbfull()->NewIterator(ro);
|
||||
i = 0;
|
||||
for (db_iter->SeekToFirst(); db_iter->Valid(); db_iter->Next()) {
|
||||
i++;
|
||||
}
|
||||
ASSERT_EQ(i, 0);
|
||||
delete db_iter;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
// This test verify UpdateAccumulatedStats is not on
|
||||
// if options.skip_stats_update_on_db_open = true
|
||||
@@ -1439,6 +1517,176 @@ TEST_F(DBCompactionTest, DeleteFileRange) {
|
||||
ASSERT_GT(old_num_files, new_num_files);
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DeleteFilesInRanges) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 10 * 1024 * 1024;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.num_levels = 4;
|
||||
options.max_background_compactions = 3;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
int32_t value_size = 10 * 1024; // 10 KB
|
||||
|
||||
Random rnd(301);
|
||||
std::map<int32_t, std::string> values;
|
||||
|
||||
// file [0 => 100), [100 => 200), ... [900, 1000)
|
||||
for (auto i = 0; i < 10; i++) {
|
||||
for (auto j = 0; j < 100; j++) {
|
||||
auto k = i * 100 + j;
|
||||
values[k] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("10", FilesPerLevel(0));
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,0,10", FilesPerLevel(0));
|
||||
|
||||
// file [0 => 100), [200 => 300), ... [800, 900)
|
||||
for (auto i = 0; i < 10; i+=2) {
|
||||
for (auto j = 0; j < 100; j++) {
|
||||
auto k = i * 100 + j;
|
||||
ASSERT_OK(Put(Key(k), values[k]));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("5,0,10", FilesPerLevel(0));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr));
|
||||
ASSERT_EQ("0,5,10", FilesPerLevel(0));
|
||||
|
||||
// Delete files in range [0, 299] (inclusive)
|
||||
{
|
||||
auto begin_str1 = Key(0), end_str1 = Key(100);
|
||||
auto begin_str2 = Key(100), end_str2 = Key(200);
|
||||
auto begin_str3 = Key(200), end_str3 = Key(299);
|
||||
Slice begin1(begin_str1), end1(end_str1);
|
||||
Slice begin2(begin_str2), end2(end_str2);
|
||||
Slice begin3(begin_str3), end3(end_str3);
|
||||
std::vector<RangePtr> ranges;
|
||||
ranges.push_back(RangePtr(&begin1, &end1));
|
||||
ranges.push_back(RangePtr(&begin2, &end2));
|
||||
ranges.push_back(RangePtr(&begin3, &end3));
|
||||
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
|
||||
ranges.data(), ranges.size()));
|
||||
ASSERT_EQ("0,3,7", FilesPerLevel(0));
|
||||
|
||||
// Keys [0, 300) should not exist.
|
||||
for (auto i = 0; i < 300; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
for (auto i = 300; i < 1000; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete files in range [600, 999) (exclusive)
|
||||
{
|
||||
auto begin_str1 = Key(600), end_str1 = Key(800);
|
||||
auto begin_str2 = Key(700), end_str2 = Key(900);
|
||||
auto begin_str3 = Key(800), end_str3 = Key(999);
|
||||
Slice begin1(begin_str1), end1(end_str1);
|
||||
Slice begin2(begin_str2), end2(end_str2);
|
||||
Slice begin3(begin_str3), end3(end_str3);
|
||||
std::vector<RangePtr> ranges;
|
||||
ranges.push_back(RangePtr(&begin1, &end1));
|
||||
ranges.push_back(RangePtr(&begin2, &end2));
|
||||
ranges.push_back(RangePtr(&begin3, &end3));
|
||||
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(),
|
||||
ranges.data(), ranges.size(), false));
|
||||
ASSERT_EQ("0,1,4", FilesPerLevel(0));
|
||||
|
||||
// Keys [600, 900) should not exist.
|
||||
for (auto i = 600; i < 900; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
for (auto i = 300; i < 600; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
for (auto i = 900; i < 1000; i++) {
|
||||
ASSERT_EQ(Get(Key(i)), values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all files.
|
||||
{
|
||||
RangePtr range;
|
||||
ASSERT_OK(DeleteFilesInRanges(db_, db_->DefaultColumnFamily(), &range, 1));
|
||||
ASSERT_EQ("", FilesPerLevel(0));
|
||||
|
||||
for (auto i = 0; i < 1000; i++) {
|
||||
ReadOptions ropts;
|
||||
std::string result;
|
||||
auto s = db_->Get(ropts, Key(i), &result);
|
||||
ASSERT_TRUE(s.IsNotFound());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, DeleteFileRangeFileEndpointsOverlapBug) {
|
||||
// regression test for #2833: groups of files whose user-keys overlap at the
|
||||
// endpoints could be split by `DeleteFilesInRange`. This caused old data to
|
||||
// reappear, either because a new version of the key was removed, or a range
|
||||
// deletion was partially dropped. It could also cause non-overlapping
|
||||
// invariant to be violated if the files dropped by DeleteFilesInRange were
|
||||
// a subset of files that a range deletion spans.
|
||||
const int kNumL0Files = 2;
|
||||
const int kValSize = 8 << 10; // 8KB
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files;
|
||||
options.target_file_size_base = 1 << 10; // 1KB
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// The snapshot prevents key 1 from having its old version dropped. The low
|
||||
// `target_file_size_base` ensures two keys will be in each output file.
|
||||
const Snapshot* snapshot = nullptr;
|
||||
Random rnd(301);
|
||||
// The value indicates which flush the key belonged to, which is enough
|
||||
// for us to determine the keys' relative ages. After L0 flushes finish,
|
||||
// files look like:
|
||||
//
|
||||
// File 0: 0 -> vals[0], 1 -> vals[0]
|
||||
// File 1: 1 -> vals[1], 2 -> vals[1]
|
||||
//
|
||||
// Then L0->L1 compaction happens, which outputs keys as follows:
|
||||
//
|
||||
// File 0: 0 -> vals[0], 1 -> vals[1]
|
||||
// File 1: 1 -> vals[0], 2 -> vals[1]
|
||||
//
|
||||
// DeleteFilesInRange shouldn't be allowed to drop just file 0, as that
|
||||
// would cause `1 -> vals[0]` (an older key) to reappear.
|
||||
std::string vals[kNumL0Files];
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
vals[i] = RandomString(&rnd, kValSize);
|
||||
Put(Key(i), vals[i]);
|
||||
Put(Key(i + 1), vals[i]);
|
||||
Flush();
|
||||
if (i == 0) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
}
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
// Verify `DeleteFilesInRange` can't drop only file 0 which would cause
|
||||
// "1 -> vals[0]" to reappear.
|
||||
std::string begin_str = Key(0), end_str = Key(1);
|
||||
Slice begin = begin_str, end = end_str;
|
||||
ASSERT_OK(DeleteFilesInRange(db_, db_->DefaultColumnFamily(), &begin, &end));
|
||||
ASSERT_EQ(vals[1], Get(Key(1)));
|
||||
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, TrivialMoveToLastLevelWithFiles) {
|
||||
int32_t trivial_move = 0;
|
||||
int32_t non_trivial_move = 0;
|
||||
@@ -2503,6 +2751,7 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) {
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.target_file_size_base = 100000000;
|
||||
options.write_buffer_size = 100000000;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
DestroyAndReopen(options);
|
||||
@@ -2724,6 +2973,409 @@ TEST_F(DBCompactionTest, OptimizedDeletionObsoleting) {
|
||||
options.statistics->getTickerCount(COMPACTION_KEY_DROP_OBSOLETE));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactFilesPendingL0Bug) {
|
||||
// https://www.facebook.com/groups/rocksdb.dev/permalink/1389452781153232/
|
||||
// CompactFiles() had a bug where it failed to pick a compaction when an L0
|
||||
// compaction existed, but marked it as scheduled anyways. It'd never be
|
||||
// unmarked as scheduled, so future compactions or DB close could hang.
|
||||
const int kNumL0Files = 5;
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0Files - 1;
|
||||
options.max_background_compactions = 2;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"LevelCompactionPicker::PickCompaction:Return",
|
||||
"DBCompactionTest::CompactFilesPendingL0Bug:Picked"},
|
||||
{"DBCompactionTest::CompactFilesPendingL0Bug:ManualCompacted",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
auto schedule_multi_compaction_token =
|
||||
dbfull()->TEST_write_controler().GetCompactionPressureToken();
|
||||
|
||||
// Files 0-3 will be included in an L0->L1 compaction.
|
||||
//
|
||||
// File 4 will be included in a call to CompactFiles() while the first
|
||||
// compaction is running.
|
||||
for (int i = 0; i < kNumL0Files - 1; ++i) {
|
||||
ASSERT_OK(Put(Key(0), "val")); // sentinel to prevent trivial move
|
||||
ASSERT_OK(Put(Key(i + 1), "val"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
TEST_SYNC_POINT("DBCompactionTest::CompactFilesPendingL0Bug:Picked");
|
||||
// file 4 flushed after 0-3 picked
|
||||
ASSERT_OK(Put(Key(kNumL0Files), "val"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// previously DB close would hang forever as this situation caused scheduled
|
||||
// compactions count to never decrement to zero.
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta);
|
||||
ASSERT_EQ(kNumL0Files, cf_meta.levels[0].files.size());
|
||||
std::vector<std::string> input_filenames;
|
||||
input_filenames.push_back(cf_meta.levels[0].files.front().name);
|
||||
ASSERT_OK(dbfull()
|
||||
->CompactFiles(CompactionOptions(), input_filenames,
|
||||
0 /* output_level */));
|
||||
TEST_SYNC_POINT("DBCompactionTest::CompactFilesPendingL0Bug:ManualCompacted");
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactFilesOverlapInL0Bug) {
|
||||
// Regression test for bug of not pulling in L0 files that overlap the user-
|
||||
// specified input files in time- and key-ranges.
|
||||
Put(Key(0), "old_val");
|
||||
Flush();
|
||||
Put(Key(0), "new_val");
|
||||
Flush();
|
||||
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
dbfull()->GetColumnFamilyMetaData(dbfull()->DefaultColumnFamily(), &cf_meta);
|
||||
ASSERT_GE(cf_meta.levels.size(), 2);
|
||||
ASSERT_EQ(2, cf_meta.levels[0].files.size());
|
||||
|
||||
// Compacting {new L0 file, L1 file} should pull in the old L0 file since it
|
||||
// overlaps in key-range and time-range.
|
||||
std::vector<std::string> input_filenames;
|
||||
input_filenames.push_back(cf_meta.levels[0].files.front().name);
|
||||
ASSERT_OK(dbfull()->CompactFiles(CompactionOptions(), input_filenames,
|
||||
1 /* output_level */));
|
||||
ASSERT_EQ("new_val", Get(Key(0)));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactBottomLevelFilesWithDeletions) {
|
||||
// bottom-level files may contain deletions due to snapshots protecting the
|
||||
// deleted keys. Once the snapshot is released, we should see files with many
|
||||
// such deletions undergo single-file compactions.
|
||||
const int kNumKeysPerFile = 1024;
|
||||
const int kNumLevelFiles = 4;
|
||||
const int kValueSize = 128;
|
||||
Options options = CurrentOptions();
|
||||
options.compression = kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = kNumLevelFiles;
|
||||
// inflate it a bit to account for key/metadata overhead
|
||||
options.target_file_size_base = 120 * kNumKeysPerFile * kValueSize / 100;
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
const Snapshot* snapshot = nullptr;
|
||||
for (int i = 0; i < kNumLevelFiles; ++i) {
|
||||
for (int j = 0; j < kNumKeysPerFile; ++j) {
|
||||
ASSERT_OK(
|
||||
Put(Key(i * kNumKeysPerFile + j), RandomString(&rnd, kValueSize)));
|
||||
}
|
||||
if (i == kNumLevelFiles - 1) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
// delete every other key after grabbing a snapshot, so these deletions
|
||||
// and the keys they cover can't be dropped until after the snapshot is
|
||||
// released.
|
||||
for (int j = 0; j < kNumLevelFiles * kNumKeysPerFile; j += 2) {
|
||||
ASSERT_OK(Delete(Key(j)));
|
||||
}
|
||||
}
|
||||
Flush();
|
||||
if (i < kNumLevelFiles - 1) {
|
||||
ASSERT_EQ(i + 1, NumTableFilesAtLevel(0));
|
||||
}
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(kNumLevelFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
std::vector<LiveFileMetaData> pre_release_metadata, post_release_metadata;
|
||||
db_->GetLiveFilesMetaData(&pre_release_metadata);
|
||||
// just need to bump seqnum so ReleaseSnapshot knows the newest key in the SST
|
||||
// files does not need to be preserved in case of a future snapshot.
|
||||
ASSERT_OK(Put(Key(0), "val"));
|
||||
// release snapshot and wait for compactions to finish. Single-file
|
||||
// compactions should be triggered, which reduce the size of each bottom-level
|
||||
// file without changing file count.
|
||||
db_->ReleaseSnapshot(snapshot);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
db_->GetLiveFilesMetaData(&post_release_metadata);
|
||||
ASSERT_EQ(pre_release_metadata.size(), post_release_metadata.size());
|
||||
|
||||
for (size_t i = 0; i < pre_release_metadata.size(); ++i) {
|
||||
const auto& pre_file = pre_release_metadata[i];
|
||||
const auto& post_file = post_release_metadata[i];
|
||||
ASSERT_EQ(1, pre_file.level);
|
||||
ASSERT_EQ(1, post_file.level);
|
||||
// each file is smaller than it was before as it was rewritten without
|
||||
// deletion markers/deleted keys.
|
||||
ASSERT_LT(post_file.size, pre_file.size);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeDelayedByL0FileCount) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual
|
||||
// compaction only triggers flush after it's sure stall won't be triggered for
|
||||
// L0 file count going too high.
|
||||
const int kNumL0FilesTrigger = 4;
|
||||
const int kNumL0FilesLimit = 8;
|
||||
// i == 0: verifies normal case where stall is avoided by delay
|
||||
// i == 1: verifies no delay in edge case where stall trigger is same as
|
||||
// compaction trigger, so stall can't be avoided
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_slowdown_writes_trigger = kNumL0FilesLimit;
|
||||
if (i == 0) {
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesTrigger;
|
||||
} else {
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesLimit;
|
||||
}
|
||||
Reopen(options);
|
||||
|
||||
if (i == 0) {
|
||||
// ensure the auto compaction doesn't finish until manual compaction has
|
||||
// had a chance to be delayed.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait", "CompactionJob::Run():End"}});
|
||||
} else {
|
||||
// ensure the auto-compaction doesn't finish until manual compaction has
|
||||
// continued without delay.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWaitDone", "CompactionJob::Run():End"}});
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int j = 0; j < kNumL0FilesLimit - 1; ++j) {
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
ASSERT_OK(Put(Key(k), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
});
|
||||
|
||||
manual_compaction_thread.join();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeDelayedByImmMemTableCount) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`, manual
|
||||
// compaction only triggers flush after it's sure stall won't be triggered for
|
||||
// immutable memtable count going too high.
|
||||
const int kNumImmMemTableLimit = 8;
|
||||
// i == 0: verifies normal case where stall is avoided by delay
|
||||
// i == 1: verifies no delay in edge case where stall trigger is same as flush
|
||||
// trigger, so stall can't be avoided
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
// the delay limit is one less than the stop limit. This test focuses on
|
||||
// avoiding delay limit, but this option sets stop limit, so add one.
|
||||
options.max_write_buffer_number = kNumImmMemTableLimit + 1;
|
||||
if (i == 1) {
|
||||
options.min_write_buffer_number_to_merge = kNumImmMemTableLimit;
|
||||
}
|
||||
Reopen(options);
|
||||
|
||||
if (i == 0) {
|
||||
// ensure the flush doesn't finish until manual compaction has had a
|
||||
// chance to be delayed.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait", "FlushJob::WriteLevel0Table"}});
|
||||
} else {
|
||||
// ensure the flush doesn't finish until manual compaction has continued
|
||||
// without delay.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWaitDone",
|
||||
"FlushJob::WriteLevel0Table"}});
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int j = 0; j < kNumImmMemTableLimit - 1; ++j) {
|
||||
ASSERT_OK(Put(Key(0), RandomString(&rnd, 1024)));
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
dbfull()->Flush(flush_opts);
|
||||
}
|
||||
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
});
|
||||
|
||||
manual_compaction_thread.join();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeShutdownWhileDelayed) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`, delay
|
||||
// does not hang if CF is dropped or DB is closed
|
||||
const int kNumL0FilesTrigger = 4;
|
||||
const int kNumL0FilesLimit = 8;
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesTrigger;
|
||||
options.level0_slowdown_writes_trigger = kNumL0FilesLimit;
|
||||
// i == 0: DB::DropColumnFamily() on CompactRange's target CF unblocks it
|
||||
// i == 1: DB::CancelAllBackgroundWork() unblocks CompactRange. This is to
|
||||
// simulate what happens during Close as we can't call Close (it
|
||||
// blocks on the auto-compaction, making a cycle).
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
CreateAndReopenWithCF({"one"}, options);
|
||||
// The calls to close CF/DB wait until the manual compaction stalls.
|
||||
// The auto-compaction waits until the manual compaction finishes to ensure
|
||||
// the signal comes from closing CF/DB, not from compaction making progress.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait",
|
||||
"DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown"},
|
||||
{"DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual",
|
||||
"CompactionJob::Run():End"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
for (int j = 0; j < kNumL0FilesLimit - 1; ++j) {
|
||||
for (int k = 0; k < 2; ++k) {
|
||||
ASSERT_OK(Put(1, Key(k), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush(1);
|
||||
}
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
ASSERT_TRUE(db_->CompactRange(cro, handles_[1], nullptr, nullptr)
|
||||
.IsShutdownInProgress());
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::CompactRangeShutdownWhileDelayed:PreShutdown");
|
||||
if (i == 0) {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[1]));
|
||||
} else {
|
||||
dbfull()->CancelAllBackgroundWork(false /* wait */);
|
||||
}
|
||||
manual_compaction_thread.join();
|
||||
TEST_SYNC_POINT(
|
||||
"DBCompactionTest::CompactRangeShutdownWhileDelayed:PostManual");
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeSkipFlushAfterDelay) {
|
||||
// Verify that, when `CompactRangeOptions::allow_write_stall == false`,
|
||||
// CompactRange skips its flush if the delay is long enough that the memtables
|
||||
// existing at the beginning of the call have already been flushed.
|
||||
const int kNumL0FilesTrigger = 4;
|
||||
const int kNumL0FilesLimit = 8;
|
||||
Options options = CurrentOptions();
|
||||
options.level0_slowdown_writes_trigger = kNumL0FilesLimit;
|
||||
options.level0_file_num_compaction_trigger = kNumL0FilesTrigger;
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
// The manual flush includes the memtable that was active when CompactRange
|
||||
// began. So it unblocks CompactRange and precludes its flush. Throughout the
|
||||
// test, stall conditions are upheld via high L0 file count.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::CompactRange:StallWait",
|
||||
"DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush"},
|
||||
{"DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush",
|
||||
"DBImpl::CompactRange:StallWaitDone"},
|
||||
{"DBImpl::CompactRange:StallWaitDone", "CompactionJob::Run():End"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
for (int i = 0; i < kNumL0FilesLimit - 1; ++i) {
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
auto manual_compaction_thread = port::Thread([this]() {
|
||||
CompactRangeOptions cro;
|
||||
cro.allow_write_stall = false;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PreFlush");
|
||||
Put(ToString(0), RandomString(&rnd, 1024));
|
||||
Flush();
|
||||
Put(ToString(0), RandomString(&rnd, 1024));
|
||||
TEST_SYNC_POINT("DBCompactionTest::CompactRangeSkipFlushAfterDelay:PostFlush");
|
||||
manual_compaction_thread.join();
|
||||
|
||||
// If CompactRange's flush was skipped, the final Put above will still be
|
||||
// in the active memtable.
|
||||
std::string num_keys_in_memtable;
|
||||
db_->GetProperty(DB::Properties::kNumEntriesActiveMemTable, &num_keys_in_memtable);
|
||||
ASSERT_EQ(ToString(1), num_keys_in_memtable);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, CompactRangeFlushOverlappingMemtable) {
|
||||
// Verify memtable only gets flushed if it contains data overlapping the range
|
||||
// provided to `CompactRange`. Tests all kinds of overlap/non-overlap.
|
||||
const int kNumEndpointKeys = 5;
|
||||
std::string keys[kNumEndpointKeys] = {"a", "b", "c", "d", "e"};
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
// One extra iteration for nullptr, which means left side of interval is
|
||||
// unbounded.
|
||||
for (int i = 0; i <= kNumEndpointKeys; ++i) {
|
||||
Slice begin;
|
||||
Slice* begin_ptr;
|
||||
if (i == 0) {
|
||||
begin_ptr = nullptr;
|
||||
} else {
|
||||
begin = keys[i - 1];
|
||||
begin_ptr = &begin;
|
||||
}
|
||||
// Start at `i` so right endpoint comes after left endpoint. One extra
|
||||
// iteration for nullptr, which means right side of interval is unbounded.
|
||||
for (int j = std::max(0, i - 1); j <= kNumEndpointKeys; ++j) {
|
||||
Slice end;
|
||||
Slice* end_ptr;
|
||||
if (j == kNumEndpointKeys) {
|
||||
end_ptr = nullptr;
|
||||
} else {
|
||||
end = keys[j];
|
||||
end_ptr = &end;
|
||||
}
|
||||
ASSERT_OK(Put("b", "val"));
|
||||
ASSERT_OK(Put("d", "val"));
|
||||
CompactRangeOptions compact_range_opts;
|
||||
ASSERT_OK(db_->CompactRange(compact_range_opts, begin_ptr, end_ptr));
|
||||
|
||||
uint64_t get_prop_tmp, num_memtable_entries = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesImmMemTables,
|
||||
&get_prop_tmp));
|
||||
num_memtable_entries += get_prop_tmp;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kNumEntriesActiveMemTable,
|
||||
&get_prop_tmp));
|
||||
num_memtable_entries += get_prop_tmp;
|
||||
if (begin_ptr == nullptr || end_ptr == nullptr ||
|
||||
(i <= 4 && j >= 1 && (begin != "c" || end != "c"))) {
|
||||
// In this case `CompactRange`'s range overlapped in some way with the
|
||||
// memtable's range, so flush should've happened. Then "b" and "d" won't
|
||||
// be in the memtable.
|
||||
ASSERT_EQ(0, num_memtable_entries);
|
||||
} else {
|
||||
ASSERT_EQ(2, num_memtable_entries);
|
||||
// flush anyways to prepare for next iteration
|
||||
db_->Flush(FlushOptions());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBCompactionTestWithParam, DBCompactionTestWithParam,
|
||||
::testing::Values(std::make_tuple(1, true),
|
||||
std::make_tuple(1, false),
|
||||
|
||||
+15
-2
@@ -57,6 +57,7 @@ Status DBImpl::EnableFileDeletions(bool force) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "File Deletions Enabled");
|
||||
should_purge_files = true;
|
||||
FindObsoleteFiles(&job_context, true);
|
||||
bg_cv_.SignalAll();
|
||||
} else {
|
||||
ROCKS_LOG_WARN(
|
||||
immutable_db_options_.info_log,
|
||||
@@ -73,7 +74,7 @@ Status DBImpl::EnableFileDeletions(bool force) {
|
||||
}
|
||||
|
||||
int DBImpl::IsFileDeletionsEnabled() const {
|
||||
return disable_delete_obsolete_files_;
|
||||
return !disable_delete_obsolete_files_;
|
||||
}
|
||||
|
||||
Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
@@ -92,7 +93,7 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
cfd->Ref();
|
||||
mutex_.Unlock();
|
||||
status = FlushMemTable(cfd, FlushOptions());
|
||||
status = FlushMemTable(cfd, FlushOptions(), FlushReason::kGetLiveFiles);
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:1");
|
||||
TEST_SYNC_POINT("DBImpl::GetLiveFiles:2");
|
||||
mutex_.Lock();
|
||||
@@ -141,6 +142,18 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
|
||||
Status DBImpl::GetSortedWalFiles(VectorLogPtr& files) {
|
||||
{
|
||||
// If caller disabled deletions, this function should return files that are
|
||||
// guaranteed not to be deleted until deletions are re-enabled. We need to
|
||||
// wait for pending purges to finish since WalManager doesn't know which
|
||||
// files are going to be purged. Additional purges won't be scheduled as
|
||||
// long as deletions are disabled (so the below loop must terminate).
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (disable_delete_obsolete_files_ > 0 &&
|
||||
pending_purge_obsolete_files_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
return wal_manager_.GetSortedWalFiles(files);
|
||||
}
|
||||
|
||||
|
||||
@@ -126,6 +126,41 @@ TEST_F(DBFlushTest, FlushInLowPriThreadPool) {
|
||||
ASSERT_EQ(1, num_compactions);
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, ManualFlushWithMinWriteBufferNumberToMerge) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100;
|
||||
options.max_write_buffer_number = 4;
|
||||
options.min_write_buffer_number_to_merge = 3;
|
||||
Reopen(options);
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BGWorkFlush",
|
||||
"DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:1"},
|
||||
{"DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:2",
|
||||
"FlushJob::WriteLevel0Table"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("key1", "value1"));
|
||||
|
||||
port::Thread t([&]() {
|
||||
// The call wait for flush to finish, i.e. with flush_options.wait = true.
|
||||
ASSERT_OK(Flush());
|
||||
});
|
||||
|
||||
// Wait for flush start.
|
||||
TEST_SYNC_POINT("DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:1");
|
||||
// Insert a second memtable before the manual flush finish.
|
||||
// At the end of the manual flush job, it will check if further flush
|
||||
// is needed, but it will not trigger flush of the second memtable because
|
||||
// min_write_buffer_number_to_merge is not reached.
|
||||
ASSERT_OK(Put("key2", "value2"));
|
||||
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
|
||||
TEST_SYNC_POINT("DBFlushTest::ManualFlushWithMinWriteBufferNumberToMerge:2");
|
||||
|
||||
// Manual flush should return, without waiting for flush indefinitely.
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST_P(DBFlushDirectIOTest, DirectIO) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
@@ -150,6 +185,26 @@ TEST_P(DBFlushDirectIOTest, DirectIO) {
|
||||
delete options.env;
|
||||
}
|
||||
|
||||
TEST_F(DBFlushTest, FlushError) {
|
||||
Options options;
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
options.write_buffer_size = 100;
|
||||
options.max_write_buffer_number = 4;
|
||||
options.min_write_buffer_number_to_merge = 3;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = fault_injection_env.get();
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("key1", "value1"));
|
||||
ASSERT_OK(Put("key2", "value2"));
|
||||
fault_injection_env->SetFilesystemActive(false);
|
||||
Status s = dbfull()->TEST_SwitchMemtable();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
Destroy(options);
|
||||
ASSERT_NE(s, Status::OK());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBFlushDirectIOTest, DBFlushDirectIOTest,
|
||||
testing::Bool());
|
||||
|
||||
|
||||
+373
-223
File diff suppressed because it is too large.
Load diff
+232
-76
@@ -14,7 +14,6 @@
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -28,6 +27,9 @@
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/pre_release_callback.h"
|
||||
#include "db/read_callback.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "db/snapshot_impl.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "db/wal_manager.h"
|
||||
@@ -52,12 +54,13 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class Arena;
|
||||
class ArenaWrappedDBIter;
|
||||
class MemTable;
|
||||
class TableCache;
|
||||
class Version;
|
||||
class VersionEdit;
|
||||
class VersionSet;
|
||||
class Arena;
|
||||
class WriteCallback;
|
||||
struct JobContext;
|
||||
struct ExternalSstFileInfo;
|
||||
@@ -65,7 +68,8 @@ struct MemTableInfo;
|
||||
|
||||
class DBImpl : public DB {
|
||||
public:
|
||||
DBImpl(const DBOptions& options, const std::string& dbname);
|
||||
DBImpl(const DBOptions& options, const std::string& dbname,
|
||||
const bool seq_per_batch = false);
|
||||
virtual ~DBImpl();
|
||||
|
||||
// Implementations of the DB interface
|
||||
@@ -93,6 +97,14 @@ class DBImpl : public DB {
|
||||
virtual Status Get(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
PinnableSlice* value) override;
|
||||
|
||||
// Function that Get and KeyMayExist call with no_io true or false
|
||||
// Note: 'value_found' from KeyMayExist propagates here
|
||||
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
bool* value_found = nullptr, ReadCallback* callback = nullptr,
|
||||
bool* is_blob_index = nullptr);
|
||||
|
||||
using DB::MultiGet;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
@@ -123,6 +135,7 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value,
|
||||
bool* value_found = nullptr) override;
|
||||
|
||||
using DB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family) override;
|
||||
@@ -130,15 +143,22 @@ class DBImpl : public DB {
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
ArenaWrappedDBIter* NewIteratorImpl(const ReadOptions& options,
|
||||
ColumnFamilyData* cfd,
|
||||
SequenceNumber snapshot,
|
||||
ReadCallback* read_callback,
|
||||
bool allow_blob = false,
|
||||
bool allow_refresh = true);
|
||||
|
||||
virtual const Snapshot* GetSnapshot() override;
|
||||
virtual void ReleaseSnapshot(const Snapshot* snapshot) override;
|
||||
using DB::GetProperty;
|
||||
virtual bool GetProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, std::string* value) override;
|
||||
using DB::GetMapProperty;
|
||||
virtual bool GetMapProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property,
|
||||
std::map<std::string, double>* value) override;
|
||||
virtual bool GetMapProperty(
|
||||
ColumnFamilyHandle* column_family, const Slice& property,
|
||||
std::map<std::string, std::string>* value) override;
|
||||
using DB::GetIntProperty;
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) override;
|
||||
@@ -165,7 +185,9 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
const int output_level,
|
||||
const int output_path_id = -1) override;
|
||||
const int output_path_id = -1,
|
||||
std::vector<std::string>* const output_file_names
|
||||
= nullptr) override;
|
||||
|
||||
virtual Status PauseBackgroundWork() override;
|
||||
virtual Status ContinueBackgroundWork() override;
|
||||
@@ -201,8 +223,15 @@ class DBImpl : public DB {
|
||||
virtual Status SyncWAL() override;
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override;
|
||||
// REQUIRES: joined the main write queue if two_write_queues is disabled, and
|
||||
// the second write queue otherwise.
|
||||
virtual void SetLastPublishedSequence(SequenceNumber seq);
|
||||
// Returns LastSequence in last_seq_same_as_publish_seq_
|
||||
// mode and LastAllocatedSequence otherwise. This is useful when visiblility
|
||||
// depends also on data written to the WAL but not to the memtable.
|
||||
SequenceNumber TEST_GetLastVisibleSequence() const;
|
||||
|
||||
bool HasActiveSnapshotLaterThanSN(SequenceNumber sn);
|
||||
virtual bool SetPreserveDeletesSequenceNumber(SequenceNumber seqnum) override;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::ResetStats;
|
||||
@@ -221,8 +250,9 @@ class DBImpl : public DB {
|
||||
const TransactionLogIterator::ReadOptions&
|
||||
read_options = TransactionLogIterator::ReadOptions()) override;
|
||||
virtual Status DeleteFile(std::string name) override;
|
||||
Status DeleteFilesInRange(ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end);
|
||||
Status DeleteFilesInRanges(ColumnFamilyHandle* column_family,
|
||||
const RangePtr* ranges, size_t n,
|
||||
bool include_end = true);
|
||||
|
||||
virtual void GetLiveFilesMetaData(
|
||||
std::vector<LiveFileMetaData>* metadata) override;
|
||||
@@ -285,7 +315,8 @@ class DBImpl : public DB {
|
||||
// TODO(andrewkr): this API need to be aware of range deletion operations
|
||||
Status GetLatestSequenceForKey(SuperVersion* sv, const Slice& key,
|
||||
bool cache_only, SequenceNumber* seq,
|
||||
bool* found_record_for_key);
|
||||
bool* found_record_for_key,
|
||||
bool* is_blob_index = nullptr);
|
||||
|
||||
using DB::IngestExternalFile;
|
||||
virtual Status IngestExternalFile(
|
||||
@@ -331,7 +362,7 @@ class DBImpl : public DB {
|
||||
ColumnFamilyHandle* column_family = nullptr,
|
||||
bool disallow_trivial_move = false);
|
||||
|
||||
void TEST_HandleWALFull();
|
||||
void TEST_SwitchWAL();
|
||||
|
||||
bool TEST_UnableToFlushOldestLog() {
|
||||
return unable_to_flush_oldest_log_;
|
||||
@@ -341,6 +372,8 @@ class DBImpl : public DB {
|
||||
return alive_log_files_.begin()->getting_flushed;
|
||||
}
|
||||
|
||||
Status TEST_SwitchMemtable(ColumnFamilyData* cfd = nullptr);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status TEST_FlushMemTable(bool wait = true,
|
||||
ColumnFamilyHandle* cfh = nullptr);
|
||||
@@ -349,7 +382,9 @@ class DBImpl : public DB {
|
||||
Status TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family = nullptr);
|
||||
|
||||
// Wait for any compaction
|
||||
Status TEST_WaitForCompact();
|
||||
// We add a bool parameter to wait for unscheduledCompactions_ == 0, but this
|
||||
// is only for the special test of CancelledCompactions
|
||||
Status TEST_WaitForCompact(bool waitUnscheduled = false);
|
||||
|
||||
// Return the maximum overlapping data (in bytes) at next level for any
|
||||
// file at a level >= 1.
|
||||
@@ -359,6 +394,9 @@ class DBImpl : public DB {
|
||||
// Return the current manifest file no.
|
||||
uint64_t TEST_Current_Manifest_FileNo();
|
||||
|
||||
// Returns the number that'll be assigned to the next file that's created.
|
||||
uint64_t TEST_Current_Next_FileNo();
|
||||
|
||||
// get total level0 file size. Only for testing.
|
||||
uint64_t TEST_GetLevel0TotalSize();
|
||||
|
||||
@@ -400,6 +438,8 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t TEST_FindMinLogContainingOutstandingPrep();
|
||||
uint64_t TEST_FindMinPrepLogReferencedByMemTable();
|
||||
size_t TEST_PreparedSectionCompletedSize();
|
||||
size_t TEST_LogsWithPrepSize();
|
||||
|
||||
int TEST_BGCompactionsAllowed() const;
|
||||
int TEST_BGFlushesAllowed() const;
|
||||
@@ -433,9 +473,11 @@ class DBImpl : public DB {
|
||||
bool no_full_scan = false);
|
||||
|
||||
// Diffs the files listed in filenames and those that do not
|
||||
// belong to live files are posibly removed. Also, removes all the
|
||||
// belong to live files are possibly removed. Also, removes all the
|
||||
// files in sst_delete_files and log_delete_files.
|
||||
// It is not necessary to hold the mutex when invoking this method.
|
||||
// If FindObsoleteFiles() was run, we need to also run
|
||||
// PurgeObsoleteFiles(), even if disable_delete_obsolete_files_ is true
|
||||
void PurgeObsoleteFiles(const JobContext& background_contet,
|
||||
bool schedule_only = false);
|
||||
|
||||
@@ -462,6 +504,9 @@ class DBImpl : public DB {
|
||||
// mutex is held.
|
||||
SuperVersion* GetAndRefSuperVersion(uint32_t column_family_id);
|
||||
|
||||
// Un-reference the super version and clean it up if it is the last reference.
|
||||
void CleanupSuperVersion(SuperVersion* sv);
|
||||
|
||||
// Un-reference the super version and return it to thread local cache if
|
||||
// needed. If it is the last reference of the super version. Clean it up
|
||||
// after un-referencing it.
|
||||
@@ -509,9 +554,20 @@ class DBImpl : public DB {
|
||||
uint64_t log_number_;
|
||||
std::string name_;
|
||||
WriteBatch* batch_;
|
||||
// The seq number of the first key in the batch
|
||||
SequenceNumber seq_;
|
||||
// Number of sub-batched. A new sub-batch is created if we txn attempts to
|
||||
// inserts a duplicate key,seq to memtable. This is currently used in
|
||||
// WritePrparedTxn
|
||||
size_t batch_cnt_;
|
||||
explicit RecoveredTransaction(const uint64_t log, const std::string& name,
|
||||
WriteBatch* batch)
|
||||
: log_number_(log), name_(name), batch_(batch) {}
|
||||
WriteBatch* batch, SequenceNumber seq,
|
||||
size_t batch_cnt)
|
||||
: log_number_(log),
|
||||
name_(name),
|
||||
batch_(batch),
|
||||
seq_(seq),
|
||||
batch_cnt_(batch_cnt) {}
|
||||
|
||||
~RecoveredTransaction() { delete batch_; }
|
||||
};
|
||||
@@ -533,8 +589,10 @@ class DBImpl : public DB {
|
||||
}
|
||||
|
||||
void InsertRecoveredTransaction(const uint64_t log, const std::string& name,
|
||||
WriteBatch* batch) {
|
||||
recovered_transactions_[name] = new RecoveredTransaction(log, name, batch);
|
||||
WriteBatch* batch, SequenceNumber seq,
|
||||
size_t batch_cnt) {
|
||||
recovered_transactions_[name] =
|
||||
new RecoveredTransaction(log, name, batch, seq, batch_cnt);
|
||||
MarkLogAsContainingPrepSection(log);
|
||||
}
|
||||
|
||||
@@ -560,14 +618,27 @@ class DBImpl : public DB {
|
||||
void AddToLogsToFreeQueue(log::Writer* log_writer) {
|
||||
logs_to_free_queue_.push_back(log_writer);
|
||||
}
|
||||
|
||||
void SetSnapshotChecker(SnapshotChecker* snapshot_checker);
|
||||
|
||||
InstrumentedMutex* mutex() { return &mutex_; }
|
||||
|
||||
Status NewDB();
|
||||
|
||||
// This is to be used only by internal rocksdb classes.
|
||||
static Status Open(const DBOptions& db_options, const std::string& name,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
const bool seq_per_batch);
|
||||
|
||||
virtual Status Close() override;
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
unique_ptr<VersionSet> versions_;
|
||||
// Flag to check whether we allocated and own the info log file
|
||||
bool own_info_log_;
|
||||
const DBOptions initial_db_options_;
|
||||
const ImmutableDBOptions immutable_db_options_;
|
||||
MutableDBOptions mutable_db_options_;
|
||||
@@ -613,10 +684,21 @@ class DBImpl : public DB {
|
||||
|
||||
void EraseThreadStatusDbInfo() const;
|
||||
|
||||
// If disable_memtable is set the application logic must guarantee that the
|
||||
// batch will still be skipped from memtable during the recovery. In
|
||||
// WriteCommitted it is guarnateed since disable_memtable is used for prepare
|
||||
// batch which will be written to memtable later during the commit, and in
|
||||
// WritePrepared it is guaranteed since it will be used only for WAL markers
|
||||
// which will never be written to memtable.
|
||||
// batch_cnt is expected to be non-zero in seq_per_batch mode and indicates
|
||||
// the number of sub-patches. A sub-patch is a subset of the write batch that
|
||||
// does not have duplicate keys.
|
||||
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false, uint64_t* seq_used = nullptr);
|
||||
bool disable_memtable = false, uint64_t* seq_used = nullptr,
|
||||
size_t batch_cnt = 0,
|
||||
PreReleaseCallback* pre_release_callback = nullptr);
|
||||
|
||||
Status PipelinedWriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
@@ -624,13 +706,23 @@ class DBImpl : public DB {
|
||||
bool disable_memtable = false,
|
||||
uint64_t* seq_used = nullptr);
|
||||
|
||||
// batch_cnt is expected to be non-zero in seq_per_batch mode and indicates
|
||||
// the number of sub-patches. A sub-patch is a subset of the write batch that
|
||||
// does not have duplicate keys.
|
||||
Status WriteImplWALOnly(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
uint64_t* seq_used = nullptr);
|
||||
uint64_t* seq_used = nullptr, size_t batch_cnt = 0,
|
||||
PreReleaseCallback* pre_release_callback = nullptr);
|
||||
|
||||
uint64_t FindMinLogContainingOutstandingPrep();
|
||||
uint64_t FindMinPrepLogReferencedByMemTable();
|
||||
// write cached_recoverable_state_ to memtable if it is not empty
|
||||
// The writer must be the leader in write_thread_ and holding mutex_
|
||||
Status WriteRecoverableState();
|
||||
|
||||
// Actual implementation of Close()
|
||||
Status CloseImpl();
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
@@ -638,24 +730,30 @@ class DBImpl : public DB {
|
||||
friend class PessimisticTransaction;
|
||||
friend class WriteCommittedTxn;
|
||||
friend class WritePreparedTxn;
|
||||
friend class WritePreparedTxnDB;
|
||||
friend class WriteBatchWithIndex;
|
||||
#ifndef ROCKSDB_LITE
|
||||
friend class ForwardIterator;
|
||||
#endif
|
||||
friend struct SuperVersion;
|
||||
friend class CompactedDBImpl;
|
||||
#ifndef NDEBUG
|
||||
friend class DBTest2_ReadCallbackTest_Test;
|
||||
friend class WriteCallbackTest_WriteWithCallbackTest_Test;
|
||||
friend class XFTransactionWriteHandler;
|
||||
friend class DBBlobIndexTest;
|
||||
#endif
|
||||
struct CompactionState;
|
||||
|
||||
struct WriteContext {
|
||||
autovector<SuperVersion*> superversions_to_free_;
|
||||
SuperVersionContext superversion_context;
|
||||
autovector<MemTable*> memtables_to_free_;
|
||||
|
||||
explicit WriteContext(bool create_superversion = false)
|
||||
: superversion_context(create_superversion) {}
|
||||
|
||||
~WriteContext() {
|
||||
for (auto& sv : superversions_to_free_) {
|
||||
delete sv;
|
||||
}
|
||||
superversion_context.Clean();
|
||||
for (auto& m : memtables_to_free_) {
|
||||
delete m;
|
||||
}
|
||||
@@ -685,9 +783,8 @@ class DBImpl : public DB {
|
||||
// Delete any unneeded files and stale in-memory entries.
|
||||
void DeleteObsoleteFiles();
|
||||
// Delete obsolete files and log status and information of file deletion
|
||||
void DeleteObsoleteFileImpl(Status file_deletion_status, int job_id,
|
||||
const std::string& fname, FileType type,
|
||||
uint64_t number, uint32_t path_id);
|
||||
void DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
FileType type, uint64_t number, uint32_t path_id);
|
||||
|
||||
// Background process needs to call
|
||||
// auto x = CaptureCurrentFileNumberInPendingOutputs()
|
||||
@@ -738,17 +835,22 @@ class DBImpl : public DB {
|
||||
|
||||
Status ScheduleFlushes(WriteContext* context);
|
||||
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context);
|
||||
Status SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
FlushReason flush_reason = FlushReason::kOthers);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status FlushMemTable(ColumnFamilyData* cfd, const FlushOptions& options,
|
||||
bool writes_stopped = false);
|
||||
FlushReason flush_reason, bool writes_stopped = false);
|
||||
|
||||
// Wait for memtable flushed
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd);
|
||||
// Wait for memtable flushed.
|
||||
// If flush_memtable_id is non-null, wait until the memtable with the ID
|
||||
// gets flush. Otherwise, wait until the column family don't have any
|
||||
// memtable pending flush.
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
const uint64_t* flush_memtable_id = nullptr);
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
Status HandleWALFull(WriteContext* write_context);
|
||||
Status SwitchWAL(WriteContext* write_context);
|
||||
|
||||
// REQUIRES: mutex locked
|
||||
Status HandleWriteBufferFull(WriteContext* write_context);
|
||||
@@ -758,7 +860,8 @@ class DBImpl : public DB {
|
||||
WriteContext* write_context);
|
||||
|
||||
WriteBatch* MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
WriteBatch* tmp_batch, size_t* write_with_wal);
|
||||
WriteBatch* tmp_batch, size_t* write_with_wal,
|
||||
WriteBatch** to_be_cached_state);
|
||||
|
||||
Status WriteToWAL(const WriteBatch& merged_batch, log::Writer* log_writer,
|
||||
uint64_t* log_used, uint64_t* log_size);
|
||||
@@ -770,10 +873,10 @@ class DBImpl : public DB {
|
||||
|
||||
Status ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used, SequenceNumber* last_sequence,
|
||||
int total_count);
|
||||
size_t seq_inc);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ if paranoid check is enabled.
|
||||
void WriteCallbackStatusCheck(const Status& status);
|
||||
void WriteStatusCheck(const Status& status);
|
||||
|
||||
// Used by WriteImpl to update bg_error_ in case of memtable insert error.
|
||||
void MemTableInsertStatusCheck(const Status& memtable_insert_status);
|
||||
@@ -783,6 +886,7 @@ class DBImpl : public DB {
|
||||
Status CompactFilesImpl(const CompactionOptions& compact_options,
|
||||
ColumnFamilyData* cfd, Version* version,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
std::vector<std::string>* const output_file_names,
|
||||
const int output_level, int output_path_id,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
|
||||
@@ -799,7 +903,7 @@ class DBImpl : public DB {
|
||||
ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
|
||||
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
void SchedulePendingFlush(ColumnFamilyData* cfd);
|
||||
void SchedulePendingFlush(ColumnFamilyData* cfd, FlushReason flush_reason);
|
||||
void SchedulePendingCompaction(ColumnFamilyData* cfd);
|
||||
void SchedulePendingPurge(std::string fname, FileType type, uint64_t number,
|
||||
uint32_t path_id, int job_id);
|
||||
@@ -838,7 +942,7 @@ class DBImpl : public DB {
|
||||
// helper functions for adding and removing from flush & compaction queues
|
||||
void AddToCompactionQueue(ColumnFamilyData* cfd);
|
||||
ColumnFamilyData* PopFirstFromCompactionQueue();
|
||||
void AddToFlushQueue(ColumnFamilyData* cfd);
|
||||
void AddToFlushQueue(ColumnFamilyData* cfd, FlushReason flush_reason);
|
||||
ColumnFamilyData* PopFirstFromFlushQueue();
|
||||
|
||||
// helper function to call after some of the logs_ were synced
|
||||
@@ -848,6 +952,8 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t GetMaxTotalWalSize() const;
|
||||
|
||||
Status CloseHelper();
|
||||
|
||||
// table_cache_ provides its own synchronization
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
|
||||
@@ -855,12 +961,12 @@ class DBImpl : public DB {
|
||||
FileLock* db_lock_;
|
||||
|
||||
// In addition to mutex_, log_write_mutex_ protected writes to logs_ and
|
||||
// logfile_number_. With concurrent_prepare it also protects alive_log_files_,
|
||||
// logfile_number_. With two_write_queues it also protects alive_log_files_,
|
||||
// and log_empty_. Refer to the definition of each variable below for more
|
||||
// details.
|
||||
InstrumentedMutex log_write_mutex_;
|
||||
// State below is protected by mutex_
|
||||
// With concurrent_prepare enabled, some of the variables that accessed during
|
||||
// With two_write_queues enabled, some of the variables that accessed during
|
||||
// WriteToWAL need different synchronization: log_empty_, alive_log_files_,
|
||||
// logs_, logfile_number_. Refer to the definition of each variable below for
|
||||
// more description.
|
||||
@@ -876,6 +982,10 @@ class DBImpl : public DB {
|
||||
// (i.e. whenever a flush is done, even if it didn't make any progress)
|
||||
// * whenever there is an error in background purge, flush or compaction
|
||||
// * whenever num_running_ingest_file_ goes to 0.
|
||||
// * whenever pending_purge_obsolete_files_ goes to 0.
|
||||
// * whenever disable_delete_obsolete_files_ goes to 0.
|
||||
// * whenever SetOptions successfully updates options.
|
||||
// * whenever a column family is dropped.
|
||||
InstrumentedCondVar bg_cv_;
|
||||
// Writes are protected by locking both mutex_ and log_write_mutex_, and reads
|
||||
// must be under either mutex_ or log_write_mutex_. Since after ::Open,
|
||||
@@ -883,12 +993,12 @@ class DBImpl : public DB {
|
||||
// from the same write_thread_ without any locks.
|
||||
uint64_t logfile_number_;
|
||||
std::deque<uint64_t>
|
||||
log_recycle_files; // a list of log files that we can recycle
|
||||
log_recycle_files_; // a list of log files that we can recycle
|
||||
bool log_dir_synced_;
|
||||
// Without concurrent_prepare, read and writes to log_empty_ are protected by
|
||||
// Without two_write_queues, read and writes to log_empty_ are protected by
|
||||
// mutex_. Since it is currently updated/read only in write_thread_, it can be
|
||||
// accessed from the same write_thread_ without any locks. With
|
||||
// concurrent_prepare writes, where it can be updated in different threads,
|
||||
// two_write_queues writes, where it can be updated in different threads,
|
||||
// read and writes are protected by log_write_mutex_ instead. This is to avoid
|
||||
// expesnive mutex_ lock during WAL write, which update log_empty_.
|
||||
bool log_empty_;
|
||||
@@ -913,9 +1023,11 @@ class DBImpl : public DB {
|
||||
writer = nullptr;
|
||||
return w;
|
||||
}
|
||||
void ClearWriter() {
|
||||
Status ClearWriter() {
|
||||
Status s = writer->WriteBuffer();
|
||||
delete writer;
|
||||
writer = nullptr;
|
||||
return s;
|
||||
}
|
||||
|
||||
uint64_t number;
|
||||
@@ -925,10 +1037,10 @@ class DBImpl : public DB {
|
||||
// true for some prefix of logs_
|
||||
bool getting_synced = false;
|
||||
};
|
||||
// Without concurrent_prepare, read and writes to alive_log_files_ are
|
||||
// Without two_write_queues, read and writes to alive_log_files_ are
|
||||
// protected by mutex_. However since back() is never popped, and push_back()
|
||||
// is done only from write_thread_, the same thread can access the item
|
||||
// reffered by back() without mutex_. With concurrent_prepare_, writes
|
||||
// reffered by back() without mutex_. With two_write_queues_, writes
|
||||
// are protected by locking both mutex_ and log_write_mutex_, and reads must
|
||||
// be under either mutex_ or log_write_mutex_.
|
||||
std::deque<LogFileNumberSize> alive_log_files_;
|
||||
@@ -948,6 +1060,15 @@ class DBImpl : public DB {
|
||||
std::deque<LogWriterNumber> logs_;
|
||||
// Signaled when getting_synced becomes false for some of the logs_.
|
||||
InstrumentedCondVar log_sync_cv_;
|
||||
// This is the app-level state that is written to the WAL but will be used
|
||||
// only during recovery. Using this feature enables not writing the state to
|
||||
// memtable on normal writes and hence improving the throughput. Each new
|
||||
// write of the state will replace the previous state entirely even if the
|
||||
// keys in the two consecuitive states do not overlap.
|
||||
// It is protected by log_write_mutex_ when two_write_queues_ is enabled.
|
||||
// Otherwise only the heaad of write_thread_ can access it.
|
||||
WriteBatch cached_recoverable_state_;
|
||||
std::atomic<bool> cached_recoverable_state_empty_ = {true};
|
||||
std::atomic<uint64_t> total_log_size_;
|
||||
// only used for dynamically adjusting max_total_wal_size. it is a sum of
|
||||
// [write_buffer_size * max_write_buffer_number] over all column families
|
||||
@@ -1133,6 +1254,10 @@ class DBImpl : public DB {
|
||||
// without any synchronization
|
||||
int disable_delete_obsolete_files_;
|
||||
|
||||
// Number of times FindObsoleteFiles has found deletable files and the
|
||||
// corresponding call to PurgeObsoleteFiles has not yet finished.
|
||||
int pending_purge_obsolete_files_;
|
||||
|
||||
// last time when DeleteObsoleteFiles with full scan was executed. Originaly
|
||||
// initialized with startup time.
|
||||
uint64_t delete_obsolete_files_last_run_;
|
||||
@@ -1166,6 +1291,9 @@ class DBImpl : public DB {
|
||||
// The options to access storage files
|
||||
const EnvOptions env_options_;
|
||||
|
||||
// Additonal options for compaction and flush
|
||||
EnvOptions env_options_for_compaction_;
|
||||
|
||||
// Number of running IngestExternalFile() calls.
|
||||
// REQUIRES: mutex held
|
||||
int num_running_ingest_file_;
|
||||
@@ -1189,27 +1317,37 @@ class DBImpl : public DB {
|
||||
// Indicate DB was opened successfully
|
||||
bool opened_successfully_;
|
||||
|
||||
// minimum log number still containing prepared data.
|
||||
// REQUIRES: logs_with_prep_mutex_ held
|
||||
//
|
||||
// sorted list of log numbers still containing prepared data.
|
||||
// this is used by FindObsoleteFiles to determine which
|
||||
// flushed logs we must keep around because they still
|
||||
// contain prepared data which has not been flushed or rolled back
|
||||
std::priority_queue<uint64_t, std::vector<uint64_t>, std::greater<uint64_t>>
|
||||
min_log_with_prep_;
|
||||
// contain prepared data which has not been committed or rolled back
|
||||
struct LogCnt {
|
||||
uint64_t log; // the log number
|
||||
uint64_t cnt; // number of prepared sections in the log
|
||||
};
|
||||
std::vector<LogCnt> logs_with_prep_;
|
||||
std::mutex logs_with_prep_mutex_;
|
||||
|
||||
// to be used in conjunction with min_log_with_prep_.
|
||||
// REQUIRES: prepared_section_completed_mutex_ held
|
||||
//
|
||||
// to be used in conjunction with logs_with_prep_.
|
||||
// once a transaction with data in log L is committed or rolled back
|
||||
// rather than removing the value from the heap we add that value
|
||||
// to prepared_section_completed_ which maps LOG -> instance_count
|
||||
// since a log could contain multiple prepared sections
|
||||
// rather than updating logs_with_prep_ directly we keep track of that
|
||||
// in prepared_section_completed_ which maps LOG -> instance_count. This helps
|
||||
// avoiding contention between a commit thread and the prepare threads.
|
||||
//
|
||||
// when trying to determine the minimum log still active we first
|
||||
// consult min_log_with_prep_. while that root value maps to
|
||||
// a value > 0 in prepared_section_completed_ we decrement the
|
||||
// instance_count for that log and pop the root value in
|
||||
// min_log_with_prep_. This will work the same as a min_heap
|
||||
// where we are deleteing arbitrary elements and the up heaping.
|
||||
// consult logs_with_prep_. while that root value maps to
|
||||
// an equal value in prepared_section_completed_ we erase the log from
|
||||
// both logs_with_prep_ and prepared_section_completed_.
|
||||
std::unordered_map<uint64_t, uint64_t> prepared_section_completed_;
|
||||
std::mutex prep_heap_mutex_;
|
||||
std::mutex prepared_section_completed_mutex_;
|
||||
|
||||
// Callback for compaction to check if a key is visible to a snapshot.
|
||||
// REQUIRES: mutex held
|
||||
std::unique_ptr<SnapshotChecker> snapshot_checker_;
|
||||
|
||||
// No copying allowed
|
||||
DBImpl(const DBImpl&);
|
||||
@@ -1217,18 +1355,15 @@ class DBImpl : public DB {
|
||||
|
||||
// Background threads call this function, which is just a wrapper around
|
||||
// the InstallSuperVersion() function. Background threads carry
|
||||
// job_context which can have new_superversion already
|
||||
// sv_context which can have new_superversion already
|
||||
// allocated.
|
||||
void InstallSuperVersionAndScheduleWorkWrapper(
|
||||
ColumnFamilyData* cfd, JobContext* job_context,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
|
||||
// All ColumnFamily state changes go through this function. Here we analyze
|
||||
// the new state and we schedule background work if we detect that the new
|
||||
// state needs flush or compaction.
|
||||
SuperVersion* InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersion* new_sv,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
void InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
FlushReason flush_reason = FlushReason::kOthers);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
@@ -1241,12 +1376,6 @@ class DBImpl : public DB {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Function that Get and KeyMayExist call with no_io true or false
|
||||
// Note: 'value_found' from KeyMayExist propagates here
|
||||
Status GetImpl(const ReadOptions& options, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, PinnableSlice* value,
|
||||
bool* value_found = nullptr);
|
||||
|
||||
bool GetIntPropertyInternal(ColumnFamilyData* cfd,
|
||||
const DBPropertyInfo& property_info,
|
||||
bool is_locked, uint64_t* value);
|
||||
@@ -1260,11 +1389,38 @@ class DBImpl : public DB {
|
||||
bool MCOverlap(ManualCompactionState* m, ManualCompactionState* m1);
|
||||
|
||||
size_t GetWalPreallocateBlockSize(uint64_t write_buffer_size) const;
|
||||
Env::WriteLifeTimeHint CalculateWALWriteHint() {
|
||||
return Env::WLTH_SHORT;
|
||||
}
|
||||
|
||||
// When set, we use a seprate queue for writes that dont write to memtable. In
|
||||
// 2PC these are the writes at Prepare phase.
|
||||
const bool concurrent_prepare_;
|
||||
// When set, we use a separate queue for writes that dont write to memtable.
|
||||
// In 2PC these are the writes at Prepare phase.
|
||||
const bool two_write_queues_;
|
||||
const bool manual_wal_flush_;
|
||||
// Increase the sequence number after writing each batch, whether memtable is
|
||||
// disabled for that or not. Otherwise the sequence number is increased after
|
||||
// writing each key into memtable. This implies that when disable_memtable is
|
||||
// set, the seq is not increased at all.
|
||||
//
|
||||
// Default: false
|
||||
const bool seq_per_batch_;
|
||||
// LastSequence also indicates last published sequence visibile to the
|
||||
// readers. Otherwise LastPublishedSequence should be used.
|
||||
const bool last_seq_same_as_publish_seq_;
|
||||
// It indicates that a customized gc algorithm must be used for
|
||||
// flush/compaction and if it is not provided vis SnapshotChecker, we should
|
||||
// disable gc to be safe.
|
||||
const bool use_custom_gc_;
|
||||
|
||||
// Clients must periodically call SetPreserveDeletesSequenceNumber()
|
||||
// to advance this seqnum. Default value is 0 which means ALL deletes are
|
||||
// preserved. Note that this has no effect if DBOptions.preserve_deletes
|
||||
// is set to false.
|
||||
std::atomic<SequenceNumber> preserve_deletes_seqnum_;
|
||||
const bool preserve_deletes_;
|
||||
|
||||
// Flag to check whether Close() has been called on this DB
|
||||
bool closed_;
|
||||
};
|
||||
|
||||
extern Options SanitizeOptions(const std::string& db,
|
||||
|
||||
+276
-101
@@ -23,6 +23,7 @@
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
Status DBImpl::SyncClosedLogs(JobContext* job_context) {
|
||||
TEST_SYNC_POINT("DBImpl::SyncClosedLogs:Start");
|
||||
mutex_.AssertHeld();
|
||||
@@ -86,11 +87,16 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
|
||||
auto snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
FlushJob flush_job(
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options, env_options_,
|
||||
versions_.get(), &mutex_, &shutting_down_, snapshot_seqs,
|
||||
earliest_write_conflict_snapshot, job_context, log_buffer,
|
||||
directories_.GetDbDir(), directories_.GetDataDir(0U),
|
||||
dbname_, cfd, immutable_db_options_, mutable_cf_options,
|
||||
env_options_for_compaction_, versions_.get(), &mutex_, &shutting_down_,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
job_context, log_buffer, directories_.GetDbDir(),
|
||||
directories_.GetDataDir(0U),
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options), stats_,
|
||||
&event_logger_, mutable_cf_options.report_bg_io_stats);
|
||||
|
||||
@@ -129,8 +135,8 @@ Status DBImpl::FlushMemTableToOutputFile(
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
InstallSuperVersionAndScheduleWorkWrapper(cfd, job_context,
|
||||
mutable_cf_options);
|
||||
InstallSuperVersionAndScheduleWork(cfd, &job_context->superversion_context,
|
||||
mutable_cf_options);
|
||||
if (made_progress) {
|
||||
*made_progress = 1;
|
||||
}
|
||||
@@ -217,6 +223,7 @@ void DBImpl::NotifyOnFlushBegin(ColumnFamilyData* cfd, FileMetaData* file_meta,
|
||||
info.smallest_seqno = file_meta->smallest_seqno;
|
||||
info.largest_seqno = file_meta->largest_seqno;
|
||||
info.table_properties = prop;
|
||||
info.flush_reason = cfd->GetFlushReason();
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
listener->OnFlushBegin(this, info);
|
||||
}
|
||||
@@ -261,6 +268,7 @@ void DBImpl::NotifyOnFlushCompleted(ColumnFamilyData* cfd,
|
||||
info.smallest_seqno = file_meta->smallest_seqno;
|
||||
info.largest_seqno = file_meta->largest_seqno;
|
||||
info.table_properties = prop;
|
||||
info.flush_reason = cfd->GetFlushReason();
|
||||
for (auto listener : immutable_db_options_.listeners) {
|
||||
listener->OnFlushCompleted(this, info);
|
||||
}
|
||||
@@ -282,10 +290,75 @@ Status DBImpl::CompactRange(const CompactRangeOptions& options,
|
||||
auto cfd = cfh->cfd();
|
||||
bool exclusive = options.exclusive_manual_compaction;
|
||||
|
||||
Status s = FlushMemTable(cfd, FlushOptions());
|
||||
if (!s.ok()) {
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return s;
|
||||
bool flush_needed = true;
|
||||
if (begin != nullptr && end != nullptr) {
|
||||
// TODO(ajkr): We could also optimize away the flush in certain cases where
|
||||
// one/both sides of the interval are unbounded. But it requires more
|
||||
// changes to RangesOverlapWithMemtables.
|
||||
Range range(*begin, *end);
|
||||
SuperVersion* super_version = cfd->GetReferencedSuperVersion(&mutex_);
|
||||
cfd->RangesOverlapWithMemtables({range}, super_version, &flush_needed);
|
||||
CleanupSuperVersion(super_version);
|
||||
}
|
||||
|
||||
if (!options.allow_write_stall && flush_needed) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
uint64_t orig_active_memtable_id = cfd->mem()->GetID();
|
||||
WriteStallCondition write_stall_condition = WriteStallCondition::kNormal;
|
||||
do {
|
||||
if (write_stall_condition != WriteStallCondition::kNormal) {
|
||||
TEST_SYNC_POINT("DBImpl::CompactRange:StallWait");
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] CompactRange waiting on stall conditions to clear",
|
||||
cfd->GetName().c_str());
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
if (cfd->IsDropped() || shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
|
||||
uint64_t earliest_memtable_id =
|
||||
std::min(cfd->mem()->GetID(), cfd->imm()->GetEarliestMemTableID());
|
||||
if (earliest_memtable_id > orig_active_memtable_id) {
|
||||
// We waited so long that the memtable we were originally waiting on was
|
||||
// flushed.
|
||||
flush_needed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
const auto* vstorage = cfd->current()->storage_info();
|
||||
|
||||
// Skip stalling check if we're below auto-flush and auto-compaction
|
||||
// triggers. If it stalled in these conditions, that'd mean the stall
|
||||
// triggers are so low that stalling is needed for any background work. In
|
||||
// that case we shouldn't wait since background work won't be scheduled.
|
||||
if (cfd->imm()->NumNotFlushed() <
|
||||
cfd->ioptions()->min_write_buffer_number_to_merge &&
|
||||
vstorage->l0_delay_trigger_count() <
|
||||
mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
break;
|
||||
}
|
||||
|
||||
// check whether one extra immutable memtable or an extra L0 file would
|
||||
// cause write stalling mode to be entered. It could still enter stall
|
||||
// mode due to pending compaction bytes, but that's less common
|
||||
write_stall_condition =
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
cfd->imm()->NumNotFlushed() + 1,
|
||||
vstorage->l0_delay_trigger_count() + 1,
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options)
|
||||
.first;
|
||||
} while (write_stall_condition != WriteStallCondition::kNormal);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::CompactRange:StallWaitDone");
|
||||
Status s;
|
||||
if (flush_needed) {
|
||||
s = FlushMemTable(cfd, FlushOptions(), FlushReason::kManualCompaction);
|
||||
if (!s.ok()) {
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
int max_level_with_files = 0;
|
||||
@@ -388,7 +461,8 @@ Status DBImpl::CompactFiles(
|
||||
const CompactionOptions& compact_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
const int output_level, const int output_path_id) {
|
||||
const int output_level, const int output_path_id,
|
||||
std::vector<std::string>* const output_file_names) {
|
||||
#ifdef ROCKSDB_LITE
|
||||
// not supported in lite version
|
||||
return Status::NotSupported("Not supported in ROCKSDB LITE");
|
||||
@@ -415,7 +489,7 @@ Status DBImpl::CompactFiles(
|
||||
WaitForIngestFile();
|
||||
|
||||
s = CompactFilesImpl(compact_options, cfd, sv->current,
|
||||
input_file_names, output_level,
|
||||
input_file_names, output_file_names, output_level,
|
||||
output_path_id, &job_context, &log_buffer);
|
||||
}
|
||||
if (sv->Unref()) {
|
||||
@@ -436,7 +510,8 @@ Status DBImpl::CompactFiles(
|
||||
} // release the mutex
|
||||
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
// Have to flush the info logs before bg_compaction_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
// released, the deconstructor of DB can kick in and destroy all the
|
||||
@@ -458,6 +533,7 @@ Status DBImpl::CompactFiles(
|
||||
Status DBImpl::CompactFilesImpl(
|
||||
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
|
||||
Version* version, const std::vector<std::string>& input_file_names,
|
||||
std::vector<std::string>* const output_file_names,
|
||||
const int output_level, int output_path_id, JobContext* job_context,
|
||||
LogBuffer* log_buffer) {
|
||||
mutex_.AssertHeld();
|
||||
@@ -515,9 +591,10 @@ Status DBImpl::CompactFilesImpl(
|
||||
c.reset(cfd->compaction_picker()->CompactFiles(
|
||||
compact_options, input_files, output_level, version->storage_info(),
|
||||
*cfd->GetLatestMutableCFOptions(), output_path_id));
|
||||
if (!c) {
|
||||
return Status::Aborted("Another Level 0 compaction is running");
|
||||
}
|
||||
// we already sanitized the set of input files and checked for conflicts
|
||||
// without releasing the lock, so we're guaranteed a compaction can be formed.
|
||||
assert(c != nullptr);
|
||||
|
||||
c->SetInputVersion(version);
|
||||
// deletion compaction currently not allowed in CompactFiles.
|
||||
assert(!c->deletion_compaction());
|
||||
@@ -529,13 +606,19 @@ Status DBImpl::CompactFilesImpl(
|
||||
auto pending_outputs_inserted_elem =
|
||||
CaptureCurrentFileNumberInPendingOutputs();
|
||||
|
||||
auto snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, log_buffer, directories_.GetDbDir(),
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
|
||||
directories_.GetDataDir(c->output_path_id()), stats_, &mutex_, &bg_error_,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, table_cache_,
|
||||
&event_logger_, c->mutable_cf_options()->paranoid_file_checks,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
nullptr); // Here we pass a nullptr for CompactionJobStats because
|
||||
// CompactFiles does not trigger OnCompactionCompleted(),
|
||||
@@ -570,8 +653,9 @@ Status DBImpl::CompactFilesImpl(
|
||||
|
||||
Status status = compaction_job.Install(*c->mutable_cf_options());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWorkWrapper(
|
||||
c->column_family_data(), job_context, *c->mutable_cf_options());
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_context,
|
||||
*c->mutable_cf_options(), FlushReason::kManualCompaction);
|
||||
}
|
||||
c->ReleaseCompactionFiles(s);
|
||||
|
||||
@@ -598,6 +682,14 @@ Status DBImpl::CompactFilesImpl(
|
||||
}
|
||||
}
|
||||
|
||||
if (output_file_names != nullptr) {
|
||||
for (const auto newf : c->edit()->GetNewFiles()) {
|
||||
(*output_file_names).push_back(TableFileName(
|
||||
immutable_db_options_.db_paths, newf.second.fd.GetNumber(),
|
||||
newf.second.fd.GetPathId()) );
|
||||
}
|
||||
}
|
||||
|
||||
c.reset();
|
||||
|
||||
bg_compaction_scheduled_--;
|
||||
@@ -649,6 +741,8 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
Version* current = cfd->current();
|
||||
current->Ref();
|
||||
// release lock while notifying events
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::NotifyOnCompactionCompleted::UnlockMutex");
|
||||
@@ -671,7 +765,7 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
info.input_files.push_back(fn);
|
||||
if (info.table_properties.count(fn) == 0) {
|
||||
std::shared_ptr<const TableProperties> tp;
|
||||
auto s = cfd->current()->GetTableProperties(&tp, fmd, &fn);
|
||||
auto s = current->GetTableProperties(&tp, fmd, &fn);
|
||||
if (s.ok()) {
|
||||
info.table_properties[fn] = tp;
|
||||
}
|
||||
@@ -688,6 +782,7 @@ void DBImpl::NotifyOnCompactionCompleted(
|
||||
}
|
||||
}
|
||||
mutex_.Lock();
|
||||
current->Unref();
|
||||
// no need to signal bg_cv_ as it will be signaled at the end of the
|
||||
// flush process.
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -701,8 +796,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
return Status::InvalidArgument("Target level exceeds number of levels");
|
||||
}
|
||||
|
||||
std::unique_ptr<SuperVersion> superversion_to_free;
|
||||
std::unique_ptr<SuperVersion> new_superversion(new SuperVersion());
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
|
||||
Status status;
|
||||
|
||||
@@ -757,8 +851,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
|
||||
status = versions_->LogAndApply(cfd, mutable_cf_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
superversion_to_free.reset(InstallSuperVersionAndScheduleWork(
|
||||
cfd, new_superversion.release(), mutable_cf_options));
|
||||
InstallSuperVersionAndScheduleWork(cfd, &sv_context, mutable_cf_options);
|
||||
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log, "[%s] LogAndApply: %s\n",
|
||||
cfd->GetName().c_str(), status.ToString().data());
|
||||
@@ -770,6 +863,7 @@ Status DBImpl::ReFitLevel(ColumnFamilyData* cfd, int level, int target_level) {
|
||||
}
|
||||
}
|
||||
|
||||
sv_context.Clean();
|
||||
refitting_level_ = false;
|
||||
|
||||
return status;
|
||||
@@ -780,7 +874,7 @@ int DBImpl::NumberLevels(ColumnFamilyHandle* column_family) {
|
||||
return cfh->cfd()->NumberLevels();
|
||||
}
|
||||
|
||||
int DBImpl::MaxMemCompactionLevel(ColumnFamilyHandle* column_family) {
|
||||
int DBImpl::MaxMemCompactionLevel(ColumnFamilyHandle* /*column_family*/) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -794,7 +888,14 @@ int DBImpl::Level0StopWriteTrigger(ColumnFamilyHandle* column_family) {
|
||||
Status DBImpl::Flush(const FlushOptions& flush_options,
|
||||
ColumnFamilyHandle* column_family) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
return FlushMemTable(cfh->cfd(), flush_options);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log, "[%s] Manual flush start.",
|
||||
cfh->GetName().c_str());
|
||||
Status s =
|
||||
FlushMemTable(cfh->cfd(), flush_options, FlushReason::kManualFlush);
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"[%s] Manual flush finished, status: %s\n",
|
||||
cfh->GetName().c_str(), s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
@@ -826,7 +927,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
|
||||
manual.begin = nullptr;
|
||||
} else {
|
||||
begin_storage.SetMaxPossibleForUserKey(*begin);
|
||||
begin_storage.SetMinPossibleForUserKey(*begin);
|
||||
manual.begin = &begin_storage;
|
||||
}
|
||||
if (end == nullptr ||
|
||||
@@ -834,7 +935,7 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
cfd->ioptions()->compaction_style == kCompactionStyleFIFO) {
|
||||
manual.end = nullptr;
|
||||
} else {
|
||||
end_storage.SetMinPossibleForUserKey(*end);
|
||||
end_storage.SetMaxPossibleForUserKey(*end);
|
||||
manual.end = &end_storage;
|
||||
}
|
||||
|
||||
@@ -880,15 +981,15 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
while (!manual.done) {
|
||||
assert(HasPendingManualCompaction());
|
||||
manual_conflict = false;
|
||||
Compaction* compaction;
|
||||
Compaction* compaction = nullptr;
|
||||
if (ShouldntRunManualCompaction(&manual) || (manual.in_progress == true) ||
|
||||
scheduled ||
|
||||
((manual.manual_end = &manual.tmp_storage1) &&
|
||||
((compaction = manual.cfd->CompactRange(
|
||||
*manual.cfd->GetLatestMutableCFOptions(), manual.input_level,
|
||||
manual.output_level, manual.output_path_id, manual.begin,
|
||||
manual.end, &manual.manual_end, &manual_conflict)) == nullptr) &&
|
||||
manual_conflict)) {
|
||||
(((manual.manual_end = &manual.tmp_storage1) != nullptr) &&
|
||||
((compaction = manual.cfd->CompactRange(
|
||||
*manual.cfd->GetLatestMutableCFOptions(), manual.input_level,
|
||||
manual.output_level, manual.output_path_id, manual.begin,
|
||||
manual.end, &manual.manual_end, &manual_conflict)) == nullptr &&
|
||||
manual_conflict))) {
|
||||
// exclusive manual compactions should not see a conflict during
|
||||
// CompactRange
|
||||
assert(!exclusive || !manual_conflict);
|
||||
@@ -927,13 +1028,15 @@ Status DBImpl::RunManualCompaction(ColumnFamilyData* cfd, int input_level,
|
||||
|
||||
Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
const FlushOptions& flush_options,
|
||||
bool writes_stopped) {
|
||||
FlushReason flush_reason, bool writes_stopped) {
|
||||
Status s;
|
||||
uint64_t flush_memtable_id = 0;
|
||||
{
|
||||
WriteContext context;
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
|
||||
if (cfd->imm()->NumNotFlushed() == 0 && cfd->mem()->IsEmpty()) {
|
||||
if (cfd->imm()->NumNotFlushed() == 0 && cfd->mem()->IsEmpty() &&
|
||||
cached_recoverable_state_empty_.load()) {
|
||||
// Nothing to flush
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -943,9 +1046,9 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
}
|
||||
|
||||
// SwitchMemtable() will release and reacquire mutex
|
||||
// during execution
|
||||
// SwitchMemtable() will release and reacquire mutex during execution
|
||||
s = SwitchMemtable(cfd, &context);
|
||||
flush_memtable_id = cfd->imm()->GetLatestMemTableID();
|
||||
|
||||
if (!writes_stopped) {
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
@@ -954,22 +1057,25 @@ Status DBImpl::FlushMemTable(ColumnFamilyData* cfd,
|
||||
cfd->imm()->FlushRequested();
|
||||
|
||||
// schedule flush
|
||||
SchedulePendingFlush(cfd);
|
||||
SchedulePendingFlush(cfd, flush_reason);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
if (s.ok() && flush_options.wait) {
|
||||
// Wait until the compaction completes
|
||||
s = WaitForFlushMemTable(cfd);
|
||||
s = WaitForFlushMemTable(cfd, &flush_memtable_id);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd) {
|
||||
Status DBImpl::WaitForFlushMemTable(ColumnFamilyData* cfd,
|
||||
const uint64_t* flush_memtable_id) {
|
||||
Status s;
|
||||
// Wait until the compaction completes
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while (cfd->imm()->NumNotFlushed() > 0 && bg_error_.ok()) {
|
||||
while (cfd->imm()->NumNotFlushed() > 0 && bg_error_.ok() &&
|
||||
(flush_memtable_id == nullptr ||
|
||||
cfd->imm()->GetEarliestMemTableID() <= *flush_memtable_id)) {
|
||||
if (shutting_down_.load(std::memory_order_acquire)) {
|
||||
return Status::ShutdownInProgress();
|
||||
}
|
||||
@@ -1044,6 +1150,7 @@ void DBImpl::MaybeScheduleFlushOrCompaction() {
|
||||
if (HasExclusiveManualCompaction()) {
|
||||
// only manual compactions are allowed to run. don't schedule automatic
|
||||
// compactions
|
||||
TEST_SYNC_POINT("DBImpl::MaybeScheduleFlushOrCompaction:Conflict");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1106,11 +1213,12 @@ ColumnFamilyData* DBImpl::PopFirstFromCompactionQueue() {
|
||||
return cfd;
|
||||
}
|
||||
|
||||
void DBImpl::AddToFlushQueue(ColumnFamilyData* cfd) {
|
||||
void DBImpl::AddToFlushQueue(ColumnFamilyData* cfd, FlushReason flush_reason) {
|
||||
assert(!cfd->pending_flush());
|
||||
cfd->Ref();
|
||||
flush_queue_.push_back(cfd);
|
||||
cfd->set_pending_flush(true);
|
||||
cfd->SetFlushReason(flush_reason);
|
||||
}
|
||||
|
||||
ColumnFamilyData* DBImpl::PopFirstFromFlushQueue() {
|
||||
@@ -1119,12 +1227,14 @@ ColumnFamilyData* DBImpl::PopFirstFromFlushQueue() {
|
||||
flush_queue_.pop_front();
|
||||
assert(cfd->pending_flush());
|
||||
cfd->set_pending_flush(false);
|
||||
// TODO: need to unset flush reason?
|
||||
return cfd;
|
||||
}
|
||||
|
||||
void DBImpl::SchedulePendingFlush(ColumnFamilyData* cfd) {
|
||||
void DBImpl::SchedulePendingFlush(ColumnFamilyData* cfd,
|
||||
FlushReason flush_reason) {
|
||||
if (!cfd->pending_flush() && cfd->imm()->IsFlushPending()) {
|
||||
AddToFlushQueue(cfd);
|
||||
AddToFlushQueue(cfd, flush_reason);
|
||||
++unscheduled_flushes_;
|
||||
}
|
||||
}
|
||||
@@ -1288,8 +1398,10 @@ void DBImpl::BackgroundCallFlush() {
|
||||
// created. Thus, we force full scan in FindObsoleteFiles()
|
||||
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress());
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
mutex_.Unlock();
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCallFlush:FilesFound");
|
||||
// Have to flush the info logs before bg_flush_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
// released, the deconstructor of DB can kick in and destroy all the
|
||||
@@ -1369,7 +1481,8 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
FindObsoleteFiles(&job_context, !s.ok() && !s.IsShutdownInProgress());
|
||||
|
||||
// delete unnecessary files if any, this is done outside the mutex
|
||||
if (job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
if (job_context.HaveSomethingToClean() ||
|
||||
job_context.HaveSomethingToDelete() || !log_buffer.IsEmpty()) {
|
||||
mutex_.Unlock();
|
||||
// Have to flush the info logs before bg_compaction_scheduled_--
|
||||
// because if bg_flush_scheduled_ becomes 0 and the lock is
|
||||
@@ -1400,7 +1513,7 @@ void DBImpl::BackgroundCallCompaction(PrepickedCompaction* prepicked_compaction,
|
||||
if (made_progress ||
|
||||
(bg_compaction_scheduled_ == 0 &&
|
||||
bg_bottom_compaction_scheduled_ == 0) ||
|
||||
HasPendingManualCompaction()) {
|
||||
HasPendingManualCompaction() || unscheduled_compactions_ == 0) {
|
||||
// signal if
|
||||
// * made_progress -- need to wakeup DelayWrite
|
||||
// * bg_{bottom,}_compaction_scheduled_ == 0 -- need to wakeup ~DBImpl
|
||||
@@ -1463,6 +1576,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
|
||||
// InternalKey manual_end_storage;
|
||||
// InternalKey* manual_end = &manual_end_storage;
|
||||
#ifndef ROCKSDB_LITE
|
||||
bool sfm_bookkeeping = false;
|
||||
#endif // ROCKSDB_LITE
|
||||
if (is_manual) {
|
||||
ManualCompactionState* m = manual_compaction;
|
||||
assert(m->in_progress);
|
||||
@@ -1488,7 +1604,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
: m->manual_end->DebugString().c_str()));
|
||||
}
|
||||
} else if (!is_prepicked && !compaction_queue_.empty()) {
|
||||
if (HaveManualCompaction(compaction_queue_.front())) {
|
||||
if (HasExclusiveManualCompaction()) {
|
||||
// Can't compact right now, but try again later
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction()::Conflict");
|
||||
|
||||
@@ -1525,27 +1641,66 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():BeforePickCompaction");
|
||||
c.reset(cfd->PickCompaction(*mutable_cf_options, log_buffer));
|
||||
TEST_SYNC_POINT("DBImpl::BackgroundCompaction():AfterPickCompaction");
|
||||
|
||||
bool enough_room = true;
|
||||
if (c != nullptr) {
|
||||
// update statistics
|
||||
MeasureTime(stats_, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
// There are three things that can change compaction score:
|
||||
// 1) When flush or compaction finish. This case is covered by
|
||||
// InstallSuperVersionAndScheduleWork
|
||||
// 2) When MutableCFOptions changes. This case is also covered by
|
||||
// InstallSuperVersionAndScheduleWork, because this is when the new
|
||||
// options take effect.
|
||||
// 3) When we Pick a new compaction, we "remove" those files being
|
||||
// compacted from the calculation, which then influences compaction
|
||||
// score. Here we check if we need the new compaction even without the
|
||||
// files that are currently being compacted. If we need another
|
||||
// compaction, we might be able to execute it in parallel, so we add it
|
||||
// to the queue and schedule a new thread.
|
||||
if (cfd->NeedsCompaction()) {
|
||||
// Yes, we need more compactions!
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
if (sfm) {
|
||||
enough_room = sfm->EnoughRoomForCompaction(c.get());
|
||||
if (enough_room) {
|
||||
sfm_bookkeeping = true;
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
if (!enough_room) {
|
||||
// Just in case tests want to change the value of enough_room
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"DBImpl::BackgroundCompaction():CancelledCompaction",
|
||||
&enough_room);
|
||||
}
|
||||
if (!enough_room) {
|
||||
// Then don't do the compaction
|
||||
c->ReleaseCompactionFiles(status);
|
||||
c->column_family_data()
|
||||
->current()
|
||||
->storage_info()
|
||||
->ComputeCompactionScore(*(c->immutable_cf_options()),
|
||||
*(c->mutable_cf_options()));
|
||||
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"Cancelled compaction because not enough room");
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
c.reset();
|
||||
// Don't need to sleep here, because BackgroundCallCompaction
|
||||
// will sleep if !s.ok()
|
||||
status = Status::CompactionTooLarge();
|
||||
RecordTick(stats_, COMPACTION_CANCELLED, 1);
|
||||
} else {
|
||||
// update statistics
|
||||
MeasureTime(stats_, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
// There are three things that can change compaction score:
|
||||
// 1) When flush or compaction finish. This case is covered by
|
||||
// InstallSuperVersionAndScheduleWork
|
||||
// 2) When MutableCFOptions changes. This case is also covered by
|
||||
// InstallSuperVersionAndScheduleWork, because this is when the new
|
||||
// options take effect.
|
||||
// 3) When we Pick a new compaction, we "remove" those files being
|
||||
// compacted from the calculation, which then influences compaction
|
||||
// score. Here we check if we need the new compaction even without the
|
||||
// files that are currently being compacted. If we need another
|
||||
// compaction, we might be able to execute it in parallel, so we add
|
||||
// it to the queue and schedule a new thread.
|
||||
if (cfd->NeedsCompaction()) {
|
||||
// Yes, we need more compactions!
|
||||
AddToCompactionQueue(cfd);
|
||||
++unscheduled_compactions_;
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1570,8 +1725,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
status = versions_->LogAndApply(c->column_family_data(),
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
InstallSuperVersionAndScheduleWorkWrapper(
|
||||
c->column_family_data(), job_context, *c->mutable_cf_options());
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_context,
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
ROCKS_LOG_BUFFER(log_buffer, "[%s] Deleted %d files\n",
|
||||
c->column_family_data()->GetName().c_str(),
|
||||
c->num_input_files(0));
|
||||
@@ -1616,8 +1772,9 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
*c->mutable_cf_options(), c->edit(),
|
||||
&mutex_, directories_.GetDbDir());
|
||||
// Use latest MutableCFOptions
|
||||
InstallSuperVersionAndScheduleWorkWrapper(
|
||||
c->column_family_data(), job_context, *c->mutable_cf_options());
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_context,
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
c->column_family_data()->internal_stats()->IncBytesMoved(c->output_level(),
|
||||
@@ -1662,21 +1819,26 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
env_->Schedule(&DBImpl::BGWorkBottomCompaction, ca, Env::Priority::BOTTOM,
|
||||
this, &DBImpl::UnscheduleCallback);
|
||||
} else {
|
||||
int output_level __attribute__((unused)) = c->output_level();
|
||||
int output_level __attribute__((__unused__));
|
||||
output_level = c->output_level();
|
||||
TEST_SYNC_POINT_CALLBACK("DBImpl::BackgroundCompaction:NonTrivial",
|
||||
&output_level);
|
||||
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
|
||||
auto snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
assert(is_snapshot_supported_ || snapshots_.empty());
|
||||
CompactionJob compaction_job(
|
||||
job_context->job_id, c.get(), immutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, log_buffer, directories_.GetDbDir(),
|
||||
job_context->job_id, c.get(), immutable_db_options_,
|
||||
env_options_for_compaction_, versions_.get(), &shutting_down_,
|
||||
preserve_deletes_seqnum_.load(), log_buffer, directories_.GetDbDir(),
|
||||
directories_.GetDataDir(c->output_path_id()), stats_, &mutex_,
|
||||
&bg_error_, snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
table_cache_, &event_logger_,
|
||||
snapshot_checker, table_cache_, &event_logger_,
|
||||
c->mutable_cf_options()->paranoid_file_checks,
|
||||
c->mutable_cf_options()->report_bg_io_stats, dbname_,
|
||||
&compaction_job_stats);
|
||||
@@ -1689,14 +1851,25 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
|
||||
status = compaction_job.Install(*c->mutable_cf_options());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWorkWrapper(
|
||||
c->column_family_data(), job_context, *c->mutable_cf_options());
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
c->column_family_data(), &job_context->superversion_context,
|
||||
*c->mutable_cf_options(), FlushReason::kAutoCompaction);
|
||||
}
|
||||
*made_progress = true;
|
||||
}
|
||||
if (c != nullptr) {
|
||||
c->ReleaseCompactionFiles(status);
|
||||
*made_progress = true;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Need to make sure SstFileManager does its bookkeeping
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(
|
||||
immutable_db_options_.sst_file_manager.get());
|
||||
if (sfm && sfm_bookkeeping) {
|
||||
sfm->OnCompactionCompletion(c.get());
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
NotifyOnCompactionCompleted(
|
||||
c->column_family_data(), c.get(), status,
|
||||
compaction_job_stats, job_context->job_id);
|
||||
@@ -1704,7 +1877,7 @@ Status DBImpl::BackgroundCompaction(bool* made_progress,
|
||||
// this will unref its input_version and column_family_data
|
||||
c.reset();
|
||||
|
||||
if (status.ok()) {
|
||||
if (status.ok() || status.IsCompactionTooLarge()) {
|
||||
// Done
|
||||
} else if (status.IsShutdownInProgress()) {
|
||||
// Ignore compaction errors found during shutting down
|
||||
@@ -1856,31 +2029,22 @@ bool DBImpl::MCOverlap(ManualCompactionState* m, ManualCompactionState* m1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// JobContext gets created and destructed outside of the lock --
|
||||
// SuperVersionContext gets created and destructed outside of the lock --
|
||||
// we
|
||||
// use this convinently to:
|
||||
// * malloc one SuperVersion() outside of the lock -- new_superversion
|
||||
// * delete SuperVersion()s outside of the lock -- superversions_to_free
|
||||
//
|
||||
// However, if InstallSuperVersionAndScheduleWork() gets called twice with the
|
||||
// same job_context, we can't reuse the SuperVersion() that got
|
||||
// same sv_context, we can't reuse the SuperVersion() that got
|
||||
// malloced because
|
||||
// first call already used it. In that rare case, we take a hit and create a
|
||||
// new SuperVersion() inside of the mutex. We do similar thing
|
||||
// for superversion_to_free
|
||||
void DBImpl::InstallSuperVersionAndScheduleWorkWrapper(
|
||||
ColumnFamilyData* cfd, JobContext* job_context,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
mutex_.AssertHeld();
|
||||
SuperVersion* old_superversion = InstallSuperVersionAndScheduleWork(
|
||||
cfd, job_context->new_superversion, mutable_cf_options);
|
||||
job_context->new_superversion = nullptr;
|
||||
job_context->superversions_to_free.push_back(old_superversion);
|
||||
}
|
||||
|
||||
SuperVersion* DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersion* new_sv,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
void DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
ColumnFamilyData* cfd, SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options, FlushReason flush_reason) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
// Update max_total_in_memory_state_
|
||||
@@ -1891,12 +2055,15 @@ SuperVersion* DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
old_sv->mutable_cf_options.max_write_buffer_number;
|
||||
}
|
||||
|
||||
auto* old = cfd->InstallSuperVersion(
|
||||
new_sv ? new_sv : new SuperVersion(), &mutex_, mutable_cf_options);
|
||||
if (sv_context->new_superversion == nullptr) {
|
||||
sv_context->NewSuperVersion();
|
||||
}
|
||||
cfd->InstallSuperVersion(sv_context, &mutex_,
|
||||
mutable_cf_options);
|
||||
|
||||
// Whenever we install new SuperVersion, we might need to issue new flushes or
|
||||
// compactions.
|
||||
SchedulePendingFlush(cfd);
|
||||
SchedulePendingFlush(cfd, flush_reason);
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
|
||||
@@ -1905,6 +2072,14 @@ SuperVersion* DBImpl::InstallSuperVersionAndScheduleWork(
|
||||
max_total_in_memory_state_ - old_memtable_size +
|
||||
mutable_cf_options.write_buffer_size *
|
||||
mutable_cf_options.max_write_buffer_number;
|
||||
return old;
|
||||
}
|
||||
|
||||
void DBImpl::SetSnapshotChecker(SnapshotChecker* snapshot_checker) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
// snapshot_checker_ should only set once. If we need to set it multiple
|
||||
// times, we need to make sure the old one is not deleted while it is still
|
||||
// using by a compaction job.
|
||||
assert(!snapshot_checker_);
|
||||
snapshot_checker_.reset(snapshot_checker);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
+33
-5
@@ -19,10 +19,10 @@ uint64_t DBImpl::TEST_GetLevel0TotalSize() {
|
||||
return default_cf_handle_->cfd()->current()->storage_info()->NumLevelBytes(0);
|
||||
}
|
||||
|
||||
void DBImpl::TEST_HandleWALFull() {
|
||||
void DBImpl::TEST_SwitchWAL() {
|
||||
WriteContext write_context;
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
HandleWALFull(&write_context);
|
||||
SwitchWAL(&write_context);
|
||||
}
|
||||
|
||||
int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes(
|
||||
@@ -60,6 +60,10 @@ uint64_t DBImpl::TEST_Current_Manifest_FileNo() {
|
||||
return versions_->manifest_file_number();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_Current_Next_FileNo() {
|
||||
return versions_->current_next_file_number();
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
const Slice* end,
|
||||
ColumnFamilyHandle* column_family,
|
||||
@@ -80,6 +84,15 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
disallow_trivial_move);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_SwitchMemtable(ColumnFamilyData* cfd) {
|
||||
WriteContext write_context;
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
if (cfd == nullptr) {
|
||||
cfd = default_cf_handle_->cfd();
|
||||
}
|
||||
return SwitchMemtable(cfd, &write_context);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait, ColumnFamilyHandle* cfh) {
|
||||
FlushOptions fo;
|
||||
fo.wait = wait;
|
||||
@@ -90,7 +103,7 @@ Status DBImpl::TEST_FlushMemTable(bool wait, ColumnFamilyHandle* cfh) {
|
||||
auto cfhi = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh);
|
||||
cfd = cfhi->cfd();
|
||||
}
|
||||
return FlushMemTable(cfd, fo);
|
||||
return FlushMemTable(cfd, fo, FlushReason::kTest);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) {
|
||||
@@ -104,7 +117,7 @@ Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) {
|
||||
return WaitForFlushMemTable(cfd);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_WaitForCompact() {
|
||||
Status DBImpl::TEST_WaitForCompact(bool wait_unscheduled) {
|
||||
// Wait until the compaction completes
|
||||
|
||||
// TODO: a bug here. This function actually does not necessarily
|
||||
@@ -113,7 +126,8 @@ Status DBImpl::TEST_WaitForCompact() {
|
||||
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
while ((bg_bottom_compaction_scheduled_ || bg_compaction_scheduled_ ||
|
||||
bg_flush_scheduled_) &&
|
||||
bg_flush_scheduled_ ||
|
||||
(wait_unscheduled && unscheduled_compactions_)) &&
|
||||
bg_error_.ok()) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
@@ -173,6 +187,12 @@ uint64_t DBImpl::TEST_FindMinLogContainingOutstandingPrep() {
|
||||
return FindMinLogContainingOutstandingPrep();
|
||||
}
|
||||
|
||||
size_t DBImpl::TEST_PreparedSectionCompletedSize() {
|
||||
return prepared_section_completed_.size();
|
||||
}
|
||||
|
||||
size_t DBImpl::TEST_LogsWithPrepSize() { return logs_with_prep_.size(); }
|
||||
|
||||
uint64_t DBImpl::TEST_FindMinPrepLogReferencedByMemTable() {
|
||||
return FindMinPrepLogReferencedByMemTable();
|
||||
}
|
||||
@@ -196,5 +216,13 @@ int DBImpl::TEST_BGFlushesAllowed() const {
|
||||
return GetBGJobLimits().max_flushes;
|
||||
}
|
||||
|
||||
SequenceNumber DBImpl::TEST_GetLastVisibleSequence() const {
|
||||
if (last_seq_same_as_publish_seq_) {
|
||||
return versions_->LastSequence();
|
||||
} else {
|
||||
return versions_->LastAllocatedSequence();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // NDEBUG
|
||||
@@ -30,10 +30,10 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
auto cfd = cfh->cfd();
|
||||
InternalKey start_key, end_key;
|
||||
if (begin != nullptr) {
|
||||
start_key.SetMaxPossibleForUserKey(*begin);
|
||||
start_key.SetMinPossibleForUserKey(*begin);
|
||||
}
|
||||
if (end != nullptr) {
|
||||
end_key.SetMinPossibleForUserKey(*end);
|
||||
end_key.SetMaxPossibleForUserKey(*end);
|
||||
}
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -138,8 +138,9 @@ Status DBImpl::PromoteL0(ColumnFamilyHandle* column_family, int target_level) {
|
||||
status = versions_->LogAndApply(cfd, *cfd->GetLatestMutableCFOptions(),
|
||||
&edit, &mutex_, directories_.GetDbDir());
|
||||
if (status.ok()) {
|
||||
InstallSuperVersionAndScheduleWorkWrapper(
|
||||
cfd, &job_context, *cfd->GetLatestMutableCFOptions());
|
||||
InstallSuperVersionAndScheduleWork(
|
||||
cfd, &job_context.superversion_context,
|
||||
*cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
} // lock released here
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
|
||||
+71
-59
@@ -50,54 +50,59 @@ uint64_t DBImpl::FindMinPrepLogReferencedByMemTable() {
|
||||
|
||||
void DBImpl::MarkLogAsHavingPrepSectionFlushed(uint64_t log) {
|
||||
assert(log != 0);
|
||||
std::lock_guard<std::mutex> lock(prep_heap_mutex_);
|
||||
std::lock_guard<std::mutex> lock(prepared_section_completed_mutex_);
|
||||
auto it = prepared_section_completed_.find(log);
|
||||
assert(it != prepared_section_completed_.end());
|
||||
it->second += 1;
|
||||
if (UNLIKELY(it == prepared_section_completed_.end())) {
|
||||
prepared_section_completed_[log] = 1;
|
||||
} else {
|
||||
it->second += 1;
|
||||
}
|
||||
}
|
||||
|
||||
void DBImpl::MarkLogAsContainingPrepSection(uint64_t log) {
|
||||
assert(log != 0);
|
||||
std::lock_guard<std::mutex> lock(prep_heap_mutex_);
|
||||
min_log_with_prep_.push(log);
|
||||
auto it = prepared_section_completed_.find(log);
|
||||
if (it == prepared_section_completed_.end()) {
|
||||
prepared_section_completed_[log] = 0;
|
||||
std::lock_guard<std::mutex> lock(logs_with_prep_mutex_);
|
||||
|
||||
auto rit = logs_with_prep_.rbegin();
|
||||
bool updated = false;
|
||||
// Most probably the last log is the one that is being marked for
|
||||
// having a prepare section; so search from the end.
|
||||
for (; rit != logs_with_prep_.rend() && rit->log >= log; ++rit) {
|
||||
if (rit->log == log) {
|
||||
rit->cnt++;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!updated) {
|
||||
// We are either at the start, or at a position with rit->log < log
|
||||
logs_with_prep_.insert(rit.base(), {log, 1});
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t DBImpl::FindMinLogContainingOutstandingPrep() {
|
||||
|
||||
if (!allow_2pc()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(prep_heap_mutex_);
|
||||
uint64_t min_log = 0;
|
||||
|
||||
// first we look in the prepared heap where we keep
|
||||
// track of transactions that have been prepared (written to WAL)
|
||||
// but not yet committed.
|
||||
while (!min_log_with_prep_.empty()) {
|
||||
min_log = min_log_with_prep_.top();
|
||||
|
||||
auto it = prepared_section_completed_.find(min_log);
|
||||
|
||||
// value was marked as 'deleted' from heap
|
||||
if (it != prepared_section_completed_.end() && it->second > 0) {
|
||||
it->second -= 1;
|
||||
min_log_with_prep_.pop();
|
||||
|
||||
// back to squere one...
|
||||
min_log = 0;
|
||||
continue;
|
||||
} else {
|
||||
// found a valid value
|
||||
break;
|
||||
std::lock_guard<std::mutex> lock(logs_with_prep_mutex_);
|
||||
auto it = logs_with_prep_.begin();
|
||||
// start with the smallest log
|
||||
for (; it != logs_with_prep_.end();) {
|
||||
auto min_log = it->log;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock2(prepared_section_completed_mutex_);
|
||||
auto completed_it = prepared_section_completed_.find(min_log);
|
||||
if (completed_it == prepared_section_completed_.end() ||
|
||||
completed_it->second < it->cnt) {
|
||||
return min_log;
|
||||
}
|
||||
assert(completed_it != prepared_section_completed_.end() &&
|
||||
completed_it->second == it->cnt);
|
||||
prepared_section_completed_.erase(completed_it);
|
||||
}
|
||||
// erase from beginning in vector is not efficient but this function is not
|
||||
// on the fast path.
|
||||
it = logs_with_prep_.erase(it);
|
||||
}
|
||||
|
||||
return min_log;
|
||||
// no such log found
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t DBImpl::MinLogNumberToKeep() {
|
||||
@@ -108,11 +113,11 @@ uint64_t DBImpl::MinLogNumberToKeep() {
|
||||
// sections of outstanding transactions.
|
||||
//
|
||||
// We must check min logs with outstanding prep before we check
|
||||
// logs referneces by memtables because a log referenced by the
|
||||
// logs references by memtables because a log referenced by the
|
||||
// first data structure could transition to the second under us.
|
||||
//
|
||||
// TODO(horuff): iterating over all column families under db mutex.
|
||||
// should find more optimial solution
|
||||
// should find more optimal solution
|
||||
auto min_log_in_prep_heap = FindMinLogContainingOutstandingPrep();
|
||||
|
||||
if (min_log_in_prep_heap != 0 && min_log_in_prep_heap < log_number) {
|
||||
@@ -148,7 +153,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
|
||||
bool doing_the_full_scan = false;
|
||||
|
||||
// logic for figurint out if we're doing the full scan
|
||||
// logic for figuring out if we're doing the full scan
|
||||
if (no_full_scan) {
|
||||
doing_the_full_scan = false;
|
||||
} else if (force ||
|
||||
@@ -168,7 +173,7 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// threads
|
||||
// Since job_context->min_pending_output is set, until file scan finishes,
|
||||
// mutex_ cannot be released. Otherwise, we might see no min_pending_output
|
||||
// here but later find newer generated unfinalized files while scannint.
|
||||
// here but later find newer generated unfinalized files while scanning.
|
||||
if (!pending_outputs_.empty()) {
|
||||
job_context->min_pending_output = *pending_outputs_.begin();
|
||||
} else {
|
||||
@@ -236,11 +241,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
while (alive_log_files_.begin()->number < min_log_number) {
|
||||
auto& earliest = *alive_log_files_.begin();
|
||||
if (immutable_db_options_.recycle_log_file_num >
|
||||
log_recycle_files.size()) {
|
||||
log_recycle_files_.size()) {
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"adding log %" PRIu64 " to recycle list\n",
|
||||
earliest.number);
|
||||
log_recycle_files.push_back(earliest.number);
|
||||
log_recycle_files_.push_back(earliest.number);
|
||||
} else {
|
||||
job_context->log_delete_files.push_back(earliest.number);
|
||||
}
|
||||
@@ -250,11 +255,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
}
|
||||
job_context->size_log_to_delete += earliest.size;
|
||||
total_log_size_ -= earliest.size;
|
||||
if (concurrent_prepare_) {
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
alive_log_files_.pop_front();
|
||||
if (concurrent_prepare_) {
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
// Current log should always stay alive since it can't have
|
||||
@@ -281,8 +286,11 @@ void DBImpl::FindObsoleteFiles(JobContext* job_context, bool force,
|
||||
// We're just cleaning up for DB::Write().
|
||||
assert(job_context->logs_to_free.empty());
|
||||
job_context->logs_to_free = logs_to_free_;
|
||||
job_context->log_recycle_files.assign(log_recycle_files.begin(),
|
||||
log_recycle_files.end());
|
||||
job_context->log_recycle_files.assign(log_recycle_files_.begin(),
|
||||
log_recycle_files_.end());
|
||||
if (job_context->HaveSomethingToDelete()) {
|
||||
++pending_purge_obsolete_files_;
|
||||
}
|
||||
logs_to_free_.clear();
|
||||
}
|
||||
|
||||
@@ -300,9 +308,10 @@ bool CompareCandidateFile(const JobContext::CandidateFileInfo& first,
|
||||
}; // namespace
|
||||
|
||||
// Delete obsolete files and log status and information of file deletion
|
||||
void DBImpl::DeleteObsoleteFileImpl(Status file_deletion_status, int job_id,
|
||||
const std::string& fname, FileType type,
|
||||
uint64_t number, uint32_t path_id) {
|
||||
void DBImpl::DeleteObsoleteFileImpl(int job_id, const std::string& fname,
|
||||
FileType type, uint64_t number,
|
||||
uint32_t path_id) {
|
||||
Status file_deletion_status;
|
||||
if (type == kTableFile) {
|
||||
file_deletion_status =
|
||||
DeleteSSTFile(&immutable_db_options_, fname, path_id);
|
||||
@@ -335,19 +344,16 @@ void DBImpl::DeleteObsoleteFileImpl(Status file_deletion_status, int job_id,
|
||||
}
|
||||
|
||||
// Diffs the files listed in filenames and those that do not
|
||||
// belong to live files are posibly removed. Also, removes all the
|
||||
// belong to live files are possibly removed. Also, removes all the
|
||||
// files in sst_delete_files and log_delete_files.
|
||||
// It is not necessary to hold the mutex when invoking this method.
|
||||
void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) {
|
||||
TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:Begin");
|
||||
// we'd better have sth to delete
|
||||
assert(state.HaveSomethingToDelete());
|
||||
|
||||
// this checks if FindObsoleteFiles() was run before. If not, don't do
|
||||
// PurgeObsoleteFiles(). If FindObsoleteFiles() was run, we need to also
|
||||
// run PurgeObsoleteFiles(), even if disable_delete_obsolete_files_ is true
|
||||
if (state.manifest_file_number == 0) {
|
||||
return;
|
||||
}
|
||||
// FindObsoleteFiles() should've populated this so nonzero
|
||||
assert(state.manifest_file_number != 0);
|
||||
|
||||
// Now, convert live list to an unordered map, WITHOUT mutex held;
|
||||
// set is slow.
|
||||
@@ -488,8 +494,7 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) {
|
||||
InstrumentedMutexLock guard_lock(&mutex_);
|
||||
SchedulePendingPurge(fname, type, number, path_id, state.job_id);
|
||||
} else {
|
||||
DeleteObsoleteFileImpl(file_deletion_status, state.job_id, fname, type,
|
||||
number, path_id);
|
||||
DeleteObsoleteFileImpl(state.job_id, fname, type, number, path_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,6 +536,13 @@ void DBImpl::PurgeObsoleteFiles(const JobContext& state, bool schedule_only) {
|
||||
wal_manager_.PurgeObsoleteWALFiles();
|
||||
#endif // ROCKSDB_LITE
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
--pending_purge_obsolete_files_;
|
||||
assert(pending_purge_obsolete_files_ >= 0);
|
||||
if (pending_purge_obsolete_files_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::PurgeObsoleteFiles:End");
|
||||
}
|
||||
|
||||
void DBImpl::DeleteObsoleteFiles() {
|
||||
|
||||
+94
-17
@@ -124,6 +124,17 @@ DBOptions SanitizeOptions(const std::string& dbname, const DBOptions& src) {
|
||||
result.avoid_flush_during_recovery = false;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// When the DB is stopped, it's possible that there are some .trash files that
|
||||
// were not deleted yet, when we open the DB we will find these .trash files
|
||||
// and schedule them to be deleted (or delete immediately if SstFileManager
|
||||
// was not used)
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(result.sst_file_manager.get());
|
||||
for (size_t i = 0; i < result.db_paths.size(); i++) {
|
||||
DeleteScheduler::CleanupDirectory(result.env, sfm, result.db_paths[i].path);
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -345,6 +356,29 @@ Status DBImpl::Recover(
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
// Verify compatibility of env_options_ and filesystem
|
||||
{
|
||||
unique_ptr<RandomAccessFile> idfile;
|
||||
EnvOptions customized_env(env_options_);
|
||||
customized_env.use_direct_reads |=
|
||||
immutable_db_options_.use_direct_io_for_flush_and_compaction;
|
||||
s = env_->NewRandomAccessFile(IdentityFileName(dbname_), &idfile,
|
||||
customized_env);
|
||||
if (!s.ok()) {
|
||||
const char* error_msg = s.ToString().c_str();
|
||||
// Check if unsupported Direct I/O is the root cause
|
||||
customized_env.use_direct_reads = false;
|
||||
s = env_->NewRandomAccessFile(IdentityFileName(dbname_), &idfile,
|
||||
customized_env);
|
||||
if (s.ok()) {
|
||||
return Status::InvalidArgument(
|
||||
"Direct I/O is not supported by the specified DB.");
|
||||
} else {
|
||||
return Status::InvalidArgument(
|
||||
"Found options incompatible with filesystem", error_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Status s = versions_->Recover(column_families, read_only);
|
||||
@@ -493,11 +527,12 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
bool stop_replay_by_wal_filter = false;
|
||||
bool stop_replay_for_corruption = false;
|
||||
bool flushed = false;
|
||||
uint64_t corrupted_log_number = kMaxSequenceNumber;
|
||||
for (auto log_number : log_numbers) {
|
||||
// The previous incarnation may not have written any MANIFEST
|
||||
// records after allocating this log number. So we manually
|
||||
// update the file number allocation counter in VersionSet.
|
||||
versions_->MarkFileNumberUsedDuringRecovery(log_number);
|
||||
versions_->MarkFileNumberUsed(log_number);
|
||||
// Open the log file
|
||||
std::string fname = LogFileName(immutable_db_options_.wal_dir, log_number);
|
||||
|
||||
@@ -674,7 +709,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
status = WriteBatchInternal::InsertInto(
|
||||
&batch, column_family_memtables_.get(), &flush_scheduler_, true,
|
||||
log_number, this, false /* concurrent_memtable_writes */,
|
||||
next_sequence, &has_valid_writes);
|
||||
next_sequence, &has_valid_writes, seq_per_batch_);
|
||||
MaybeIgnoreError(&status);
|
||||
if (!status.ok()) {
|
||||
// We are treating this as a failure while reading since we read valid
|
||||
@@ -711,6 +746,12 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
}
|
||||
|
||||
if (!status.ok()) {
|
||||
if (status.IsNotSupported()) {
|
||||
// We should not treat NotSupported as corruption. It is rather a clear
|
||||
// sign that we are processing a WAL that is produced by an incompatible
|
||||
// version of the code.
|
||||
return status;
|
||||
}
|
||||
if (immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kSkipAnyCorruptedRecords) {
|
||||
// We should ignore all errors unconditionally
|
||||
@@ -720,6 +761,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// We should ignore the error but not continue replaying
|
||||
status = Status::OK();
|
||||
stop_replay_for_corruption = true;
|
||||
corrupted_log_number = log_number;
|
||||
ROCKS_LOG_INFO(immutable_db_options_.info_log,
|
||||
"Point in time recovered to log #%" PRIu64
|
||||
" seq #%" PRIu64,
|
||||
@@ -737,10 +779,30 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
auto last_sequence = *next_sequence - 1;
|
||||
if ((*next_sequence != kMaxSequenceNumber) &&
|
||||
(versions_->LastSequence() <= last_sequence)) {
|
||||
versions_->SetLastToBeWrittenSequence(last_sequence);
|
||||
versions_->SetLastAllocatedSequence(last_sequence);
|
||||
versions_->SetLastPublishedSequence(last_sequence);
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
}
|
||||
// Compare the corrupted log number to all columnfamily's current log number.
|
||||
// Abort Open() if any column family's log number is greater than
|
||||
// the corrupted log number, which means CF contains data beyond the point of
|
||||
// corruption. This could during PIT recovery when the WAL is corrupted and
|
||||
// some (but not all) CFs are flushed
|
||||
if (stop_replay_for_corruption == true &&
|
||||
(immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kPointInTimeRecovery ||
|
||||
immutable_db_options_.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords)) {
|
||||
for (auto cfd : *versions_->GetColumnFamilySet()) {
|
||||
if (cfd->GetLogNumber() > corrupted_log_number) {
|
||||
ROCKS_LOG_ERROR(immutable_db_options_.info_log,
|
||||
"Column family inconsistency: SST file contains data"
|
||||
" beyond the point of corruption.");
|
||||
return Status::Corruption("SST file is ahead of WALs");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// True if there's any data in the WALs; if not, we can skip re-processing
|
||||
// them later
|
||||
@@ -796,7 +858,7 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
// not actually used. that is because VersionSet assumes
|
||||
// VersionSet::next_file_number_ always to be strictly greater than any
|
||||
// log number
|
||||
versions_->MarkFileNumberUsedDuringRecovery(max_log_number + 1);
|
||||
versions_->MarkFileNumberUsed(max_log_number + 1);
|
||||
status = versions_->LogAndApply(
|
||||
cfd, *cfd->GetLatestMutableCFOptions(), edit, &mutex_);
|
||||
if (!status.ok()) {
|
||||
@@ -809,13 +871,13 @@ Status DBImpl::RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
if (data_seen && !flushed) {
|
||||
// Mark these as alive so they'll be considered for deletion later by
|
||||
// FindObsoleteFiles()
|
||||
if (concurrent_prepare_) {
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
for (auto log_number : log_numbers) {
|
||||
alive_log_files_.push_back(LogFileNumberSize(log_number));
|
||||
}
|
||||
if (concurrent_prepare_) {
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
}
|
||||
@@ -857,26 +919,28 @@ Status DBImpl::WriteLevel0TableForRecovery(int job_id, ColumnFamilyData* cfd,
|
||||
const uint64_t current_time = static_cast<uint64_t>(_current_time);
|
||||
|
||||
{
|
||||
auto write_hint = cfd->CalculateSSTWriteHint(0);
|
||||
mutex_.Unlock();
|
||||
|
||||
SequenceNumber earliest_write_conflict_snapshot;
|
||||
std::vector<SequenceNumber> snapshot_seqs =
|
||||
snapshots_.GetAll(&earliest_write_conflict_snapshot);
|
||||
|
||||
EnvOptions optimized_env_options =
|
||||
env_->OptimizeForCompactionTableWrite(env_options_, immutable_db_options_);
|
||||
auto snapshot_checker = snapshot_checker_.get();
|
||||
if (use_custom_gc_ && snapshot_checker == nullptr) {
|
||||
snapshot_checker = DisableGCSnapshotChecker::Instance();
|
||||
}
|
||||
s = BuildTable(
|
||||
dbname_, env_, *cfd->ioptions(), mutable_cf_options,
|
||||
optimized_env_options, cfd->table_cache(), iter.get(),
|
||||
env_options_for_compaction_, cfd->table_cache(), iter.get(),
|
||||
std::unique_ptr<InternalIterator>(mem->NewRangeTombstoneIterator(ro)),
|
||||
&meta, cfd->internal_comparator(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
snapshot_seqs, earliest_write_conflict_snapshot,
|
||||
snapshot_seqs, earliest_write_conflict_snapshot, snapshot_checker,
|
||||
GetCompressionFlush(*cfd->ioptions(), mutable_cf_options),
|
||||
cfd->ioptions()->compression_opts, paranoid_file_checks,
|
||||
cfd->internal_stats(), TableFileCreationReason::kRecovery,
|
||||
&event_logger_, job_id, Env::IO_HIGH, nullptr /* table_properties */,
|
||||
-1 /* level */, current_time);
|
||||
-1 /* level */, current_time, write_hint);
|
||||
LogFlush(immutable_db_options_.info_log);
|
||||
ROCKS_LOG_DEBUG(immutable_db_options_.info_log,
|
||||
"[%s] [WriteLevel0TableForRecovery]"
|
||||
@@ -929,6 +993,15 @@ Status DB::Open(const Options& options, const std::string& dbname, DB** dbptr) {
|
||||
Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr) {
|
||||
const bool seq_per_batch = true;
|
||||
return DBImpl::Open(db_options, dbname, column_families, handles, dbptr,
|
||||
!seq_per_batch);
|
||||
}
|
||||
|
||||
Status DBImpl::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
std::vector<ColumnFamilyHandle*>* handles, DB** dbptr,
|
||||
const bool seq_per_batch) {
|
||||
Status s = SanitizeOptionsByTable(db_options, column_families);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -948,7 +1021,7 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
std::max(max_write_buffer_size, cf.options.write_buffer_size);
|
||||
}
|
||||
|
||||
DBImpl* impl = new DBImpl(db_options, dbname);
|
||||
DBImpl* impl = new DBImpl(db_options, dbname, seq_per_batch);
|
||||
s = impl->env_->CreateDirIfMissing(impl->immutable_db_options_.wal_dir);
|
||||
if (s.ok()) {
|
||||
for (auto db_path : impl->immutable_db_options_.db_paths) {
|
||||
@@ -970,6 +1043,7 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
return s;
|
||||
}
|
||||
impl->mutex_.Lock();
|
||||
auto write_hint = impl->CalculateWALWriteHint();
|
||||
// Handles create_if_missing, error_if_exists
|
||||
s = impl->Recover(column_families);
|
||||
if (s.ok()) {
|
||||
@@ -985,6 +1059,7 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
LogFileName(impl->immutable_db_options_.wal_dir, new_log_number),
|
||||
&lfile, opt_env_options);
|
||||
if (s.ok()) {
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
lfile->SetPreallocationBlockSize(
|
||||
impl->GetWalPreallocateBlockSize(max_write_buffer_size));
|
||||
{
|
||||
@@ -1027,16 +1102,18 @@ Status DB::Open(const DBOptions& db_options, const std::string& dbname,
|
||||
}
|
||||
}
|
||||
if (s.ok()) {
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
delete impl->InstallSuperVersionAndScheduleWork(
|
||||
cfd, nullptr, *cfd->GetLatestMutableCFOptions());
|
||||
impl->InstallSuperVersionAndScheduleWork(
|
||||
cfd, &sv_context, *cfd->GetLatestMutableCFOptions());
|
||||
}
|
||||
if (impl->concurrent_prepare_) {
|
||||
sv_context.Clean();
|
||||
if (impl->two_write_queues_) {
|
||||
impl->log_write_mutex_.Lock();
|
||||
}
|
||||
impl->alive_log_files_.push_back(
|
||||
DBImpl::LogFileNumberSize(impl->logfile_number_));
|
||||
if (impl->concurrent_prepare_) {
|
||||
if (impl->two_write_queues_) {
|
||||
impl->log_write_mutex_.Unlock();
|
||||
}
|
||||
impl->DeleteObsoleteFiles();
|
||||
|
||||
@@ -57,6 +57,7 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& read_options,
|
||||
auto cfd = cfh->cfd();
|
||||
SuperVersion* super_version = cfd->GetSuperVersion()->Ref();
|
||||
SequenceNumber latest_snapshot = versions_->LastSequence();
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
auto db_iter = NewArenaWrappedDbIterator(
|
||||
env_, read_options, *cfd->ioptions(),
|
||||
(read_options.snapshot != nullptr
|
||||
@@ -64,7 +65,7 @@ Iterator* DBImplReadOnly::NewIterator(const ReadOptions& read_options,
|
||||
->number_
|
||||
: latest_snapshot),
|
||||
super_version->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
super_version->version_number);
|
||||
super_version->version_number, read_callback);
|
||||
auto internal_iter =
|
||||
NewInternalIterator(read_options, cfd, super_version, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator());
|
||||
@@ -76,6 +77,7 @@ Status DBImplReadOnly::NewIterators(
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_families,
|
||||
std::vector<Iterator*>* iterators) {
|
||||
ReadCallback* read_callback = nullptr; // No read callback provided.
|
||||
if (iterators == nullptr) {
|
||||
return Status::InvalidArgument("iterators not allowed to be nullptr");
|
||||
}
|
||||
@@ -93,7 +95,7 @@ Status DBImplReadOnly::NewIterators(
|
||||
->number_
|
||||
: latest_snapshot),
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
sv->version_number);
|
||||
sv->version_number, read_callback);
|
||||
auto* internal_iter =
|
||||
NewInternalIterator(read_options, cfd, sv, db_iter->GetArena(),
|
||||
db_iter->GetRangeDelAggregator());
|
||||
@@ -105,7 +107,7 @@ Status DBImplReadOnly::NewIterators(
|
||||
}
|
||||
|
||||
Status DB::OpenForReadOnly(const Options& options, const std::string& dbname,
|
||||
DB** dbptr, bool error_if_log_file_exist) {
|
||||
DB** dbptr, bool /*error_if_log_file_exist*/) {
|
||||
*dbptr = nullptr;
|
||||
|
||||
// Try to first open DB as fully compacted DB
|
||||
@@ -140,6 +142,7 @@ Status DB::OpenForReadOnly(
|
||||
*dbptr = nullptr;
|
||||
handles->clear();
|
||||
|
||||
SuperVersionContext sv_context(/* create_superversion */ true);
|
||||
DBImplReadOnly* impl = new DBImplReadOnly(db_options, dbname);
|
||||
impl->mutex_.Lock();
|
||||
Status s = impl->Recover(column_families, true /* read only */,
|
||||
@@ -158,10 +161,12 @@ Status DB::OpenForReadOnly(
|
||||
}
|
||||
if (s.ok()) {
|
||||
for (auto cfd : *impl->versions_->GetColumnFamilySet()) {
|
||||
delete cfd->InstallSuperVersion(new SuperVersion(), &impl->mutex_);
|
||||
sv_context.NewSuperVersion();
|
||||
cfd->InstallSuperVersion(&sv_context, &impl->mutex_);
|
||||
}
|
||||
}
|
||||
impl->mutex_.Unlock();
|
||||
sv_context.Clean();
|
||||
if (s.ok()) {
|
||||
*dbptr = impl;
|
||||
for (auto* h : *handles) {
|
||||
|
||||
+32
-29
@@ -36,46 +36,49 @@ class DBImplReadOnly : public DBImpl {
|
||||
std::vector<Iterator*>* iterators) override;
|
||||
|
||||
using DBImpl::Put;
|
||||
virtual Status Put(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) override {
|
||||
virtual Status Put(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/, const Slice& /*value*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
using DBImpl::Merge;
|
||||
virtual Status Merge(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
const Slice& value) override {
|
||||
virtual Status Merge(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/, const Slice& /*value*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
using DBImpl::Delete;
|
||||
virtual Status Delete(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key) override {
|
||||
virtual Status Delete(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
using DBImpl::SingleDelete;
|
||||
virtual Status SingleDelete(const WriteOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice& key) override {
|
||||
virtual Status SingleDelete(const WriteOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status Write(const WriteOptions& options,
|
||||
WriteBatch* updates) override {
|
||||
virtual Status Write(const WriteOptions& /*options*/,
|
||||
WriteBatch* /*updates*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
using DBImpl::CompactRange;
|
||||
virtual Status CompactRange(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* begin, const Slice* end) override {
|
||||
virtual Status CompactRange(const CompactRangeOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice* /*begin*/,
|
||||
const Slice* /*end*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::CompactFiles;
|
||||
virtual Status CompactFiles(
|
||||
const CompactionOptions& compact_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
const int output_level, const int output_path_id = -1) override {
|
||||
const CompactionOptions& /*compact_options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*input_file_names*/,
|
||||
const int /*output_level*/, const int /*output_path_id*/ = -1,
|
||||
std::vector<std::string>* const /*output_file_names*/ = nullptr
|
||||
) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
@@ -83,18 +86,18 @@ class DBImplReadOnly : public DBImpl {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
virtual Status EnableFileDeletions(bool force) override {
|
||||
virtual Status EnableFileDeletions(bool /*force*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&,
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable = true) override {
|
||||
uint64_t* /*manifest_file_size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
using DBImpl::Flush;
|
||||
virtual Status Flush(const FlushOptions& options,
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
virtual Status Flush(const FlushOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
@@ -105,9 +108,9 @@ class DBImplReadOnly : public DBImpl {
|
||||
|
||||
using DB::IngestExternalFile;
|
||||
virtual Status IngestExternalFile(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& external_files,
|
||||
const IngestExternalFileOptions& ingestion_options) override {
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*external_files*/,
|
||||
const IngestExternalFileOptions& /*ingestion_options*/) override {
|
||||
return Status::NotSupported("Not supported operation in read only mode.");
|
||||
}
|
||||
|
||||
|
||||
+273
-92
@@ -57,17 +57,34 @@ Status DBImpl::WriteWithCallback(const WriteOptions& write_options,
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// The main write queue. This is the only write queue that updates LastSequence.
|
||||
// When using one write queue, the same sequence also indicates the last
|
||||
// published sequence.
|
||||
Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
bool disable_memtable, uint64_t* seq_used) {
|
||||
bool disable_memtable, uint64_t* seq_used,
|
||||
size_t batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback) {
|
||||
assert(!seq_per_batch_ || batch_cnt != 0);
|
||||
if (my_batch == nullptr) {
|
||||
return Status::Corruption("Batch is nullptr!");
|
||||
}
|
||||
if (concurrent_prepare_ && immutable_db_options_.enable_pipelined_write) {
|
||||
if (write_options.sync && write_options.disableWAL) {
|
||||
return Status::InvalidArgument("Sync writes has to enable WAL.");
|
||||
}
|
||||
if (two_write_queues_ && immutable_db_options_.enable_pipelined_write) {
|
||||
return Status::NotSupported(
|
||||
"pipelined_writes is not compatible with concurrent prepares");
|
||||
}
|
||||
if (seq_per_batch_ && immutable_db_options_.enable_pipelined_write) {
|
||||
// TODO(yiwu): update pipeline write with seq_per_batch and batch_cnt
|
||||
return Status::NotSupported(
|
||||
"pipelined_writes is not compatible with seq_per_batch");
|
||||
}
|
||||
// Otherwise IsLatestPersistentState optimization does not make sense
|
||||
assert(!WriteBatchInternal::IsLatestPersistentState(my_batch) ||
|
||||
disable_memtable);
|
||||
|
||||
Status status;
|
||||
if (write_options.low_pri) {
|
||||
@@ -77,9 +94,9 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
}
|
||||
|
||||
if (concurrent_prepare_ && disable_memtable) {
|
||||
if (two_write_queues_ && disable_memtable) {
|
||||
return WriteImplWALOnly(write_options, my_batch, callback, log_used,
|
||||
log_ref, seq_used);
|
||||
log_ref, seq_used, batch_cnt, pre_release_callback);
|
||||
}
|
||||
|
||||
if (immutable_db_options_.enable_pipelined_write) {
|
||||
@@ -89,7 +106,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
disable_memtable);
|
||||
disable_memtable, batch_cnt, pre_release_callback);
|
||||
|
||||
if (!write_options.disableWAL) {
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
@@ -108,11 +125,23 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
&w, w.sequence, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/, this,
|
||||
true /*concurrent_memtable_writes*/);
|
||||
true /*concurrent_memtable_writes*/, seq_per_batch_, w.batch_cnt);
|
||||
}
|
||||
|
||||
if (write_thread_.CompleteParallelMemTableWriter(&w)) {
|
||||
// we're responsible for exit batch group
|
||||
for (auto* writer : *(w.write_group)) {
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
disable_memtable);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO(myabandeh): propagate status to write_group
|
||||
auto last_sequence = w.write_group->last_sequence;
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
@@ -144,15 +173,15 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
WriteThread::WriteGroup write_group;
|
||||
bool in_parallel_group = false;
|
||||
uint64_t last_sequence = kMaxSequenceNumber;
|
||||
if (!concurrent_prepare_) {
|
||||
if (!two_write_queues_) {
|
||||
last_sequence = versions_->LastSequence();
|
||||
}
|
||||
|
||||
mutex_.Lock();
|
||||
|
||||
bool need_log_sync = !write_options.disableWAL && write_options.sync;
|
||||
bool need_log_sync = write_options.sync;
|
||||
bool need_log_dir_sync = need_log_sync && !log_dir_synced_;
|
||||
if (!concurrent_prepare_ || !disable_memtable) {
|
||||
if (!two_write_queues_ || !disable_memtable) {
|
||||
// With concurrent writes we do preprocess only in the write thread that
|
||||
// also does write to memtable to avoid sync issue on shared data structure
|
||||
// with the other thread
|
||||
@@ -184,10 +213,12 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
// more than once to a particular key.
|
||||
bool parallel = immutable_db_options_.allow_concurrent_memtable_write &&
|
||||
write_group.size > 1;
|
||||
int total_count = 0;
|
||||
uint64_t total_byte_size = 0;
|
||||
size_t total_count = 0;
|
||||
size_t valid_batches = 0;
|
||||
size_t total_byte_size = 0;
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CheckCallback(this)) {
|
||||
valid_batches += writer->batch_cnt;
|
||||
if (writer->ShouldWriteToMemtable()) {
|
||||
total_count += WriteBatchInternal::Count(writer->batch);
|
||||
parallel = parallel && !writer->batch->HasMerge();
|
||||
@@ -197,8 +228,15 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
total_byte_size, WriteBatchInternal::ByteSize(writer->batch));
|
||||
}
|
||||
}
|
||||
// Note about seq_per_batch_: either disableWAL is set for the entire write
|
||||
// group or not. In either case we inc seq for each write batch with no
|
||||
// failed callback. This means that there could be a batch with
|
||||
// disalbe_memtable in between; although we do not write this batch to
|
||||
// memtable it still consumes a seq. Otherwise, if !seq_per_batch_, we inc
|
||||
// the seq per valid written key to mem.
|
||||
size_t seq_inc = seq_per_batch_ ? valid_batches : total_count;
|
||||
|
||||
const bool concurrent_update = concurrent_prepare_;
|
||||
const bool concurrent_update = two_write_queues_;
|
||||
// Update stats while we are an exclusive group leader, so we know
|
||||
// that nobody else can be writing to these particular stats.
|
||||
// We're optimistic, updating the stats before we successfully
|
||||
@@ -226,7 +264,7 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
if (!concurrent_prepare_) {
|
||||
if (!two_write_queues_) {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
status = WriteToWAL(write_group, log_writer, log_used, need_log_sync,
|
||||
@@ -235,32 +273,43 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
} else {
|
||||
if (status.ok() && !write_options.disableWAL) {
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
// LastToBeWrittenSequence is increased inside WriteToWAL under
|
||||
// LastAllocatedSequence is increased inside WriteToWAL under
|
||||
// wal_write_mutex_ to ensure ordered events in WAL
|
||||
status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
|
||||
total_count);
|
||||
seq_inc);
|
||||
} else {
|
||||
// Otherwise we inc seq number for memtable writes
|
||||
last_sequence = versions_->FetchAddLastToBeWrittenSequence(total_count);
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
}
|
||||
}
|
||||
assert(last_sequence != kMaxSequenceNumber);
|
||||
const SequenceNumber current_sequence = last_sequence + 1;
|
||||
last_sequence += total_count;
|
||||
last_sequence += seq_inc;
|
||||
|
||||
if (status.ok()) {
|
||||
PERF_TIMER_GUARD(write_memtable_time);
|
||||
|
||||
if (!parallel) {
|
||||
// w.sequence will be set inside InsertInto
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
write_group, current_sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, write_options.ignore_missing_column_families,
|
||||
0 /*recovery_log_number*/, this);
|
||||
0 /*recovery_log_number*/, this, parallel, seq_per_batch_);
|
||||
} else {
|
||||
SequenceNumber next_sequence = current_sequence;
|
||||
// Note: the logic for advancing seq here must be consistent with the
|
||||
// logic in WriteBatchInternal::InsertInto(write_group...) as well as
|
||||
// with WriteBatchInternal::InsertInto(write_batch...) that is called on
|
||||
// the merged batch during recovery from the WAL.
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->ShouldWriteToMemtable()) {
|
||||
writer->sequence = next_sequence;
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
writer->sequence = next_sequence;
|
||||
if (seq_per_batch_) {
|
||||
assert(writer->batch_cnt);
|
||||
next_sequence += writer->batch_cnt;
|
||||
} else if (writer->ShouldWriteToMemtable()) {
|
||||
next_sequence += WriteBatchInternal::Count(writer->batch);
|
||||
}
|
||||
}
|
||||
@@ -279,27 +328,28 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
w.status = WriteBatchInternal::InsertInto(
|
||||
&w, w.sequence, &column_family_memtables, &flush_scheduler_,
|
||||
write_options.ignore_missing_column_families, 0 /*log_number*/,
|
||||
this, true /*concurrent_memtable_writes*/);
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
this, true /*concurrent_memtable_writes*/, seq_per_batch_,
|
||||
w.batch_cnt);
|
||||
}
|
||||
}
|
||||
if (seq_used != nullptr) {
|
||||
*seq_used = w.sequence;
|
||||
}
|
||||
}
|
||||
}
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteCallbackStatusCheck(status);
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
mutex_.Lock();
|
||||
MarkLogsSynced(logfile_number_, need_log_dir_sync, status);
|
||||
mutex_.Unlock();
|
||||
// Requesting sync with concurrent_prepare_ is expected to be very rare. We
|
||||
// Requesting sync with two_write_queues_ is expected to be very rare. We
|
||||
// hance provide a simple implementation that is not necessarily efficient.
|
||||
if (concurrent_prepare_) {
|
||||
if (two_write_queues_) {
|
||||
if (manual_wal_flush_) {
|
||||
status = FlushWAL(true);
|
||||
} else {
|
||||
@@ -316,10 +366,21 @@ Status DBImpl::WriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
if (should_exit_batch_group) {
|
||||
if (status.ok()) {
|
||||
for (auto* writer : write_group) {
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
disable_memtable);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
versions_->SetLastSequence(last_sequence);
|
||||
}
|
||||
MemTableInsertStatusCheck(w.status);
|
||||
write_thread_.ExitAsBatchGroupLeader(write_group, w.status);
|
||||
write_thread_.ExitAsBatchGroupLeader(write_group, status);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
@@ -385,6 +446,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
RecordTick(stats_, NUMBER_KEYS_WRITTEN, total_count);
|
||||
stats->AddDBStats(InternalStats::BYTES_WRITTEN, total_byte_size);
|
||||
RecordTick(stats_, BYTES_WRITTEN, total_byte_size);
|
||||
MeasureTime(stats_, BYTES_PER_WRITE, total_byte_size);
|
||||
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
@@ -402,7 +464,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteCallbackStatusCheck(w.status);
|
||||
WriteStatusCheck(w.status);
|
||||
}
|
||||
|
||||
if (need_log_sync) {
|
||||
@@ -426,7 +488,7 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
memtable_write_group.status = WriteBatchInternal::InsertInto(
|
||||
memtable_write_group, w.sequence, column_family_memtables_.get(),
|
||||
&flush_scheduler_, write_options.ignore_missing_column_families,
|
||||
0 /*log_number*/, this);
|
||||
0 /*log_number*/, this, seq_per_batch_);
|
||||
versions_->SetLastSequence(memtable_write_group.last_sequence);
|
||||
write_thread_.ExitAsMemTableWriter(&w, memtable_write_group);
|
||||
}
|
||||
@@ -454,17 +516,19 @@ Status DBImpl::PipelinedWriteImpl(const WriteOptions& write_options,
|
||||
return w.FinalStatus();
|
||||
}
|
||||
|
||||
// The 2nd write queue. If enabled it will be used only for WAL-only writes.
|
||||
// This is the only queue that updates LastPublishedSequence which is only
|
||||
// applicable in a two-queue setting.
|
||||
Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
WriteBatch* my_batch, WriteCallback* callback,
|
||||
uint64_t* log_used, uint64_t log_ref,
|
||||
uint64_t* seq_used) {
|
||||
uint64_t* seq_used, size_t batch_cnt,
|
||||
PreReleaseCallback* pre_release_callback) {
|
||||
Status status;
|
||||
PERF_TIMER_GUARD(write_pre_and_post_process_time);
|
||||
WriteThread::Writer w(write_options, my_batch, callback, log_ref,
|
||||
true /* disable_memtable */);
|
||||
if (write_options.disableWAL) {
|
||||
return status;
|
||||
}
|
||||
true /* disable_memtable */, batch_cnt,
|
||||
pre_release_callback);
|
||||
RecordTick(stats_, WRITE_WITH_WAL);
|
||||
StopWatch write_sw(env_, immutable_db_options_.statistics.get(), DB_WRITE);
|
||||
|
||||
@@ -488,7 +552,7 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
// Note: no need to update last_batch_group_size_ here since the batch writes
|
||||
// to WAL only
|
||||
|
||||
uint64_t total_byte_size = 0;
|
||||
size_t total_byte_size = 0;
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CheckCallback(this)) {
|
||||
total_byte_size = WriteBatchInternal::AppendedByteSize(
|
||||
@@ -518,19 +582,39 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
PERF_TIMER_STOP(write_pre_and_post_process_time);
|
||||
|
||||
PERF_TIMER_GUARD(write_wal_time);
|
||||
// LastToBeWrittenSequence is increased inside WriteToWAL under
|
||||
// LastAllocatedSequence is increased inside WriteToWAL under
|
||||
// wal_write_mutex_ to ensure ordered events in WAL
|
||||
status = ConcurrentWriteToWAL(write_group, log_used, &last_sequence,
|
||||
0 /*total_count*/);
|
||||
size_t seq_inc = 0 /* total_count */;
|
||||
if (seq_per_batch_) {
|
||||
size_t total_batch_cnt = 0;
|
||||
for (auto* writer : write_group) {
|
||||
assert(writer->batch_cnt);
|
||||
total_batch_cnt += writer->batch_cnt;
|
||||
}
|
||||
seq_inc = total_batch_cnt;
|
||||
}
|
||||
if (!write_options.disableWAL) {
|
||||
status =
|
||||
ConcurrentWriteToWAL(write_group, log_used, &last_sequence, seq_inc);
|
||||
} else {
|
||||
// Otherwise we inc seq number to do solely the seq allocation
|
||||
last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
}
|
||||
auto curr_seq = last_sequence + 1;
|
||||
for (auto* writer : write_group) {
|
||||
if (writer->CheckCallback(this)) {
|
||||
writer->sequence = curr_seq;
|
||||
curr_seq += WriteBatchInternal::Count(writer->batch);
|
||||
if (writer->CallbackFailed()) {
|
||||
continue;
|
||||
}
|
||||
writer->sequence = curr_seq;
|
||||
if (seq_per_batch_) {
|
||||
assert(writer->batch_cnt);
|
||||
curr_seq += writer->batch_cnt;
|
||||
}
|
||||
// else seq advances only by memtable writes
|
||||
}
|
||||
if (status.ok() && write_options.sync) {
|
||||
// Requesting sync with concurrent_prepare_ is expected to be very rare. We
|
||||
assert(!write_options.disableWAL);
|
||||
// Requesting sync with two_write_queues_ is expected to be very rare. We
|
||||
// hance provide a simple implementation that is not necessarily efficient.
|
||||
if (manual_wal_flush_) {
|
||||
status = FlushWAL(true);
|
||||
@@ -541,9 +625,23 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
PERF_TIMER_START(write_pre_and_post_process_time);
|
||||
|
||||
if (!w.CallbackFailed()) {
|
||||
WriteCallbackStatusCheck(status);
|
||||
WriteStatusCheck(status);
|
||||
}
|
||||
nonmem_write_thread_.ExitAsBatchGroupLeader(write_group, w.status);
|
||||
if (status.ok()) {
|
||||
for (auto* writer : write_group) {
|
||||
if (!writer->CallbackFailed() && writer->pre_release_callback) {
|
||||
assert(writer->sequence != kMaxSequenceNumber);
|
||||
const bool DISABLE_MEMTABLE = true;
|
||||
Status ws = writer->pre_release_callback->Callback(writer->sequence,
|
||||
DISABLE_MEMTABLE);
|
||||
if (!ws.ok()) {
|
||||
status = ws;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
nonmem_write_thread_.ExitAsBatchGroupLeader(write_group, status);
|
||||
if (status.ok()) {
|
||||
status = w.FinalStatus();
|
||||
}
|
||||
@@ -553,7 +651,7 @@ Status DBImpl::WriteImplWALOnly(const WriteOptions& write_options,
|
||||
return status;
|
||||
}
|
||||
|
||||
void DBImpl::WriteCallbackStatusCheck(const Status& status) {
|
||||
void DBImpl::WriteStatusCheck(const Status& status) {
|
||||
// Is setting bg_error_ enough here? This will at least stop
|
||||
// compaction and fail any further writes.
|
||||
if (immutable_db_options_.paranoid_checks && !status.ok() &&
|
||||
@@ -605,7 +703,7 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
versions_->GetColumnFamilySet()->NumberOfColumnFamilies() == 1);
|
||||
if (UNLIKELY(status.ok() && !single_column_family_mode_ &&
|
||||
total_log_size_ > GetMaxTotalWalSize())) {
|
||||
status = HandleWALFull(write_context);
|
||||
status = SwitchWAL(write_context);
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && write_buffer_manager_->ShouldFlush())) {
|
||||
@@ -618,7 +716,7 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !bg_error_.ok())) {
|
||||
return bg_error_;
|
||||
status = bg_error_;
|
||||
}
|
||||
|
||||
if (UNLIKELY(status.ok() && !flush_scheduler_.Empty())) {
|
||||
@@ -663,18 +761,24 @@ Status DBImpl::PreprocessWrite(const WriteOptions& write_options,
|
||||
}
|
||||
|
||||
WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
WriteBatch* tmp_batch, size_t* write_with_wal) {
|
||||
WriteBatch* tmp_batch, size_t* write_with_wal,
|
||||
WriteBatch** to_be_cached_state) {
|
||||
assert(write_with_wal != nullptr);
|
||||
assert(tmp_batch != nullptr);
|
||||
assert(*to_be_cached_state == nullptr);
|
||||
WriteBatch* merged_batch = nullptr;
|
||||
*write_with_wal = 0;
|
||||
auto* leader = write_group.leader;
|
||||
if (write_group.size == 1 && leader->ShouldWriteToWAL() &&
|
||||
assert(!leader->disable_wal); // Same holds for all in the batch group
|
||||
if (write_group.size == 1 && !leader->CallbackFailed() &&
|
||||
leader->batch->GetWalTerminationPoint().is_cleared()) {
|
||||
// we simply write the first WriteBatch to WAL if the group only
|
||||
// contains one batch, that batch should be written to the WAL,
|
||||
// and the batch is not wanting to be truncated
|
||||
merged_batch = leader->batch;
|
||||
if (WriteBatchInternal::IsLatestPersistentState(merged_batch)) {
|
||||
*to_be_cached_state = merged_batch;
|
||||
}
|
||||
*write_with_wal = 1;
|
||||
} else {
|
||||
// WAL needs all of the batches flattened into a single batch.
|
||||
@@ -682,9 +786,13 @@ WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
// interface
|
||||
merged_batch = tmp_batch;
|
||||
for (auto writer : write_group) {
|
||||
if (writer->ShouldWriteToWAL()) {
|
||||
if (!writer->CallbackFailed()) {
|
||||
WriteBatchInternal::Append(merged_batch, writer->batch,
|
||||
/*WAL_only*/ true);
|
||||
if (WriteBatchInternal::IsLatestPersistentState(writer->batch)) {
|
||||
// We only need to cache the last of such write batch
|
||||
*to_be_cached_state = writer->batch;
|
||||
}
|
||||
(*write_with_wal)++;
|
||||
}
|
||||
}
|
||||
@@ -692,7 +800,7 @@ WriteBatch* DBImpl::MergeBatch(const WriteThread::WriteGroup& write_group,
|
||||
return merged_batch;
|
||||
}
|
||||
|
||||
// When concurrent_prepare_ is disabled, this function is called from the only
|
||||
// When two_write_queues_ is disabled, this function is called from the only
|
||||
// write thread. Otherwise this must be called holding log_write_mutex_.
|
||||
Status DBImpl::WriteToWAL(const WriteBatch& merged_batch,
|
||||
log::Writer* log_writer, uint64_t* log_used,
|
||||
@@ -718,9 +826,12 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
SequenceNumber sequence) {
|
||||
Status status;
|
||||
|
||||
assert(!write_group.leader->disable_wal);
|
||||
// Same holds for all in the batch group
|
||||
size_t write_with_wal = 0;
|
||||
WriteBatch* merged_batch =
|
||||
MergeBatch(write_group, &tmp_batch_, &write_with_wal);
|
||||
WriteBatch* to_be_cached_state = nullptr;
|
||||
WriteBatch* merged_batch = MergeBatch(write_group, &tmp_batch_,
|
||||
&write_with_wal, &to_be_cached_state);
|
||||
if (merged_batch == write_group.leader->batch) {
|
||||
write_group.leader->log_used = logfile_number_;
|
||||
} else if (write_with_wal > 1) {
|
||||
@@ -733,6 +844,10 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
|
||||
uint64_t log_size;
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
|
||||
if (status.ok() && need_log_sync) {
|
||||
StopWatch sw(env_, stats_, WAL_FILE_SYNC_MICROS);
|
||||
@@ -777,13 +892,16 @@ Status DBImpl::WriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
uint64_t* log_used,
|
||||
SequenceNumber* last_sequence,
|
||||
int total_count) {
|
||||
size_t seq_inc) {
|
||||
Status status;
|
||||
|
||||
assert(!write_group.leader->disable_wal);
|
||||
// Same holds for all in the batch group
|
||||
WriteBatch tmp_batch;
|
||||
size_t write_with_wal = 0;
|
||||
WriteBatch* to_be_cached_state = nullptr;
|
||||
WriteBatch* merged_batch =
|
||||
MergeBatch(write_group, &tmp_batch, &write_with_wal);
|
||||
MergeBatch(write_group, &tmp_batch, &write_with_wal, &to_be_cached_state);
|
||||
|
||||
// We need to lock log_write_mutex_ since logs_ and alive_log_files might be
|
||||
// pushed back concurrently
|
||||
@@ -795,13 +913,17 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
writer->log_used = logfile_number_;
|
||||
}
|
||||
}
|
||||
*last_sequence = versions_->FetchAddLastToBeWrittenSequence(total_count);
|
||||
*last_sequence = versions_->FetchAddLastAllocatedSequence(seq_inc);
|
||||
auto sequence = *last_sequence + 1;
|
||||
WriteBatchInternal::SetSequence(merged_batch, sequence);
|
||||
|
||||
log::Writer* log_writer = logs_.back().writer;
|
||||
uint64_t log_size;
|
||||
status = WriteToWAL(*merged_batch, log_writer, log_used, &log_size);
|
||||
if (to_be_cached_state) {
|
||||
cached_recoverable_state_ = *to_be_cached_state;
|
||||
cached_recoverable_state_empty_ = false;
|
||||
}
|
||||
log_write_mutex_.Unlock();
|
||||
|
||||
if (status.ok()) {
|
||||
@@ -816,7 +938,40 @@ Status DBImpl::ConcurrentWriteToWAL(const WriteThread::WriteGroup& write_group,
|
||||
return status;
|
||||
}
|
||||
|
||||
Status DBImpl::HandleWALFull(WriteContext* write_context) {
|
||||
Status DBImpl::WriteRecoverableState() {
|
||||
mutex_.AssertHeld();
|
||||
if (!cached_recoverable_state_empty_) {
|
||||
bool dont_care_bool;
|
||||
SequenceNumber next_seq;
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
SequenceNumber seq = versions_->LastSequence();
|
||||
WriteBatchInternal::SetSequence(&cached_recoverable_state_, seq + 1);
|
||||
auto status = WriteBatchInternal::InsertInto(
|
||||
&cached_recoverable_state_, column_family_memtables_.get(),
|
||||
&flush_scheduler_, true, 0 /*recovery_log_number*/, this,
|
||||
false /* concurrent_memtable_writes */, &next_seq, &dont_care_bool,
|
||||
seq_per_batch_);
|
||||
auto last_seq = next_seq - 1;
|
||||
if (two_write_queues_) {
|
||||
versions_->FetchAddLastAllocatedSequence(last_seq - seq);
|
||||
}
|
||||
versions_->SetLastSequence(last_seq);
|
||||
versions_->SetLastPublishedSequence(last_seq);
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
if (status.ok()) {
|
||||
cached_recoverable_state_.Clear();
|
||||
cached_recoverable_state_empty_ = true;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::SwitchWAL(WriteContext* write_context) {
|
||||
mutex_.AssertHeld();
|
||||
assert(write_context != nullptr);
|
||||
Status status;
|
||||
@@ -868,7 +1023,7 @@ Status DBImpl::HandleWALFull(WriteContext* write_context) {
|
||||
break;
|
||||
}
|
||||
cfd->imm()->FlushRequested();
|
||||
SchedulePendingFlush(cfd);
|
||||
SchedulePendingFlush(cfd, FlushReason::kWriteBufferManager);
|
||||
}
|
||||
}
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
@@ -911,10 +1066,11 @@ Status DBImpl::HandleWriteBufferFull(WriteContext* write_context) {
|
||||
}
|
||||
}
|
||||
if (cfd_picked != nullptr) {
|
||||
status = SwitchMemtable(cfd_picked, write_context);
|
||||
status = SwitchMemtable(cfd_picked, write_context,
|
||||
FlushReason::kWriteBufferFull);
|
||||
if (status.ok()) {
|
||||
cfd_picked->imm()->FlushRequested();
|
||||
SchedulePendingFlush(cfd_picked);
|
||||
SchedulePendingFlush(cfd_picked, FlushReason::kWriteBufferFull);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
}
|
||||
@@ -1012,7 +1168,7 @@ Status DBImpl::ThrottleLowPriWritesIfNeeded(const WriteOptions& write_options,
|
||||
Status DBImpl::ScheduleFlushes(WriteContext* context) {
|
||||
ColumnFamilyData* cfd;
|
||||
while ((cfd = flush_scheduler_.TakeNextColumnFamily()) != nullptr) {
|
||||
auto status = SwitchMemtable(cfd, context);
|
||||
auto status = SwitchMemtable(cfd, context, FlushReason::kWriteBufferFull);
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
@@ -1024,7 +1180,7 @@ Status DBImpl::ScheduleFlushes(WriteContext* context) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* /*cfd*/,
|
||||
const MemTableInfo& mem_table_info) {
|
||||
if (immutable_db_options_.listeners.size() == 0U) {
|
||||
return;
|
||||
@@ -1041,10 +1197,11 @@ void DBImpl::NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
|
||||
// REQUIRES: mutex_ is held
|
||||
// REQUIRES: this thread is currently at the front of the writer queue
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context,
|
||||
FlushReason flush_reason) {
|
||||
mutex_.AssertHeld();
|
||||
WriteThread::Writer nonmem_w;
|
||||
if (concurrent_prepare_) {
|
||||
if (two_write_queues_) {
|
||||
// SwitchMemtable is a rare event. To simply the reasoning, we make sure
|
||||
// that there is no concurrent thread writing to WAL.
|
||||
nonmem_write_thread_.EnterUnbatched(&nonmem_w, &mutex_);
|
||||
@@ -1054,6 +1211,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
log::Writer* new_log = nullptr;
|
||||
MemTable* new_mem = nullptr;
|
||||
|
||||
// Recoverable state is persisted in WAL. After memtable switch, WAL might
|
||||
// be deleted, so we write the state to memtable to be persisted as well.
|
||||
Status s = WriteRecoverableState();
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
// In case of pipelined write is enabled, wait for all pending memtable
|
||||
// writers.
|
||||
if (immutable_db_options_.enable_pipelined_write) {
|
||||
@@ -1063,22 +1227,21 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
// Attempt to switch to a new memtable and trigger flush of old.
|
||||
// Do this without holding the dbmutex lock.
|
||||
assert(versions_->prev_log_number() == 0);
|
||||
if (concurrent_prepare_) {
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Lock();
|
||||
}
|
||||
bool creating_new_log = !log_empty_;
|
||||
if (concurrent_prepare_) {
|
||||
if (two_write_queues_) {
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
uint64_t recycle_log_number = 0;
|
||||
if (creating_new_log && immutable_db_options_.recycle_log_file_num &&
|
||||
!log_recycle_files.empty()) {
|
||||
recycle_log_number = log_recycle_files.front();
|
||||
log_recycle_files.pop_front();
|
||||
!log_recycle_files_.empty()) {
|
||||
recycle_log_number = log_recycle_files_.front();
|
||||
log_recycle_files_.pop_front();
|
||||
}
|
||||
uint64_t new_log_number =
|
||||
creating_new_log ? versions_->NewFileNumber() : logfile_number_;
|
||||
SuperVersion* new_superversion = nullptr;
|
||||
const MutableCFOptions mutable_cf_options = *cfd->GetLatestMutableCFOptions();
|
||||
|
||||
// Set memtable_info for memtable sealed callback
|
||||
@@ -1097,8 +1260,8 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
BuildDBOptions(immutable_db_options_, mutable_db_options_);
|
||||
const auto preallocate_block_size =
|
||||
GetWalPreallocateBlockSize(mutable_cf_options.write_buffer_size);
|
||||
auto write_hint = CalculateWALWriteHint();
|
||||
mutex_.Unlock();
|
||||
Status s;
|
||||
{
|
||||
if (creating_new_log) {
|
||||
EnvOptions opt_env_opt =
|
||||
@@ -1123,6 +1286,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
// use preallocate_block_size instead
|
||||
// of calling GetWalPreallocateBlockSize()
|
||||
lfile->SetPreallocationBlockSize(preallocate_block_size);
|
||||
lfile->SetWriteLifeTimeHint(write_hint);
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(lfile), opt_env_opt));
|
||||
new_log = new log::Writer(
|
||||
@@ -1134,7 +1298,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
if (s.ok()) {
|
||||
SequenceNumber seq = versions_->LastSequence();
|
||||
new_mem = cfd->ConstructNewMemtable(mutable_cf_options, seq);
|
||||
new_superversion = new SuperVersion();
|
||||
context->superversion_context.NewSuperVersion();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -1149,17 +1313,7 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
". Immutable memtables: %d.\n",
|
||||
cfd->GetName().c_str(), new_log_number, num_imm_unflushed);
|
||||
mutex_.Lock();
|
||||
if (!s.ok()) {
|
||||
// how do we fail if we're not creating new log?
|
||||
assert(creating_new_log);
|
||||
assert(!new_mem);
|
||||
assert(!new_log);
|
||||
if (concurrent_prepare_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
if (creating_new_log) {
|
||||
if (s.ok() && creating_new_log) {
|
||||
log_write_mutex_.Lock();
|
||||
logfile_number_ = new_log_number;
|
||||
assert(new_log != nullptr);
|
||||
@@ -1168,12 +1322,31 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
if (!logs_.empty()) {
|
||||
// Alway flush the buffer of the last log before switching to a new one
|
||||
log::Writer* cur_log_writer = logs_.back().writer;
|
||||
cur_log_writer->WriteBuffer();
|
||||
s = cur_log_writer->WriteBuffer();
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_WARN(immutable_db_options_.info_log,
|
||||
"[%s] Failed to switch from #%" PRIu64 " to #%" PRIu64
|
||||
" WAL file -- %s\n",
|
||||
cfd->GetName().c_str(), cur_log_writer->get_log_number(),
|
||||
new_log_number);
|
||||
}
|
||||
}
|
||||
logs_.emplace_back(logfile_number_, new_log);
|
||||
alive_log_files_.push_back(LogFileNumberSize(logfile_number_));
|
||||
log_write_mutex_.Unlock();
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
// how do we fail if we're not creating new log?
|
||||
assert(creating_new_log);
|
||||
assert(!new_mem);
|
||||
assert(!new_log);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
for (auto loop_cfd : *versions_->GetColumnFamilySet()) {
|
||||
// all this is just optimization to delete logs that
|
||||
// are no longer needed -- if CF is empty, that means it
|
||||
@@ -1192,9 +1365,9 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
cfd->imm()->Add(cfd->mem(), &context->memtables_to_free_);
|
||||
new_mem->Ref();
|
||||
cfd->SetMemtable(new_mem);
|
||||
context->superversions_to_free_.push_back(InstallSuperVersionAndScheduleWork(
|
||||
cfd, new_superversion, mutable_cf_options));
|
||||
if (concurrent_prepare_) {
|
||||
InstallSuperVersionAndScheduleWork(cfd, &context->superversion_context,
|
||||
mutable_cf_options, flush_reason);
|
||||
if (two_write_queues_) {
|
||||
nonmem_write_thread_.ExitUnbatched(&nonmem_w);
|
||||
}
|
||||
return s;
|
||||
@@ -1202,11 +1375,13 @@ Status DBImpl::SwitchMemtable(ColumnFamilyData* cfd, WriteContext* context) {
|
||||
|
||||
size_t DBImpl::GetWalPreallocateBlockSize(uint64_t write_buffer_size) const {
|
||||
mutex_.AssertHeld();
|
||||
size_t bsize = write_buffer_size / 10 + write_buffer_size;
|
||||
size_t bsize = static_cast<size_t>(
|
||||
write_buffer_size / 10 + write_buffer_size);
|
||||
// Some users might set very high write_buffer_size and rely on
|
||||
// max_total_wal_size or other parameters to control the WAL size.
|
||||
if (mutable_db_options_.max_total_wal_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, mutable_db_options_.max_total_wal_size);
|
||||
bsize = std::min<size_t>(bsize, static_cast<size_t>(
|
||||
mutable_db_options_.max_total_wal_size));
|
||||
}
|
||||
if (immutable_db_options_.db_write_buffer_size > 0) {
|
||||
bsize = std::min<size_t>(bsize, immutable_db_options_.db_write_buffer_size);
|
||||
@@ -1228,7 +1403,10 @@ Status DB::Put(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
// 8 bytes are taken by header, 4 bytes for count, 1 byte for type,
|
||||
// and we allocate 11 extra bytes for key length, as well as value length.
|
||||
WriteBatch batch(key.size() + value.size() + 24);
|
||||
batch.Put(column_family, key, value);
|
||||
Status s = batch.Put(column_family, key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
|
||||
@@ -1257,7 +1435,10 @@ Status DB::DeleteRange(const WriteOptions& opt,
|
||||
Status DB::Merge(const WriteOptions& opt, ColumnFamilyHandle* column_family,
|
||||
const Slice& key, const Slice& value) {
|
||||
WriteBatch batch;
|
||||
batch.Merge(column_family, key, value);
|
||||
Status s = batch.Merge(column_family, key, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
return Write(opt, &batch);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
+269
-73
@@ -8,9 +8,8 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include "db/db_iter.h"
|
||||
#include <stdexcept>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
@@ -18,7 +17,6 @@
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
@@ -50,7 +48,7 @@ static void DumpInternalIter(Iterator* iter) {
|
||||
// combines multiple entries for the same userkey found in the DB
|
||||
// representation into a single entry while accounting for sequence
|
||||
// numbers, deletion markers, overwrites, etc.
|
||||
class DBIter: public Iterator {
|
||||
class DBIter final: public Iterator {
|
||||
public:
|
||||
// The following is grossly complicated. TODO: clean it up
|
||||
// Which direction is the iterator currently moving?
|
||||
@@ -78,6 +76,7 @@ class DBIter: public Iterator {
|
||||
prev_count_ = 0;
|
||||
prev_found_count_ = 0;
|
||||
bytes_read_ = 0;
|
||||
skip_count_ = 0;
|
||||
}
|
||||
|
||||
void BumpGlobalStatistics(Statistics* global_statistics) {
|
||||
@@ -86,6 +85,7 @@ class DBIter: public Iterator {
|
||||
RecordTick(global_statistics, NUMBER_DB_PREV, prev_count_);
|
||||
RecordTick(global_statistics, NUMBER_DB_PREV_FOUND, prev_found_count_);
|
||||
RecordTick(global_statistics, ITER_BYTES_READ, bytes_read_);
|
||||
RecordTick(global_statistics, NUMBER_ITER_SKIP, skip_count_);
|
||||
PERF_COUNTER_ADD(iter_read_bytes, bytes_read_);
|
||||
ResetCounters();
|
||||
}
|
||||
@@ -100,12 +100,15 @@ class DBIter: public Iterator {
|
||||
uint64_t prev_found_count_;
|
||||
// Map to Tickers::ITER_BYTES_READ
|
||||
uint64_t bytes_read_;
|
||||
// Map to Tickers::NUMBER_ITER_SKIP
|
||||
uint64_t skip_count_;
|
||||
};
|
||||
|
||||
DBIter(Env* _env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options, const Comparator* cmp,
|
||||
InternalIterator* iter, SequenceNumber s, bool arena_mode,
|
||||
uint64_t max_sequential_skip_in_iterations)
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, bool allow_blob)
|
||||
: arena_mode_(arena_mode),
|
||||
env_(_env),
|
||||
logger_(cf_options.info_log),
|
||||
@@ -117,12 +120,18 @@ class DBIter: public Iterator {
|
||||
valid_(false),
|
||||
current_entry_is_merged_(false),
|
||||
statistics_(cf_options.statistics),
|
||||
num_internal_keys_skipped_(0),
|
||||
iterate_lower_bound_(read_options.iterate_lower_bound),
|
||||
iterate_upper_bound_(read_options.iterate_upper_bound),
|
||||
prefix_same_as_start_(read_options.prefix_same_as_start),
|
||||
pin_thru_lifetime_(read_options.pin_data),
|
||||
total_order_seek_(read_options.total_order_seek),
|
||||
range_del_agg_(cf_options.internal_comparator, s,
|
||||
true /* collapse_deletions */) {
|
||||
true /* collapse_deletions */),
|
||||
read_callback_(read_callback),
|
||||
allow_blob_(allow_blob),
|
||||
is_blob_(false),
|
||||
start_seqnum_(read_options.iter_start_seqnum) {
|
||||
RecordTick(statistics_, NO_ITERATORS);
|
||||
prefix_extractor_ = cf_options.prefix_extractor;
|
||||
max_skip_ = max_sequential_skip_in_iterations;
|
||||
@@ -139,7 +148,10 @@ class DBIter: public Iterator {
|
||||
if (pinned_iters_mgr_.PinningEnabled()) {
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
}
|
||||
RecordTick(statistics_, NO_ITERATORS, -1);
|
||||
// Compiler warning issue filed:
|
||||
// https://github.com/facebook/rocksdb/issues/3013
|
||||
RecordTick(statistics_, NO_ITERATORS, uint64_t(-1));
|
||||
ResetInternalKeysSkippedCounter();
|
||||
local_stats_.BumpGlobalStatistics(statistics_);
|
||||
if (!arena_mode_) {
|
||||
delete iter_;
|
||||
@@ -159,7 +171,12 @@ class DBIter: public Iterator {
|
||||
virtual bool Valid() const override { return valid_; }
|
||||
virtual Slice key() const override {
|
||||
assert(valid_);
|
||||
return saved_key_.GetUserKey();
|
||||
if(start_seqnum_ > 0) {
|
||||
return saved_key_.GetInternalKey();
|
||||
} else {
|
||||
return saved_key_.GetUserKey();
|
||||
}
|
||||
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
assert(valid_);
|
||||
@@ -180,6 +197,10 @@ class DBIter: public Iterator {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
bool IsBlob() const {
|
||||
assert(valid_ && (allow_blob_ || !is_blob_));
|
||||
return is_blob_;
|
||||
}
|
||||
|
||||
virtual Status GetProperty(std::string prop_name,
|
||||
std::string* prop) override {
|
||||
@@ -196,6 +217,9 @@ class DBIter: public Iterator {
|
||||
*prop = "Iterator is not valid.";
|
||||
}
|
||||
return Status::OK();
|
||||
} else if (prop_name == "rocksdb.iterator.internal-key") {
|
||||
*prop = saved_key_.GetUserKey().ToString();
|
||||
return Status::OK();
|
||||
}
|
||||
return Status::InvalidArgument("Undentified property.");
|
||||
}
|
||||
@@ -224,6 +248,7 @@ class DBIter: public Iterator {
|
||||
bool ParseKey(ParsedInternalKey* key);
|
||||
void MergeValuesNewToOld();
|
||||
bool TooManyInternalKeysSkipped(bool increment = true);
|
||||
bool IsVisible(SequenceNumber sequence);
|
||||
|
||||
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
||||
// is called
|
||||
@@ -250,6 +275,10 @@ class DBIter: public Iterator {
|
||||
}
|
||||
|
||||
inline void ResetInternalKeysSkippedCounter() {
|
||||
local_stats_.skip_count_ += num_internal_keys_skipped_;
|
||||
if (valid_) {
|
||||
local_stats_.skip_count_--;
|
||||
}
|
||||
num_internal_keys_skipped_ = 0;
|
||||
}
|
||||
|
||||
@@ -264,6 +293,10 @@ class DBIter: public Iterator {
|
||||
|
||||
Status status_;
|
||||
IterKey saved_key_;
|
||||
// Reusable internal key data structure. This is only used inside one function
|
||||
// and should not be used across functions. Reusing this object can reduce
|
||||
// overhead of calling construction of the function if creating it each time.
|
||||
ParsedInternalKey ikey_;
|
||||
std::string saved_value_;
|
||||
Slice pinned_value_;
|
||||
Direction direction_;
|
||||
@@ -274,6 +307,7 @@ class DBIter: public Iterator {
|
||||
uint64_t max_skip_;
|
||||
uint64_t max_skippable_internal_keys_;
|
||||
uint64_t num_internal_keys_skipped_;
|
||||
const Slice* iterate_lower_bound_;
|
||||
const Slice* iterate_upper_bound_;
|
||||
IterKey prefix_start_buf_;
|
||||
Slice prefix_start_key_;
|
||||
@@ -287,6 +321,12 @@ class DBIter: public Iterator {
|
||||
RangeDelAggregator range_del_agg_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
ReadCallback* read_callback_;
|
||||
bool allow_blob_;
|
||||
bool is_blob_;
|
||||
// for diff snapshots we want the lower bound on the seqnum;
|
||||
// if this value > 0 iterator will return internal keys
|
||||
SequenceNumber start_seqnum_;
|
||||
|
||||
// No copying allowed
|
||||
DBIter(const DBIter&);
|
||||
@@ -376,23 +416,23 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
// - none of the above : saved_key_ can contain anything, it doesn't matter.
|
||||
uint64_t num_skipped = 0;
|
||||
|
||||
do {
|
||||
ParsedInternalKey ikey;
|
||||
is_blob_ = false;
|
||||
|
||||
if (!ParseKey(&ikey)) {
|
||||
do {
|
||||
if (!ParseKey(&ikey_)) {
|
||||
// Skip corrupted keys.
|
||||
iter_->Next();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_->Compare(ikey.user_key, *iterate_upper_bound_) >= 0) {
|
||||
user_comparator_->Compare(ikey_.user_key, *iterate_upper_bound_) >= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (prefix_extractor_ && prefix_check &&
|
||||
prefix_extractor_->Transform(ikey.user_key)
|
||||
.compare(prefix_start_key_) != 0) {
|
||||
prefix_extractor_->Transform(ikey_.user_key)
|
||||
.compare(prefix_start_key_) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -400,49 +440,91 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ikey.sequence <= sequence_) {
|
||||
if (skipping &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) <=
|
||||
0) {
|
||||
if (IsVisible(ikey_.sequence)) {
|
||||
if (skipping && user_comparator_->Compare(ikey_.user_key,
|
||||
saved_key_.GetUserKey()) <= 0) {
|
||||
num_skipped++; // skip this entry
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
} else {
|
||||
num_skipped = 0;
|
||||
switch (ikey.type) {
|
||||
switch (ikey_.type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
saved_key_.SetUserKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = true;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
// if iterartor specified start_seqnum we
|
||||
// 1) return internal key, including the type
|
||||
// 2) return ikey only if ikey.seqnum >= start_seqnum_
|
||||
// not that if deletion seqnum is < start_seqnum_ we
|
||||
// just skip it like in normal iterator.
|
||||
if (start_seqnum_ > 0 && ikey_.sequence >= start_seqnum_) {
|
||||
saved_key_.SetInternalKey(ikey_);
|
||||
valid_=true;
|
||||
return;
|
||||
} else {
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
skipping = true;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
saved_key_.SetUserKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
case kTypeBlobIndex:
|
||||
if (start_seqnum_ > 0) {
|
||||
// we are taking incremental snapshot here
|
||||
// incremental snapshots aren't supported on DB with range deletes
|
||||
assert(!(
|
||||
(ikey_.type == kTypeBlobIndex) && (start_seqnum_ > 0)
|
||||
));
|
||||
if (ikey_.sequence >= start_seqnum_) {
|
||||
saved_key_.SetInternalKey(ikey_);
|
||||
valid_ = true;
|
||||
return;
|
||||
} else {
|
||||
// this key and all previous versions shouldn't be included,
|
||||
// skipping
|
||||
saved_key_.SetUserKey(ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
skipping = true;
|
||||
}
|
||||
} else {
|
||||
valid_ = true;
|
||||
return;
|
||||
saved_key_.SetUserKey(
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey_, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
} else if (ikey_.type == kTypeBlobIndex) {
|
||||
if (!allow_blob_) {
|
||||
ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
|
||||
status_ = Status::NotSupported(
|
||||
"Encounter unexpected blob index. Please open DB with "
|
||||
"rocksdb::blob_db::BlobDB instead.");
|
||||
valid_ = false;
|
||||
} else {
|
||||
is_blob_ = true;
|
||||
valid_ = true;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
valid_ = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kTypeMerge:
|
||||
saved_key_.SetUserKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
ikey_.user_key,
|
||||
!pin_thru_lifetime_ || !iter_->IsKeyPinned() /* copy */);
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
ikey_, RangeDelAggregator::RangePositioningMode::
|
||||
kForwardTraversal)) {
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
skipping = true;
|
||||
@@ -468,12 +550,12 @@ void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
|
||||
// Here saved_key_ may contain some old key, or the default empty key, or
|
||||
// key assigned by some random other method. We don't care.
|
||||
if (user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) <=
|
||||
if (user_comparator_->Compare(ikey_.user_key, saved_key_.GetUserKey()) <=
|
||||
0) {
|
||||
num_skipped++;
|
||||
} else {
|
||||
saved_key_.SetUserKey(
|
||||
ikey.user_key,
|
||||
ikey_.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = false;
|
||||
num_skipped = 0;
|
||||
@@ -532,10 +614,12 @@ void DBIter::MergeValuesNewToOld() {
|
||||
// Start the merge process by pushing the first operand
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:PushedFirstOperand");
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
Status s;
|
||||
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
||||
TEST_SYNC_POINT("DBIter::MergeValuesNewToOld:SteppedToNextOperand");
|
||||
if (!ParseKey(&ikey)) {
|
||||
// skip corrupted key
|
||||
continue;
|
||||
@@ -561,6 +645,7 @@ void DBIter::MergeValuesNewToOld() {
|
||||
merge_operator_, ikey.user_key, &val, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_, &pinned_value_, true);
|
||||
if (!s.ok()) {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
}
|
||||
// iter_ is positioned after put
|
||||
@@ -572,6 +657,18 @@ void DBIter::MergeValuesNewToOld() {
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
PERF_COUNTER_ADD(internal_merge_count, 1);
|
||||
} else if (kTypeBlobIndex == ikey.type) {
|
||||
if (!allow_blob_) {
|
||||
ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
|
||||
status_ = Status::NotSupported(
|
||||
"Encounter unexpected blob index. Please open DB with "
|
||||
"rocksdb::blob_db::BlobDB instead.");
|
||||
} else {
|
||||
status_ =
|
||||
Status::NotSupported("Blob DB does not support merge operator.");
|
||||
}
|
||||
valid_ = false;
|
||||
return;
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
@@ -586,6 +683,7 @@ void DBIter::MergeValuesNewToOld() {
|
||||
&saved_value_, logger_, statistics_, env_,
|
||||
&pinned_value_, true);
|
||||
if (!s.ok()) {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
}
|
||||
}
|
||||
@@ -642,7 +740,7 @@ void DBIter::ReverseToBackward() {
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetUserKey()) >
|
||||
0) {
|
||||
assert(ikey.sequence != kMaxSequenceNumber);
|
||||
if (ikey.sequence > sequence_) {
|
||||
if (!IsVisible(ikey.sequence)) {
|
||||
PERF_COUNTER_ADD(internal_recent_skipped_count, 1);
|
||||
} else {
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
@@ -677,8 +775,23 @@ void DBIter::PrevInternal() {
|
||||
ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
|
||||
if (prefix_extractor_ && prefix_same_as_start_ &&
|
||||
prefix_extractor_->Transform(saved_key_.GetUserKey())
|
||||
.compare(prefix_start_key_) != 0) {
|
||||
// Current key does not have the same prefix as start
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
// We've iterated earlier than the user-specified lower bound.
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (FindValueForCurrentKey()) {
|
||||
valid_ = true;
|
||||
if (!iter_->Valid()) {
|
||||
return;
|
||||
}
|
||||
@@ -686,11 +799,6 @@ void DBIter::PrevInternal() {
|
||||
if (user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
FindPrevUserKey();
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_ &&
|
||||
prefix_extractor_->Transform(saved_key_.GetUserKey())
|
||||
.compare(prefix_start_key_) != 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -731,7 +839,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
ReleaseTempPinnedData();
|
||||
TempPinData();
|
||||
size_t num_skipped = 0;
|
||||
while (iter_->Valid() && ikey.sequence <= sequence_ &&
|
||||
while (iter_->Valid() && IsVisible(ikey.sequence) &&
|
||||
user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey())) {
|
||||
if (TooManyInternalKeysSkipped()) {
|
||||
return false;
|
||||
@@ -745,6 +853,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
last_key_entry_type = ikey.type;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeValue:
|
||||
case kTypeBlobIndex:
|
||||
if (range_del_agg_.ShouldDelete(
|
||||
ikey,
|
||||
RangeDelAggregator::RangePositioningMode::kBackwardTraversal)) {
|
||||
@@ -790,6 +899,7 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
}
|
||||
|
||||
Status s;
|
||||
is_blob_ = false;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
@@ -805,6 +915,18 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
merge_operator_, saved_key_.GetUserKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_, logger_, statistics_,
|
||||
env_, &pinned_value_, true);
|
||||
} else if (last_not_merge_type == kTypeBlobIndex) {
|
||||
if (!allow_blob_) {
|
||||
ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
|
||||
status_ = Status::NotSupported(
|
||||
"Encounter unexpected blob index. Please open DB with "
|
||||
"rocksdb::blob_db::BlobDB instead.");
|
||||
} else {
|
||||
status_ =
|
||||
Status::NotSupported("Blob DB does not support merge operator.");
|
||||
}
|
||||
valid_ = false;
|
||||
return true;
|
||||
} else {
|
||||
assert(last_not_merge_type == kTypeValue);
|
||||
s = MergeHelper::TimedFullMerge(
|
||||
@@ -816,12 +938,25 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
case kTypeValue:
|
||||
// do nothing - we've already has value in saved_value_
|
||||
break;
|
||||
case kTypeBlobIndex:
|
||||
if (!allow_blob_) {
|
||||
ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
|
||||
status_ = Status::NotSupported(
|
||||
"Encounter unexpected blob index. Please open DB with "
|
||||
"rocksdb::blob_db::BlobDB instead.");
|
||||
valid_ = false;
|
||||
return true;
|
||||
}
|
||||
is_blob_ = true;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
valid_ = true;
|
||||
if (!s.ok()) {
|
||||
if (s.ok()) {
|
||||
valid_ = true;
|
||||
} else {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
}
|
||||
return true;
|
||||
@@ -842,6 +977,17 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
// assume there is at least one parseable key for this user key
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kForward);
|
||||
assert(iter_->Valid());
|
||||
assert(user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey()));
|
||||
|
||||
// In case read_callback presents, the value we seek to may not be visible.
|
||||
// Seek for the next value that's visible.
|
||||
while (!IsVisible(ikey.sequence)) {
|
||||
iter_->Next();
|
||||
FindParseableKey(&ikey, kForward);
|
||||
assert(iter_->Valid());
|
||||
assert(user_comparator_->Equal(ikey.user_key, saved_key_.GetUserKey()));
|
||||
}
|
||||
|
||||
if (ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion ||
|
||||
range_del_agg_.ShouldDelete(
|
||||
@@ -849,7 +995,15 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (ikey.type == kTypeValue) {
|
||||
if (ikey.type == kTypeBlobIndex && !allow_blob_) {
|
||||
ROCKS_LOG_ERROR(logger_, "Encounter unexpected blob index.");
|
||||
status_ = Status::NotSupported(
|
||||
"Encounter unexpected blob index. Please open DB with "
|
||||
"rocksdb::blob_db::BlobDB instead.");
|
||||
valid_ = false;
|
||||
return true;
|
||||
}
|
||||
if (ikey.type == kTypeValue || ikey.type == kTypeBlobIndex) {
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
valid_ = true;
|
||||
@@ -889,8 +1043,10 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
iter_->Seek(last_key);
|
||||
RecordTick(statistics_, NUMBER_OF_RESEEKS_IN_ITERATION);
|
||||
}
|
||||
valid_ = true;
|
||||
if (!s.ok()) {
|
||||
if (s.ok()) {
|
||||
valid_ = true;
|
||||
} else {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
}
|
||||
return true;
|
||||
@@ -901,8 +1057,10 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
&val, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_,
|
||||
&pinned_value_, true);
|
||||
valid_ = true;
|
||||
if (!s.ok()) {
|
||||
if (s.ok()) {
|
||||
valid_ = true;
|
||||
} else {
|
||||
valid_ = false;
|
||||
status_ = s;
|
||||
}
|
||||
return true;
|
||||
@@ -937,7 +1095,7 @@ void DBIter::FindPrevUserKey() {
|
||||
while (iter_->Valid() &&
|
||||
((cmp = user_comparator_->Compare(ikey.user_key,
|
||||
saved_key_.GetUserKey())) == 0 ||
|
||||
(cmp > 0 && ikey.sequence > sequence_))) {
|
||||
(cmp > 0 && !IsVisible(ikey.sequence)))) {
|
||||
if (TooManyInternalKeysSkipped()) {
|
||||
return;
|
||||
}
|
||||
@@ -955,7 +1113,7 @@ void DBIter::FindPrevUserKey() {
|
||||
}
|
||||
}
|
||||
assert(ikey.sequence != kMaxSequenceNumber);
|
||||
if (ikey.sequence > sequence_) {
|
||||
if (!IsVisible(ikey.sequence)) {
|
||||
PERF_COUNTER_ADD(internal_recent_skipped_count, 1);
|
||||
} else {
|
||||
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
|
||||
@@ -977,6 +1135,11 @@ bool DBIter::TooManyInternalKeysSkipped(bool increment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DBIter::IsVisible(SequenceNumber sequence) {
|
||||
return sequence <= sequence_ &&
|
||||
(read_callback_ == nullptr || read_callback_->IsCommitted(sequence));
|
||||
}
|
||||
|
||||
// Skip all unparseable keys
|
||||
void DBIter::FindParseableKey(ParsedInternalKey* ikey, Direction direction) {
|
||||
while (iter_->Valid() && !ParseKey(ikey)) {
|
||||
@@ -995,6 +1158,13 @@ void DBIter::Seek(const Slice& target) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(target, sequence_);
|
||||
|
||||
if (iterate_lower_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_lower_bound_) < 0) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_lower_bound_, sequence_);
|
||||
}
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->Seek(saved_key_.GetInternalKey());
|
||||
@@ -1013,6 +1183,7 @@ void DBIter::Seek(const Slice& target) {
|
||||
}
|
||||
if (statistics_ != nullptr) {
|
||||
if (valid_) {
|
||||
// Decrement since we don't want to count this key as skipped
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
RecordTick(statistics_, ITER_BYTES_READ, key().size() + value().size());
|
||||
PERF_COUNTER_ADD(iter_read_bytes, key().size() + value().size());
|
||||
@@ -1037,6 +1208,13 @@ void DBIter::SeekForPrev(const Slice& target) {
|
||||
saved_key_.SetInternalKey(target, 0 /* sequence_number */,
|
||||
kValueTypeForSeekForPrev);
|
||||
|
||||
if (iterate_upper_bound_ != nullptr &&
|
||||
user_comparator_->Compare(saved_key_.GetUserKey(),
|
||||
*iterate_upper_bound_) >= 0) {
|
||||
saved_key_.Clear();
|
||||
saved_key_.SetInternalKey(*iterate_upper_bound_, kMaxSequenceNumber);
|
||||
}
|
||||
|
||||
{
|
||||
PERF_TIMER_GUARD(seek_internal_seek_time);
|
||||
iter_->SeekForPrev(saved_key_.GetInternalKey());
|
||||
@@ -1076,6 +1254,10 @@ void DBIter::SeekToFirst() {
|
||||
if (prefix_extractor_ != nullptr) {
|
||||
max_skip_ = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
if (iterate_lower_bound_ != nullptr) {
|
||||
Seek(*iterate_lower_bound_);
|
||||
return;
|
||||
}
|
||||
direction_ = kForward;
|
||||
ReleaseTempPinnedData();
|
||||
ResetInternalKeysSkippedCounter();
|
||||
@@ -1135,7 +1317,8 @@ void DBIter::SeekToLast() {
|
||||
if (!Valid()) {
|
||||
return;
|
||||
} else if (user_comparator_->Equal(*iterate_upper_bound_, key())) {
|
||||
Prev();
|
||||
ReleaseTempPinnedData();
|
||||
PrevInternal();
|
||||
}
|
||||
} else {
|
||||
PrevInternal();
|
||||
@@ -1160,10 +1343,12 @@ Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
|
||||
const Comparator* user_key_comparator,
|
||||
InternalIterator* internal_iter,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations) {
|
||||
DBIter* db_iter = new DBIter(env, read_options, cf_options,
|
||||
user_key_comparator, internal_iter, sequence,
|
||||
false, max_sequential_skip_in_iterations);
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback, bool allow_blob) {
|
||||
DBIter* db_iter =
|
||||
new DBIter(env, read_options, cf_options, user_key_comparator,
|
||||
internal_iter, sequence, false,
|
||||
max_sequential_skip_in_iterations, read_callback, allow_blob);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
@@ -1191,6 +1376,7 @@ inline void ArenaWrappedDBIter::Prev() { db_iter_->Prev(); }
|
||||
inline Slice ArenaWrappedDBIter::key() const { return db_iter_->key(); }
|
||||
inline Slice ArenaWrappedDBIter::value() const { return db_iter_->value(); }
|
||||
inline Status ArenaWrappedDBIter::status() const { return db_iter_->status(); }
|
||||
bool ArenaWrappedDBIter::IsBlob() const { return db_iter_->IsBlob(); }
|
||||
inline Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
|
||||
std::string* prop) {
|
||||
if (prop_name == "rocksdb.iterator.super-version-number") {
|
||||
@@ -1207,19 +1393,26 @@ void ArenaWrappedDBIter::Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iteration,
|
||||
uint64_t version_number) {
|
||||
uint64_t version_number,
|
||||
ReadCallback* read_callback, bool allow_blob,
|
||||
bool allow_refresh) {
|
||||
auto mem = arena_.AllocateAligned(sizeof(DBIter));
|
||||
db_iter_ = new (mem)
|
||||
DBIter(env, read_options, cf_options, cf_options.user_comparator, nullptr,
|
||||
sequence, true, max_sequential_skip_in_iteration);
|
||||
sequence, true, max_sequential_skip_in_iteration, read_callback,
|
||||
allow_blob);
|
||||
sv_number_ = version_number;
|
||||
allow_refresh_ = allow_refresh;
|
||||
}
|
||||
|
||||
Status ArenaWrappedDBIter::Refresh() {
|
||||
if (cfd_ == nullptr || db_impl_ == nullptr) {
|
||||
if (cfd_ == nullptr || db_impl_ == nullptr || !allow_refresh_) {
|
||||
return Status::NotSupported("Creating renew iterator is not allowed.");
|
||||
}
|
||||
assert(db_iter_ != nullptr);
|
||||
// TODO(yiwu): For last_seq_same_as_publish_seq_==false, this is not the
|
||||
// correct behavior. Will be corrected automatically when we take a snapshot
|
||||
// here for the case of WritePreparedTxnDB.
|
||||
SequenceNumber latest_seq = db_impl_->GetLatestSequenceNumber();
|
||||
uint64_t cur_sv_number = cfd_->GetSuperVersionNumber();
|
||||
if (sv_number_ != cur_sv_number) {
|
||||
@@ -1231,7 +1424,7 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
SuperVersion* sv = cfd_->GetReferencedSuperVersion(db_impl_->mutex());
|
||||
Init(env, read_options_, *(cfd_->ioptions()), latest_seq,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number);
|
||||
cur_sv_number, read_callback_, allow_blob_, allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
|
||||
read_options_, cfd_, sv, &arena_, db_iter_->GetRangeDelAggregator());
|
||||
@@ -1247,12 +1440,15 @@ ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
DBImpl* db_impl, ColumnFamilyData* cfd) {
|
||||
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
bool allow_blob, bool allow_refresh) {
|
||||
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
|
||||
iter->Init(env, read_options, cf_options, sequence,
|
||||
max_sequential_skip_in_iterations, version_number);
|
||||
if (db_impl != nullptr && cfd != nullptr) {
|
||||
iter->StoreRefreshInfo(read_options, db_impl, cfd);
|
||||
max_sequential_skip_in_iterations, version_number, read_callback,
|
||||
allow_blob, allow_refresh);
|
||||
if (db_impl != nullptr && cfd != nullptr && allow_refresh) {
|
||||
iter->StoreRefreshInfo(read_options, db_impl, cfd, read_callback,
|
||||
allow_blob);
|
||||
}
|
||||
|
||||
return iter;
|
||||
|
||||
+16
-6
@@ -33,7 +33,9 @@ extern Iterator* NewDBIterator(Env* env, const ReadOptions& read_options,
|
||||
const Comparator* user_key_comparator,
|
||||
InternalIterator* internal_iter,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations);
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
ReadCallback* read_callback,
|
||||
bool allow_blob = false);
|
||||
|
||||
// A wrapper iterator which wraps DB Iterator and the arena, with which the DB
|
||||
// iterator is supposed be allocated. This class is used as an entry point of
|
||||
@@ -63,20 +65,24 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
virtual Slice value() const override;
|
||||
virtual Status status() const override;
|
||||
virtual Status Refresh() override;
|
||||
bool IsBlob() const;
|
||||
|
||||
virtual Status GetProperty(std::string prop_name, std::string* prop) override;
|
||||
|
||||
void Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations,
|
||||
uint64_t version_number);
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
ReadCallback* read_callback, bool allow_blob, bool allow_refresh);
|
||||
|
||||
void StoreRefreshInfo(const ReadOptions& read_options, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd) {
|
||||
ColumnFamilyData* cfd, ReadCallback* read_callback,
|
||||
bool allow_blob) {
|
||||
read_options_ = read_options;
|
||||
db_impl_ = db_impl;
|
||||
cfd_ = cfd;
|
||||
read_callback_ = read_callback;
|
||||
allow_blob_ = allow_blob;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -86,6 +92,9 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
ColumnFamilyData* cfd_ = nullptr;
|
||||
DBImpl* db_impl_ = nullptr;
|
||||
ReadOptions read_options_;
|
||||
ReadCallback* read_callback_;
|
||||
bool allow_blob_ = false;
|
||||
bool allow_refresh_ = true;
|
||||
};
|
||||
|
||||
// Generate the arena wrapped iterator class.
|
||||
@@ -95,6 +104,7 @@ extern ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
DBImpl* db_impl = nullptr, ColumnFamilyData* cfd = nullptr);
|
||||
|
||||
ReadCallback* read_callback, DBImpl* db_impl = nullptr,
|
||||
ColumnFamilyData* cfd = nullptr, bool allow_blob = false,
|
||||
bool allow_refresh = true);
|
||||
} // namespace rocksdb
|
||||
+404
-182
File diff suppressed because it is too large.
Load diff
+494
-100
File diff suppressed because it is too large.
Load diff
+81
-5
@@ -28,13 +28,11 @@ class MockMemTableRep : public MemTableRep {
|
||||
return rep_->Allocate(len, buf);
|
||||
}
|
||||
|
||||
virtual void Insert(KeyHandle handle) override {
|
||||
return rep_->Insert(handle);
|
||||
}
|
||||
virtual void Insert(KeyHandle handle) override { rep_->Insert(handle); }
|
||||
|
||||
virtual void InsertWithHint(KeyHandle handle, void** hint) override {
|
||||
num_insert_with_hint_++;
|
||||
ASSERT_NE(nullptr, hint);
|
||||
EXPECT_NE(nullptr, hint);
|
||||
last_hint_in_ = *hint;
|
||||
rep_->InsertWithHint(handle, hint);
|
||||
last_hint_out_ = *hint;
|
||||
@@ -121,7 +119,7 @@ class TestPrefixExtractor : public SliceTransform {
|
||||
return separator(key) != nullptr;
|
||||
}
|
||||
|
||||
virtual bool InRange(const Slice& key) const override { return false; }
|
||||
virtual bool InRange(const Slice& /*key*/) const override { return false; }
|
||||
|
||||
private:
|
||||
const char* separator(const Slice& key) const {
|
||||
@@ -129,6 +127,84 @@ class TestPrefixExtractor : public SliceTransform {
|
||||
}
|
||||
};
|
||||
|
||||
// Test that ::Add properly returns false when inserting duplicate keys
|
||||
TEST_F(DBMemTableTest, DuplicateSeq) {
|
||||
SequenceNumber seq = 123;
|
||||
std::string value;
|
||||
Status s;
|
||||
MergeContext merge_context;
|
||||
Options options;
|
||||
InternalKeyComparator ikey_cmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(ikey_cmp, {} /* snapshots */);
|
||||
|
||||
// Create a MemTable
|
||||
InternalKeyComparator cmp(BytewiseComparator());
|
||||
auto factory = std::make_shared<SkipListFactory>();
|
||||
options.memtable_factory = factory;
|
||||
ImmutableCFOptions ioptions(options);
|
||||
WriteBufferManager wb(options.db_write_buffer_size);
|
||||
MemTable* mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
|
||||
// Write some keys and make sure it returns false on duplicates
|
||||
bool res;
|
||||
res = mem->Add(seq, kTypeValue, "key", "value2");
|
||||
ASSERT_TRUE(res);
|
||||
res = mem->Add(seq, kTypeValue, "key", "value2");
|
||||
ASSERT_FALSE(res);
|
||||
// Changing the type should still cause the duplicatae key
|
||||
res = mem->Add(seq, kTypeMerge, "key", "value2");
|
||||
ASSERT_FALSE(res);
|
||||
// Changing the seq number will make the key fresh
|
||||
res = mem->Add(seq + 1, kTypeMerge, "key", "value2");
|
||||
ASSERT_TRUE(res);
|
||||
// Test with different types for duplicate keys
|
||||
res = mem->Add(seq, kTypeDeletion, "key", "");
|
||||
ASSERT_FALSE(res);
|
||||
res = mem->Add(seq, kTypeSingleDeletion, "key", "");
|
||||
ASSERT_FALSE(res);
|
||||
|
||||
// Test the duplicate keys under stress
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
bool insert_dup = i % 10 == 1;
|
||||
if (!insert_dup) {
|
||||
seq++;
|
||||
}
|
||||
res = mem->Add(seq, kTypeValue, "foo", "value" + ToString(seq));
|
||||
if (insert_dup) {
|
||||
ASSERT_FALSE(res);
|
||||
} else {
|
||||
ASSERT_TRUE(res);
|
||||
}
|
||||
}
|
||||
delete mem;
|
||||
|
||||
// Test with InsertWithHint
|
||||
options.memtable_insert_with_hint_prefix_extractor.reset(
|
||||
new TestPrefixExtractor()); // which uses _ to extract the prefix
|
||||
ioptions = ImmutableCFOptions(options);
|
||||
mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
// Insert a duplicate key with _ in it
|
||||
res = mem->Add(seq, kTypeValue, "key_1", "value");
|
||||
ASSERT_TRUE(res);
|
||||
res = mem->Add(seq, kTypeValue, "key_1", "value");
|
||||
ASSERT_FALSE(res);
|
||||
delete mem;
|
||||
|
||||
// Test when InsertConcurrently will be invoked
|
||||
options.allow_concurrent_memtable_write = true;
|
||||
ioptions = ImmutableCFOptions(options);
|
||||
mem = new MemTable(cmp, ioptions, MutableCFOptions(options), &wb,
|
||||
kMaxSequenceNumber, 0 /* column_family_id */);
|
||||
MemTablePostProcessInfo post_process_info;
|
||||
res = mem->Add(seq, kTypeValue, "key", "value", true, &post_process_info);
|
||||
ASSERT_TRUE(res);
|
||||
res = mem->Add(seq, kTypeValue, "key", "value", true, &post_process_info);
|
||||
ASSERT_FALSE(res);
|
||||
delete mem;
|
||||
}
|
||||
|
||||
TEST_F(DBMemTableTest, InsertWithHint) {
|
||||
Options options;
|
||||
options.allow_concurrent_memtable_write = false;
|
||||
|
||||
@@ -8,16 +8,125 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/forward_iterator.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
#include "utilities/merge_operators/string_append/stringappend2.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
TestReadCallback(SnapshotChecker* snapshot_checker,
|
||||
SequenceNumber snapshot_seq)
|
||||
: snapshot_checker_(snapshot_checker), snapshot_seq_(snapshot_seq) {}
|
||||
|
||||
bool IsCommitted(SequenceNumber seq) override {
|
||||
return snapshot_checker_->IsInSnapshot(seq, snapshot_seq_);
|
||||
}
|
||||
|
||||
private:
|
||||
SnapshotChecker* snapshot_checker_;
|
||||
SequenceNumber snapshot_seq_;
|
||||
};
|
||||
|
||||
// Test merge operator functionality.
|
||||
class DBMergeOperatorTest : public DBTestBase {
|
||||
public:
|
||||
DBMergeOperatorTest() : DBTestBase("/db_merge_operator_test") {}
|
||||
|
||||
std::string GetWithReadCallback(SnapshotChecker* snapshot_checker,
|
||||
const Slice& key,
|
||||
const Snapshot* snapshot = nullptr) {
|
||||
SequenceNumber seq = snapshot == nullptr ? db_->GetLatestSequenceNumber()
|
||||
: snapshot->GetSequenceNumber();
|
||||
TestReadCallback read_callback(snapshot_checker, seq);
|
||||
ReadOptions read_opt;
|
||||
read_opt.snapshot = snapshot;
|
||||
PinnableSlice value;
|
||||
Status s =
|
||||
dbfull()->GetImpl(read_opt, db_->DefaultColumnFamily(), key, &value,
|
||||
nullptr /*value_found*/, &read_callback);
|
||||
if (!s.ok()) {
|
||||
return s.ToString();
|
||||
}
|
||||
return value.ToString();
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DBMergeOperatorTest, LimitMergeOperands) {
|
||||
class LimitedStringAppendMergeOp : public StringAppendTESTOperator {
|
||||
public:
|
||||
LimitedStringAppendMergeOp(int limit, char delim)
|
||||
: StringAppendTESTOperator(delim), limit_(limit) {}
|
||||
|
||||
const char* Name() const override {
|
||||
return "DBMergeOperatorTest::LimitedStringAppendMergeOp";
|
||||
}
|
||||
|
||||
bool ShouldMerge(const std::vector<Slice>& operands) const override {
|
||||
if (operands.size() > 0 && limit_ > 0 && operands.size() >= limit_) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t limit_ = 0;
|
||||
};
|
||||
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
// Use only the latest two merge operands.
|
||||
options.merge_operator =
|
||||
std::make_shared<LimitedStringAppendMergeOp>(2, ',');
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
// All K1 values are in memtable.
|
||||
ASSERT_OK(Merge("k1", "a"));
|
||||
ASSERT_OK(Merge("k1", "b"));
|
||||
ASSERT_OK(Merge("k1", "c"));
|
||||
ASSERT_OK(Merge("k1", "d"));
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "k1", &value).ok());
|
||||
// Make sure that only the latest two merge operands are used. If this was
|
||||
// not the case the value would be "a,b,c,d".
|
||||
ASSERT_EQ(value, "c,d");
|
||||
|
||||
// All K2 values are flushed to L0 into a single file.
|
||||
ASSERT_OK(Merge("k2", "a"));
|
||||
ASSERT_OK(Merge("k2", "b"));
|
||||
ASSERT_OK(Merge("k2", "c"));
|
||||
ASSERT_OK(Merge("k2", "d"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "k2", &value).ok());
|
||||
ASSERT_EQ(value, "c,d");
|
||||
|
||||
// All K3 values are flushed and are in different files.
|
||||
ASSERT_OK(Merge("k3", "ab"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("k3", "bc"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("k3", "cd"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("k3", "de"));
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "k3", &value).ok());
|
||||
ASSERT_EQ(value, "cd,de");
|
||||
|
||||
// All K4 values are in different levels
|
||||
ASSERT_OK(Merge("k4", "ab"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(4);
|
||||
ASSERT_OK(Merge("k4", "bc"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(3);
|
||||
ASSERT_OK(Merge("k4", "cd"));
|
||||
ASSERT_OK(Flush());
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_OK(Merge("k4", "de"));
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), "k4", &value).ok());
|
||||
ASSERT_EQ(value, "cd,de");
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperatorTest, MergeErrorOnRead) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
@@ -57,16 +166,33 @@ TEST_F(DBMergeOperatorTest, MergeErrorOnIteration) {
|
||||
ASSERT_OK(Merge("k1", "v1"));
|
||||
ASSERT_OK(Merge("k1", "corrupted"));
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
VerifyDBFromMap({{"k1", ""}, {"k2", "v2"}}, nullptr, false,
|
||||
{{"k1", Status::Corruption()}});
|
||||
auto* iter = db_->NewIterator(ReadOptions());
|
||||
iter->Seek("k1");
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
delete iter;
|
||||
iter = db_->NewIterator(ReadOptions());
|
||||
iter->Seek("k2");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Prev();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
delete iter;
|
||||
VerifyDBInternal({{"k1", "corrupted"}, {"k1", "v1"}, {"k2", "v2"}});
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Merge("k1", "v1"));
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
ASSERT_OK(Merge("k2", "corrupted"));
|
||||
VerifyDBFromMap({{"k1", "v1"}, {"k2", ""}}, nullptr, false,
|
||||
{{"k2", Status::Corruption()}});
|
||||
iter = db_->NewIterator(ReadOptions());
|
||||
iter->Seek("k1");
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
iter->Next();
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_TRUE(iter->status().IsCorruption());
|
||||
delete iter;
|
||||
VerifyDBInternal({{"k1", "v1"}, {"k2", "corrupted"}, {"k2", "v2"}});
|
||||
}
|
||||
|
||||
@@ -158,7 +284,7 @@ TEST_P(MergeOperatorPinningTest, Randomized) {
|
||||
Random rnd(301);
|
||||
std::map<std::string, std::string> true_data;
|
||||
|
||||
const int kTotalMerges = 10000;
|
||||
const int kTotalMerges = 5000;
|
||||
// Every key gets ~10 operands
|
||||
const int kKeyRange = kTotalMerges / 10;
|
||||
const int kOperandSize = 20;
|
||||
@@ -356,8 +482,152 @@ TEST_P(MergeOperatorPinningTest, TailingIterator) {
|
||||
writer_thread.join();
|
||||
reader_thread.join();
|
||||
}
|
||||
|
||||
TEST_F(DBMergeOperatorTest, TailingIteratorMemtableUnrefedBySomeoneElse) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Overview of the test:
|
||||
// * There are two merge operands for the same key: one in an sst file,
|
||||
// another in a memtable.
|
||||
// * Seek a tailing iterator to this key.
|
||||
// * As part of the seek, the iterator will:
|
||||
// (a) first visit the operand in the memtable and tell ForwardIterator
|
||||
// to pin this operand, then
|
||||
// (b) move on to the operand in the sst file, then pass both operands
|
||||
// to merge operator.
|
||||
// * The memtable may get flushed and unreferenced by another thread between
|
||||
// (a) and (b). The test simulates it by flushing the memtable inside a
|
||||
// SyncPoint callback located between (a) and (b).
|
||||
// * In this case it's ForwardIterator's responsibility to keep the memtable
|
||||
// pinned until (b) is complete. There used to be a bug causing
|
||||
// ForwardIterator to not pin it in some circumstances. This test
|
||||
// reproduces it.
|
||||
|
||||
db_->Merge(WriteOptions(), "key", "sst");
|
||||
db_->Flush(FlushOptions()); // Switch to SuperVersion A
|
||||
db_->Merge(WriteOptions(), "key", "memtable");
|
||||
|
||||
// Pin SuperVersion A
|
||||
std::unique_ptr<Iterator> someone_else(db_->NewIterator(ReadOptions()));
|
||||
|
||||
bool pushed_first_operand = false;
|
||||
bool stepped_to_next_operand = false;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBIter::MergeValuesNewToOld:PushedFirstOperand", [&](void*) {
|
||||
EXPECT_FALSE(pushed_first_operand);
|
||||
pushed_first_operand = true;
|
||||
db_->Flush(FlushOptions()); // Switch to SuperVersion B
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBIter::MergeValuesNewToOld:SteppedToNextOperand", [&](void*) {
|
||||
EXPECT_FALSE(stepped_to_next_operand);
|
||||
stepped_to_next_operand = true;
|
||||
someone_else.reset(); // Unpin SuperVersion A
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ReadOptions ro;
|
||||
ro.tailing = true;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ro));
|
||||
iter->Seek("key");
|
||||
|
||||
ASSERT_TRUE(iter->status().ok());
|
||||
ASSERT_TRUE(iter->Valid());
|
||||
EXPECT_EQ(std::string("sst,memtable"), iter->value().ToString());
|
||||
EXPECT_TRUE(pushed_first_operand);
|
||||
EXPECT_TRUE(stepped_to_next_operand);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBMergeOperatorTest, SnapshotCheckerAndReadCallback) {
|
||||
Options options = CurrentOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
class TestSnapshotChecker : public SnapshotChecker {
|
||||
bool IsInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot_seq) const override {
|
||||
switch (snapshot_seq) {
|
||||
case 0:
|
||||
return seq == 0;
|
||||
case 1:
|
||||
return seq <= 1;
|
||||
case 2:
|
||||
// seq = 2 not visible to snapshot with seq = 2
|
||||
return seq <= 1;
|
||||
case 3:
|
||||
return seq <= 3;
|
||||
case 4:
|
||||
// seq = 4 not visible to snpahost with seq = 4
|
||||
return seq <= 3;
|
||||
default:
|
||||
// seq >=4 is uncommitted
|
||||
return seq <= 4;
|
||||
};
|
||||
}
|
||||
};
|
||||
TestSnapshotChecker* snapshot_checker = new TestSnapshotChecker();
|
||||
dbfull()->SetSnapshotChecker(snapshot_checker);
|
||||
|
||||
std::string value;
|
||||
ASSERT_OK(Merge("foo", "v1"));
|
||||
ASSERT_EQ(1, db_->GetLatestSequenceNumber());
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
ASSERT_OK(Merge("foo", "v2"));
|
||||
ASSERT_EQ(2, db_->GetLatestSequenceNumber());
|
||||
// v2 is not visible to latest snapshot, which has seq = 2.
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
// Take a snapshot with seq = 2.
|
||||
const Snapshot* snapshot1 = db_->GetSnapshot();
|
||||
ASSERT_EQ(2, snapshot1->GetSequenceNumber());
|
||||
// v2 is not visible to snapshot1, which has seq = 2
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1));
|
||||
|
||||
// Verify flush doesn't alter the result.
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1));
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
|
||||
ASSERT_OK(Merge("foo", "v3"));
|
||||
ASSERT_EQ(3, db_->GetLatestSequenceNumber());
|
||||
ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
ASSERT_OK(Merge("foo", "v4"));
|
||||
ASSERT_EQ(4, db_->GetLatestSequenceNumber());
|
||||
// v4 is not visible to latest snapshot, which has seq = 4.
|
||||
ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
const Snapshot* snapshot2 = db_->GetSnapshot();
|
||||
ASSERT_EQ(4, snapshot2->GetSequenceNumber());
|
||||
// v4 is not visible to snapshot2, which has seq = 4.
|
||||
ASSERT_EQ("v1,v2,v3",
|
||||
GetWithReadCallback(snapshot_checker, "foo", snapshot2));
|
||||
|
||||
// Verify flush doesn't alter the result.
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1));
|
||||
ASSERT_EQ("v1,v2,v3",
|
||||
GetWithReadCallback(snapshot_checker, "foo", snapshot2));
|
||||
ASSERT_EQ("v1,v2,v3", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
|
||||
ASSERT_OK(Merge("foo", "v5"));
|
||||
ASSERT_EQ(5, db_->GetLatestSequenceNumber());
|
||||
// v5 is uncommitted
|
||||
ASSERT_EQ("v1,v2,v3,v4", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
|
||||
// full manual compaction.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// Verify compaction doesn't alter the result.
|
||||
ASSERT_EQ("v1", GetWithReadCallback(snapshot_checker, "foo", snapshot1));
|
||||
ASSERT_EQ("v1,v2,v3",
|
||||
GetWithReadCallback(snapshot_checker, "foo", snapshot2));
|
||||
ASSERT_EQ("v1,v2,v3,v4", GetWithReadCallback(snapshot_checker, "foo"));
|
||||
|
||||
db_->ReleaseSnapshot(snapshot1);
|
||||
db_->ReleaseSnapshot(snapshot2);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+283
-1
@@ -117,6 +117,149 @@ TEST_F(DBOptionsTest, GetLatestCFOptions) {
|
||||
GetMutableCFOptionsMap(dbfull()->GetOptions(handles_[1])));
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetBytesPerSync) {
|
||||
const size_t kValueSize = 1024 * 1024; // 1MB
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.bytes_per_sync = 1024 * 1024;
|
||||
options.use_direct_reads = false;
|
||||
options.write_buffer_size = 400 * kValueSize;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
int counter = 0;
|
||||
int low_bytes_per_sync = 0;
|
||||
int i = 0;
|
||||
const std::string kValue(kValueSize, 'v');
|
||||
ASSERT_EQ(options.bytes_per_sync, dbfull()->GetDBOptions().bytes_per_sync);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WritableFileWriter::RangeSync:0", [&](void* arg) {
|
||||
counter++;
|
||||
});
|
||||
|
||||
WriteOptions write_opts;
|
||||
// should sync approximately 40MB/1MB ~= 40 times.
|
||||
for (i = 0; i < 40; i++) {
|
||||
Put(Key(i), kValue, write_opts);
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
low_bytes_per_sync = counter;
|
||||
ASSERT_GT(low_bytes_per_sync, 35);
|
||||
ASSERT_LT(low_bytes_per_sync, 45);
|
||||
|
||||
counter = 0;
|
||||
// 8388608 = 8 * 1024 * 1024
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"bytes_per_sync", "8388608"}}));
|
||||
ASSERT_EQ(8388608, dbfull()->GetDBOptions().bytes_per_sync);
|
||||
// should sync approximately 40MB*2/8MB ~= 10 times.
|
||||
// data will be 40*2MB because of previous Puts too.
|
||||
for (i = 0; i < 40; i++) {
|
||||
Put(Key(i), kValue, write_opts);
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_GT(counter, 5);
|
||||
ASSERT_LT(counter, 15);
|
||||
|
||||
// Redundant assert. But leaving it here just to get the point across that
|
||||
// low_bytes_per_sync > counter.
|
||||
ASSERT_GT(low_bytes_per_sync, counter);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetWalBytesPerSync) {
|
||||
const size_t kValueSize = 1024 * 1024 * 3;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.wal_bytes_per_sync = 512;
|
||||
options.write_buffer_size = 100 * kValueSize;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(512, dbfull()->GetDBOptions().wal_bytes_per_sync);
|
||||
int counter = 0;
|
||||
int low_bytes_per_sync = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WritableFileWriter::RangeSync:0", [&](void* arg) {
|
||||
counter++;
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
const std::string kValue(kValueSize, 'v');
|
||||
int i = 0;
|
||||
for (; i < 10; i++) {
|
||||
Put(Key(i), kValue);
|
||||
}
|
||||
// Do not flush. If we flush here, SwitchWAL will reuse old WAL file since its
|
||||
// empty and will not get the new wal_bytes_per_sync value.
|
||||
low_bytes_per_sync = counter;
|
||||
//5242880 = 1024 * 1024 * 5
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"wal_bytes_per_sync", "5242880"}}));
|
||||
ASSERT_EQ(5242880, dbfull()->GetDBOptions().wal_bytes_per_sync);
|
||||
counter = 0;
|
||||
i = 0;
|
||||
for (; i < 10; i++) {
|
||||
Put(Key(i), kValue);
|
||||
}
|
||||
ASSERT_GT(counter, 0);
|
||||
ASSERT_GT(low_bytes_per_sync, 0);
|
||||
ASSERT_GT(low_bytes_per_sync, counter);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, WritableFileMaxBufferSize) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.writable_file_max_buffer_size = 1024 * 1024;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_manifest_file_size = 1;
|
||||
options.env = env_;
|
||||
int buffer_size = 1024 * 1024;
|
||||
Reopen(options);
|
||||
ASSERT_EQ(buffer_size,
|
||||
dbfull()->GetDBOptions().writable_file_max_buffer_size);
|
||||
|
||||
std::atomic<int> match_cnt(0);
|
||||
std::atomic<int> unmatch_cnt(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"WritableFileWriter::WritableFileWriter:0", [&](void* arg) {
|
||||
int value = static_cast<int>(reinterpret_cast<uintptr_t>(arg));
|
||||
if (value == buffer_size) {
|
||||
match_cnt++;
|
||||
} else {
|
||||
unmatch_cnt++;
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
int i = 0;
|
||||
for (; i < 3; i++) {
|
||||
ASSERT_OK(Put("foo", ToString(i)));
|
||||
ASSERT_OK(Put("bar", ToString(i)));
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(unmatch_cnt, 0);
|
||||
ASSERT_GE(match_cnt, 11);
|
||||
|
||||
buffer_size = 512 * 1024;
|
||||
match_cnt = 0;
|
||||
unmatch_cnt = 0;
|
||||
ASSERT_OK(
|
||||
dbfull()->SetDBOptions({{"writable_file_max_buffer_size", "524288"}}));
|
||||
ASSERT_EQ(buffer_size,
|
||||
dbfull()->GetDBOptions().writable_file_max_buffer_size);
|
||||
i = 0;
|
||||
for (; i < 3; i++) {
|
||||
ASSERT_OK(Put("foo", ToString(i)));
|
||||
ASSERT_OK(Put("bar", ToString(i)));
|
||||
Flush();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(unmatch_cnt, 0);
|
||||
ASSERT_GE(match_cnt, 11);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetOptionsAndReopen) {
|
||||
Random rnd(1044);
|
||||
auto rand_opts = GetRandomizedMutableCFOptionsMap(&rnd);
|
||||
@@ -375,8 +518,13 @@ static void assert_candidate_files_empty(DBImpl* dbfull, const bool empty) {
|
||||
JobContext job_context(0);
|
||||
dbfull->FindObsoleteFiles(&job_context, false);
|
||||
ASSERT_EQ(empty, job_context.full_scan_candidate_files.empty());
|
||||
job_context.Clean();
|
||||
dbfull->TEST_UnlockMutex();
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
// fulfill the contract of FindObsoleteFiles by calling PurgeObsoleteFiles
|
||||
// afterwards; otherwise the test may hang on shutdown
|
||||
dbfull->PurgeObsoleteFiles(job_context);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, DeleteObsoleteFilesPeriodChange) {
|
||||
@@ -441,6 +589,140 @@ TEST_F(DBOptionsTest, SanitizeDelayedWriteRate) {
|
||||
ASSERT_EQ(31 * 1024 * 1024, dbfull()->GetDBOptions().delayed_write_rate);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, SetFIFOCompactionOptions) {
|
||||
Options options;
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.write_buffer_size = 10 << 10; // 10KB
|
||||
options.arena_block_size = 4096;
|
||||
options.compression = kNoCompression;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
env_->time_elapse_only_sleep_ = false;
|
||||
options.env = env_;
|
||||
|
||||
// Test dynamically changing compaction_options_fifo.ttl
|
||||
env_->addon_time_.store(0);
|
||||
options.compaction_options_fifo.ttl = 1 * 60 * 60; // 1 hour
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// Generate and flush a file about 10KB.
|
||||
for (int j = 0; j < 10; j++) {
|
||||
ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
// Add 61 seconds to the time.
|
||||
env_->addon_time_.fetch_add(61);
|
||||
|
||||
// No files should be compacted as ttl is set to 1 hour.
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 3600);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
// Set ttl to 1 minute. So all files should get deleted.
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compaction_options_fifo", "{ttl=60;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 60);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
|
||||
// Test dynamically changing compaction_options_fifo.max_table_files_size
|
||||
env_->addon_time_.store(0);
|
||||
options.compaction_options_fifo.max_table_files_size = 500 << 10; // 00KB
|
||||
options.compaction_options_fifo.ttl = 0;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// Generate and flush a file about 10KB.
|
||||
for (int j = 0; j < 10; j++) {
|
||||
ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
// No files should be compacted as max_table_files_size is set to 500 KB.
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
500 << 10);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
// Set max_table_files_size to 12 KB. So only 1 file should remain now.
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo", "{max_table_files_size=12288;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
12 << 10);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1);
|
||||
|
||||
// Test dynamically changing compaction_options_fifo.allow_compaction
|
||||
options.compaction_options_fifo.max_table_files_size = 500 << 10; // 500KB
|
||||
options.compaction_options_fifo.ttl = 0;
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
options.level0_file_num_compaction_trigger = 6;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
// Generate and flush a file about 10KB.
|
||||
for (int j = 0; j < 10; j++) {
|
||||
ASSERT_OK(Put(ToString(i * 20 + j), RandomString(&rnd, 980)));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
// No files should be compacted as max_table_files_size is set to 500 KB and
|
||||
// allow_compaction is false
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 10);
|
||||
|
||||
// Set allow_compaction to true. So number of files should be between 1 and 5.
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo", "{allow_compaction=true;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_GE(NumTableFilesAtLevel(0), 1);
|
||||
ASSERT_LE(NumTableFilesAtLevel(0), 5);
|
||||
}
|
||||
|
||||
TEST_F(DBOptionsTest, CompactionReadaheadSizeChange) {
|
||||
SpecialEnv env(env_);
|
||||
Options options;
|
||||
options.env = &env;
|
||||
|
||||
options.compaction_readahead_size = 0;
|
||||
options.new_table_reader_for_compaction_inputs = true;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
const std::string kValue(1024, 'v');
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_EQ(0, dbfull()->GetDBOptions().compaction_readahead_size);
|
||||
ASSERT_OK(dbfull()->SetDBOptions({{"compaction_readahead_size", "256"}}));
|
||||
ASSERT_EQ(256, dbfull()->GetDBOptions().compaction_readahead_size);
|
||||
for (int i = 0; i < 1024; i++) {
|
||||
Put(Key(i), kValue);
|
||||
}
|
||||
Flush();
|
||||
for (int i = 0; i < 1024 * 2; i++) {
|
||||
Put(Key(i), kValue);
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(256, env_->compaction_readahead_size_);
|
||||
Close();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
+137
-10
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/perf_level.h"
|
||||
@@ -68,27 +69,27 @@ TEST_F(DBPropertiesTest, Empty) {
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("1", num);
|
||||
ASSERT_EQ("0", num);
|
||||
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("2", num);
|
||||
ASSERT_EQ("0", num);
|
||||
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("3", num);
|
||||
ASSERT_EQ("0", num);
|
||||
|
||||
ASSERT_OK(db_->EnableFileDeletions(false));
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("2", num);
|
||||
ASSERT_EQ("0", num);
|
||||
|
||||
ASSERT_OK(db_->EnableFileDeletions());
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetProperty("rocksdb.is-file-deletions-enabled", &num));
|
||||
ASSERT_EQ("0", num);
|
||||
ASSERT_EQ("1", num);
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
@@ -985,8 +986,9 @@ class CountingUserTblPropCollector : public TablePropertiesCollector {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status AddUserKey(const Slice& user_key, const Slice& value, EntryType type,
|
||||
SequenceNumber seq, uint64_t file_size) override {
|
||||
Status AddUserKey(const Slice& /*user_key*/, const Slice& /*value*/,
|
||||
EntryType /*type*/, SequenceNumber /*seq*/,
|
||||
uint64_t /*file_size*/) override {
|
||||
++count_;
|
||||
return Status::OK();
|
||||
}
|
||||
@@ -1027,8 +1029,9 @@ class CountingDeleteTabPropCollector : public TablePropertiesCollector {
|
||||
public:
|
||||
const char* Name() const override { return "CountingDeleteTabPropCollector"; }
|
||||
|
||||
Status AddUserKey(const Slice& user_key, const Slice& value, EntryType type,
|
||||
SequenceNumber seq, uint64_t file_size) override {
|
||||
Status AddUserKey(const Slice& /*user_key*/, const Slice& /*value*/,
|
||||
EntryType type, SequenceNumber /*seq*/,
|
||||
uint64_t /*file_size*/) override {
|
||||
if (type == kEntryDelete) {
|
||||
num_deletes_++;
|
||||
}
|
||||
@@ -1055,7 +1058,7 @@ class CountingDeleteTabPropCollectorFactory
|
||||
: public TablePropertiesCollectorFactory {
|
||||
public:
|
||||
virtual TablePropertiesCollector* CreateTablePropertiesCollector(
|
||||
TablePropertiesCollectorFactory::Context context) override {
|
||||
TablePropertiesCollectorFactory::Context /*context*/) override {
|
||||
return new CountingDeleteTabPropCollector();
|
||||
}
|
||||
const char* Name() const override {
|
||||
@@ -1309,6 +1312,130 @@ TEST_F(DBPropertiesTest, EstimateNumKeysUnderflow) {
|
||||
ASSERT_EQ(0, num_keys);
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, EstimateOldestKeyTime) {
|
||||
std::unique_ptr<MockTimeEnv> mock_env(new MockTimeEnv(Env::Default()));
|
||||
uint64_t oldest_key_time = 0;
|
||||
Options options;
|
||||
options.env = mock_env.get();
|
||||
|
||||
// "rocksdb.estimate-oldest-key-time" only available to fifo compaction.
|
||||
mock_env->set_current_time(100);
|
||||
for (auto compaction : {kCompactionStyleLevel, kCompactionStyleUniversal,
|
||||
kCompactionStyleNone}) {
|
||||
options.compaction_style = compaction;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_FALSE(dbfull()->GetIntProperty(
|
||||
DB::Properties::kEstimateOldestKeyTime, &oldest_key_time));
|
||||
}
|
||||
|
||||
options.compaction_style = kCompactionStyleFIFO;
|
||||
options.compaction_options_fifo.ttl = 300;
|
||||
options.compaction_options_fifo.allow_compaction = false;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
mock_env->set_current_time(100);
|
||||
ASSERT_OK(Put("k1", "v1"));
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
|
||||
&oldest_key_time));
|
||||
ASSERT_EQ(100, oldest_key_time);
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
|
||||
&oldest_key_time));
|
||||
ASSERT_EQ(100, oldest_key_time);
|
||||
|
||||
mock_env->set_current_time(200);
|
||||
ASSERT_OK(Put("k2", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
|
||||
&oldest_key_time));
|
||||
ASSERT_EQ(100, oldest_key_time);
|
||||
|
||||
mock_env->set_current_time(300);
|
||||
ASSERT_OK(Put("k3", "v3"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("3", FilesPerLevel());
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
|
||||
&oldest_key_time));
|
||||
ASSERT_EQ(100, oldest_key_time);
|
||||
|
||||
mock_env->set_current_time(450);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("2", FilesPerLevel());
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
|
||||
&oldest_key_time));
|
||||
ASSERT_EQ(200, oldest_key_time);
|
||||
|
||||
mock_env->set_current_time(550);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
|
||||
&oldest_key_time));
|
||||
ASSERT_EQ(300, oldest_key_time);
|
||||
|
||||
mock_env->set_current_time(650);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("", FilesPerLevel());
|
||||
ASSERT_FALSE(dbfull()->GetIntProperty(DB::Properties::kEstimateOldestKeyTime,
|
||||
&oldest_key_time));
|
||||
|
||||
// Close before mock_env destructs.
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, SstFilesSize) {
|
||||
struct TestListener : public EventListener {
|
||||
void OnCompactionCompleted(DB* db,
|
||||
const CompactionJobInfo& /*info*/) override {
|
||||
assert(callback_triggered == false);
|
||||
assert(size_before_compaction > 0);
|
||||
callback_triggered = true;
|
||||
uint64_t total_sst_size = 0;
|
||||
uint64_t live_sst_size = 0;
|
||||
bool ok = db->GetIntProperty(DB::Properties::kTotalSstFilesSize,
|
||||
&total_sst_size);
|
||||
ASSERT_TRUE(ok);
|
||||
// total_sst_size include files before and after compaction.
|
||||
ASSERT_GT(total_sst_size, size_before_compaction);
|
||||
ok =
|
||||
db->GetIntProperty(DB::Properties::kLiveSstFilesSize, &live_sst_size);
|
||||
ASSERT_TRUE(ok);
|
||||
// live_sst_size only include files after compaction.
|
||||
ASSERT_GT(live_sst_size, 0);
|
||||
ASSERT_LT(live_sst_size, size_before_compaction);
|
||||
}
|
||||
|
||||
uint64_t size_before_compaction = 0;
|
||||
bool callback_triggered = false;
|
||||
};
|
||||
std::shared_ptr<TestListener> listener = std::make_shared<TestListener>();
|
||||
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.listeners.push_back(listener);
|
||||
Reopen(options);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put("key" + ToString(i), std::string(1000, 'v')));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_OK(Delete("key" + ToString(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
uint64_t sst_size;
|
||||
bool ok = db_->GetIntProperty(DB::Properties::kTotalSstFilesSize, &sst_size);
|
||||
ASSERT_TRUE(ok);
|
||||
ASSERT_GT(sst_size, 0);
|
||||
listener->size_before_compaction = sst_size;
|
||||
// Compact to clean all keys and trigger listener.
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_TRUE(listener->callback_triggered);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
+104
-11
@@ -327,15 +327,14 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
|
||||
std::string trash_dir = test::TmpDir(env_) + "/trash";
|
||||
int64_t rate_bytes_per_sec = 1024 * 10; // 10 Kbs / Sec
|
||||
Status s;
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, trash_dir, 0, false, &s));
|
||||
NewSstFileManager(env_, nullptr, "", 0, false, &s, 0));
|
||||
ASSERT_OK(s);
|
||||
options.sst_file_manager->SetDeleteRateBytesPerSecond(rate_bytes_per_sec);
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
sfm->delete_scheduler()->TEST_SetMaxTrashDBRatio(1.1);
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// Create 4 files in L0
|
||||
@@ -377,6 +376,30 @@ TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, OpenDBWithExistingTrash) {
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", 1024 * 1024 /* 1 MB/sec */));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
Destroy(last_options_);
|
||||
|
||||
// Add some trash files to the db directory so the DB can clean them up
|
||||
env_->CreateDirIfMissing(dbname_);
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_OK(WriteStringToFile(env_, "abc", dbname_ + "/" + "003.sst.trash"));
|
||||
|
||||
// Reopen the DB and verify that it deletes existing trash files
|
||||
ASSERT_OK(TryReopen(options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "001.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "002.sst.trash"));
|
||||
ASSERT_NOK(env_->FileExists(dbname_ + "/" + "003.sst.trash"));
|
||||
}
|
||||
|
||||
|
||||
// Create a DB with 2 db_paths, and generate multiple files in the 2
|
||||
// db_paths using CompactRangeOptions, make sure that files that were
|
||||
// deleted from first db_path were deleted using DeleteScheduler and
|
||||
@@ -386,6 +409,11 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* arg) { bg_delete_file++; });
|
||||
// The deletion scheduler sometimes skips marking file as trash according to
|
||||
// a heuristic. In that case the deletion will go through the below SyncPoint.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteFile",
|
||||
[&](void* arg) { bg_delete_file++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
@@ -394,14 +422,14 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 1024 * 100);
|
||||
options.env = env_;
|
||||
|
||||
std::string trash_dir = test::TmpDir(env_) + "/trash";
|
||||
int64_t rate_bytes_per_sec = 1024 * 1024; // 1 Mb / Sec
|
||||
Status s;
|
||||
options.sst_file_manager.reset(NewSstFileManager(
|
||||
env_, nullptr, trash_dir, rate_bytes_per_sec, false, &s));
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, "", rate_bytes_per_sec, false, &s,
|
||||
/* max_trash_db_ratio= */ 1.1));
|
||||
|
||||
ASSERT_OK(s);
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
sfm->delete_scheduler()->TEST_SetMaxTrashDBRatio(1.1);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -438,13 +466,15 @@ TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(bg_delete_file, 8);
|
||||
|
||||
// Compaction will delete both files and regenerate a file in L1 in second
|
||||
// db path. The deleted files should still be cleaned up via delete scheduler.
|
||||
compact_options.bottommost_level_compaction =
|
||||
BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(compact_options, nullptr, nullptr));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(bg_delete_file, 8);
|
||||
ASSERT_EQ(bg_delete_file, 10);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
@@ -460,9 +490,8 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
std::string trash_dir = test::TmpDir(env_) + "/trash";
|
||||
options.sst_file_manager.reset(
|
||||
NewSstFileManager(env_, nullptr, trash_dir, 0, false, &s));
|
||||
NewSstFileManager(env_, nullptr, "", 0, false, &s, 0));
|
||||
ASSERT_OK(s);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
@@ -480,7 +509,7 @@ TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
sfm->SetDeleteRateBytesPerSecond(1024 * 1024);
|
||||
sfm->delete_scheduler()->TEST_SetMaxTrashDBRatio(1.1);
|
||||
sfm->delete_scheduler()->SetMaxTrashDBRatio(1.1);
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
sfm->WaitForEmptyTrash();
|
||||
// We have deleted the 4 sst files in the delete_scheduler
|
||||
@@ -516,6 +545,52 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) {
|
||||
ASSERT_NOK(Flush());
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, CancellingCompactionsWorks) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.statistics = CreateDBStatistics();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int completed_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction():CancelledCompaction", [&](void* arg) {
|
||||
sfm->SetMaxAllowedSpaceUsage(0);
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun",
|
||||
[&](void* arg) { completed_compactions++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
// Generate a file containing 10 keys.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
uint64_t total_file_size = 0;
|
||||
auto files_in_db = GetAllSSTFiles(&total_file_size);
|
||||
// Set the maximum allowed space usage to the current total size
|
||||
sfm->SetMaxAllowedSpaceUsage(2 * total_file_size + 1);
|
||||
|
||||
// Generate another file to trigger compaction.
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForCompact(true);
|
||||
|
||||
ASSERT_GT(completed_compactions, 0);
|
||||
ASSERT_EQ(sfm->GetCompactionsReservedSize(), 0);
|
||||
// Make sure the stat is bumped
|
||||
ASSERT_GT(dbfull()->immutable_db_options().statistics.get()->getTickerCount(COMPACTION_CANCELLED), 0);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) {
|
||||
// This test will set a maximum allowed space for the DB, then it will
|
||||
// keep filling the DB until the limit is reached and bg_error_ is set.
|
||||
@@ -544,6 +619,12 @@ TEST_F(DBSSTTest, DBWithMaxSpaceAllowedRandomized) {
|
||||
estimate_multiplier++; // used in the main loop assert
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction():CancelledCompaction", [&](void* arg) {
|
||||
bool* enough_room = static_cast<bool*>(arg);
|
||||
*enough_room = true;
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::FinishCompactionOutputFile:MaxAllowedSpaceReached",
|
||||
[&](void* arg) {
|
||||
@@ -650,9 +731,18 @@ TEST_F(DBSSTTest, OpenDBWithInfiniteMaxOpenFiles) {
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, GetTotalSstFilesSize) {
|
||||
// We don't propagate oldest-key-time table property on compaction and
|
||||
// just write 0 as default value. This affect the exact table size, since
|
||||
// we encode table properties as varint64. Force time to be 0 to work around
|
||||
// it. Should remove the workaround after we propagate the property on
|
||||
// compaction.
|
||||
std::unique_ptr<MockTimeEnv> mock_env(new MockTimeEnv(Env::Default()));
|
||||
mock_env->set_current_time(0);
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
options.env = mock_env.get();
|
||||
DestroyAndReopen(options);
|
||||
// Generate 5 files in L0
|
||||
for (int i = 0; i < 5; i++) {
|
||||
@@ -739,6 +829,9 @@ TEST_F(DBSSTTest, GetTotalSstFilesSize) {
|
||||
// Live SST files = 0
|
||||
// Total SST files = 0
|
||||
ASSERT_EQ(total_sst_files_size, 0);
|
||||
|
||||
// Close db before mock_env destruct.
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, GetTotalSstFilesSizeVersionsFilesShared) {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/utilities/table_properties_collectors.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
@@ -250,6 +251,37 @@ TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, DeletionTriggeredCompactionMarking) {
|
||||
const int kNumKeys = 1000;
|
||||
const int kWindowSize = 100;
|
||||
const int kNumDelsTrigger = 90;
|
||||
|
||||
Options opts = CurrentOptions();
|
||||
opts.table_properties_collector_factories.emplace_back(
|
||||
NewCompactOnDeletionCollectorFactory(kWindowSize, kNumDelsTrigger));
|
||||
Reopen(opts);
|
||||
|
||||
// add an L1 file to prevent tombstones from dropping due to obsolescence
|
||||
// during flush
|
||||
Put(Key(0), "val");
|
||||
Flush();
|
||||
MoveFilesToLevel(1);
|
||||
|
||||
for (int i = 0; i < kNumKeys; ++i) {
|
||||
if (i >= kNumKeys - kWindowSize &&
|
||||
i < kNumKeys - kWindowSize + kNumDelsTrigger) {
|
||||
Delete(Key(i));
|
||||
} else {
|
||||
Put(Key(i), "val");
|
||||
}
|
||||
}
|
||||
Flush();
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_GT(NumTableFilesAtLevel(1), 0);
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -214,9 +214,9 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
||||
}
|
||||
ASSERT_TRUE(file_iters_renewed_null);
|
||||
ASSERT_TRUE(file_iters_renewed_copy);
|
||||
iter = 0;
|
||||
itern = 0;
|
||||
iterh = 0;
|
||||
iter = nullptr;
|
||||
itern = nullptr;
|
||||
iterh = nullptr;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.block_cache_compressed = nullptr;
|
||||
@@ -229,7 +229,7 @@ TEST_F(DBTestTailingIterator, TailingIteratorTrimSeekToNext) {
|
||||
Slice target1(buf5, 20);
|
||||
iteri->Seek(target1);
|
||||
ASSERT_TRUE(iteri->status().IsIncomplete());
|
||||
iteri = 0;
|
||||
iteri = nullptr;
|
||||
|
||||
read_options.read_tier = kReadAllTier;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory());
|
||||
|
||||
+434
-109
@@ -60,7 +60,6 @@
|
||||
#include "util/compression.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/filename.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/rate_limiter.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -223,6 +222,10 @@ TEST_F(DBTest, SkipDelay) {
|
||||
|
||||
for (bool sync : {true, false}) {
|
||||
for (bool disableWAL : {true, false}) {
|
||||
if (sync && disableWAL) {
|
||||
// sync and disableWAL is incompatible.
|
||||
continue;
|
||||
}
|
||||
// Use a small number to ensure a large delay that is still effective
|
||||
// when we do Put
|
||||
// TODO(myabandeh): this is time dependent and could potentially make
|
||||
@@ -477,6 +480,36 @@ TEST_F(DBTest, SingleDeletePutFlush) {
|
||||
kSkipUniversalCompaction | kSkipMergePut));
|
||||
}
|
||||
|
||||
// Disable because not all platform can run it.
|
||||
// It requires more than 9GB memory to run it, With single allocation
|
||||
// of more than 3GB.
|
||||
TEST_F(DBTest, DISABLED_SanitizeVeryVeryLargeValue) {
|
||||
const size_t kValueSize = 4 * size_t{1024 * 1024 * 1024}; // 4GB value
|
||||
std::string raw(kValueSize, 'v');
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.merge_operator = MergeOperators::CreatePutOperator();
|
||||
options.write_buffer_size = 100000; // Small write buffer
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("boo", "v1"));
|
||||
ASSERT_TRUE(Put("foo", raw).IsInvalidArgument());
|
||||
ASSERT_TRUE(Merge("foo", raw).IsInvalidArgument());
|
||||
|
||||
WriteBatch wb;
|
||||
ASSERT_TRUE(wb.Put("foo", raw).IsInvalidArgument());
|
||||
ASSERT_TRUE(wb.Merge("foo", raw).IsInvalidArgument());
|
||||
|
||||
Slice value_slice = raw;
|
||||
Slice key_slice = "foo";
|
||||
SliceParts sp_key(&key_slice, 1);
|
||||
SliceParts sp_value(&value_slice, 1);
|
||||
|
||||
ASSERT_TRUE(wb.Put(sp_key, sp_value).IsInvalidArgument());
|
||||
ASSERT_TRUE(wb.Merge(sp_key, sp_value).IsInvalidArgument());
|
||||
}
|
||||
|
||||
// Disable because not all platform can run it.
|
||||
// It requires more than 9GB memory to run it, With single allocation
|
||||
// of more than 3GB.
|
||||
@@ -500,7 +533,9 @@ TEST_F(DBTest, DISABLED_VeryLargeValue) {
|
||||
ASSERT_OK(Put(key2, raw));
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
std::string value;
|
||||
Status s = db_->Get(ReadOptions(), key1, &value);
|
||||
@@ -715,9 +750,9 @@ TEST_F(DBTest, FlushSchedule) {
|
||||
namespace {
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -747,9 +782,9 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
db_test->env_->addon_time_.fetch_add(1000);
|
||||
return true;
|
||||
}
|
||||
@@ -764,7 +799,7 @@ class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
|
||||
}
|
||||
|
||||
@@ -2188,6 +2223,8 @@ class ModelDB : public DB {
|
||||
batch.Put(cf, k, v);
|
||||
return Write(o, &batch);
|
||||
}
|
||||
using DB::Close;
|
||||
virtual Status Close() override { return Status::OK(); }
|
||||
using DB::Delete;
|
||||
virtual Status Delete(const WriteOptions& o, ColumnFamilyHandle* cf,
|
||||
const Slice& key) override {
|
||||
@@ -2210,17 +2247,17 @@ class ModelDB : public DB {
|
||||
return Write(o, &batch);
|
||||
}
|
||||
using DB::Get;
|
||||
virtual Status Get(const ReadOptions& options, ColumnFamilyHandle* cf,
|
||||
const Slice& key, PinnableSlice* value) override {
|
||||
virtual Status Get(const ReadOptions& /*options*/, ColumnFamilyHandle* /*cf*/,
|
||||
const Slice& key, PinnableSlice* /*value*/) override {
|
||||
return Status::NotSupported(key);
|
||||
}
|
||||
|
||||
using DB::MultiGet;
|
||||
virtual std::vector<Status> MultiGet(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
const ReadOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_family*/,
|
||||
const std::vector<Slice>& keys,
|
||||
std::vector<std::string>* values) override {
|
||||
std::vector<std::string>* /*values*/) override {
|
||||
std::vector<Status> s(keys.size(),
|
||||
Status::NotSupported("Not implemented."));
|
||||
return s;
|
||||
@@ -2229,9 +2266,9 @@ class ModelDB : public DB {
|
||||
#ifndef ROCKSDB_LITE
|
||||
using DB::IngestExternalFile;
|
||||
virtual Status IngestExternalFile(
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& external_files,
|
||||
const IngestExternalFileOptions& options) override {
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*external_files*/,
|
||||
const IngestExternalFileOptions& /*options*/) override {
|
||||
return Status::NotSupported("Not implemented.");
|
||||
}
|
||||
|
||||
@@ -2241,22 +2278,22 @@ class ModelDB : public DB {
|
||||
|
||||
using DB::GetPropertiesOfAllTables;
|
||||
virtual Status GetPropertiesOfAllTables(
|
||||
ColumnFamilyHandle* column_family,
|
||||
TablePropertiesCollection* props) override {
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
TablePropertiesCollection* /*props*/) override {
|
||||
return Status();
|
||||
}
|
||||
|
||||
virtual Status GetPropertiesOfTablesInRange(
|
||||
ColumnFamilyHandle* column_family, const Range* range, std::size_t n,
|
||||
TablePropertiesCollection* props) override {
|
||||
ColumnFamilyHandle* /*column_family*/, const Range* /*range*/,
|
||||
std::size_t /*n*/, TablePropertiesCollection* /*props*/) override {
|
||||
return Status();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
using DB::KeyMayExist;
|
||||
virtual bool KeyMayExist(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family, const Slice& key,
|
||||
std::string* value,
|
||||
virtual bool KeyMayExist(const ReadOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*key*/, std::string* /*value*/,
|
||||
bool* value_found = nullptr) override {
|
||||
if (value_found != nullptr) {
|
||||
*value_found = false;
|
||||
@@ -2264,8 +2301,9 @@ class ModelDB : public DB {
|
||||
return true; // Not Supported directly
|
||||
}
|
||||
using DB::NewIterator;
|
||||
virtual Iterator* NewIterator(const ReadOptions& options,
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
virtual Iterator* NewIterator(
|
||||
const ReadOptions& options,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
if (options.snapshot == nullptr) {
|
||||
KVMap* saved = new KVMap;
|
||||
*saved = map_;
|
||||
@@ -2277,9 +2315,9 @@ class ModelDB : public DB {
|
||||
}
|
||||
}
|
||||
virtual Status NewIterators(
|
||||
const ReadOptions& options,
|
||||
const std::vector<ColumnFamilyHandle*>& column_family,
|
||||
std::vector<Iterator*>* iterators) override {
|
||||
const ReadOptions& /*options*/,
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_family*/,
|
||||
std::vector<Iterator*>* /*iterators*/) override {
|
||||
return Status::NotSupported("Not supported yet");
|
||||
}
|
||||
virtual const Snapshot* GetSnapshot() override {
|
||||
@@ -2292,7 +2330,7 @@ class ModelDB : public DB {
|
||||
delete reinterpret_cast<const ModelSnapshot*>(snapshot);
|
||||
}
|
||||
|
||||
virtual Status Write(const WriteOptions& options,
|
||||
virtual Status Write(const WriteOptions& /*options*/,
|
||||
WriteBatch* batch) override {
|
||||
class Handler : public WriteBatch::Handler {
|
||||
public:
|
||||
@@ -2300,7 +2338,8 @@ class ModelDB : public DB {
|
||||
virtual void Put(const Slice& key, const Slice& value) override {
|
||||
(*map_)[key.ToString()] = value.ToString();
|
||||
}
|
||||
virtual void Merge(const Slice& key, const Slice& value) override {
|
||||
virtual void Merge(const Slice& /*key*/,
|
||||
const Slice& /*value*/) override {
|
||||
// ignore merge for now
|
||||
// (*map_)[key.ToString()] = value.ToString();
|
||||
}
|
||||
@@ -2314,62 +2353,67 @@ class ModelDB : public DB {
|
||||
}
|
||||
|
||||
using DB::GetProperty;
|
||||
virtual bool GetProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, std::string* value) override {
|
||||
virtual bool GetProperty(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*property*/,
|
||||
std::string* /*value*/) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetIntProperty;
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property, uint64_t* value) override {
|
||||
virtual bool GetIntProperty(ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice& /*property*/,
|
||||
uint64_t* /*value*/) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetMapProperty;
|
||||
virtual bool GetMapProperty(ColumnFamilyHandle* column_family,
|
||||
const Slice& property,
|
||||
std::map<std::string, double>* value) override {
|
||||
virtual bool GetMapProperty(
|
||||
ColumnFamilyHandle* /*column_family*/, const Slice& /*property*/,
|
||||
std::map<std::string, std::string>* /*value*/) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetAggregatedIntProperty;
|
||||
virtual bool GetAggregatedIntProperty(const Slice& property,
|
||||
uint64_t* value) override {
|
||||
virtual bool GetAggregatedIntProperty(const Slice& /*property*/,
|
||||
uint64_t* /*value*/) override {
|
||||
return false;
|
||||
}
|
||||
using DB::GetApproximateSizes;
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* column_family,
|
||||
const Range* range, int n, uint64_t* sizes,
|
||||
uint8_t include_flags
|
||||
virtual void GetApproximateSizes(ColumnFamilyHandle* /*column_family*/,
|
||||
const Range* /*range*/, int n,
|
||||
uint64_t* sizes,
|
||||
uint8_t /*include_flags*/
|
||||
= INCLUDE_FILES) override {
|
||||
for (int i = 0; i < n; i++) {
|
||||
sizes[i] = 0;
|
||||
}
|
||||
}
|
||||
using DB::GetApproximateMemTableStats;
|
||||
virtual void GetApproximateMemTableStats(ColumnFamilyHandle* column_family,
|
||||
const Range& range,
|
||||
uint64_t* const count,
|
||||
uint64_t* const size) override {
|
||||
virtual void GetApproximateMemTableStats(
|
||||
ColumnFamilyHandle* /*column_family*/, const Range& /*range*/,
|
||||
uint64_t* const count, uint64_t* const size) override {
|
||||
*count = 0;
|
||||
*size = 0;
|
||||
}
|
||||
using DB::CompactRange;
|
||||
virtual Status CompactRange(const CompactRangeOptions& options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const Slice* start, const Slice* end) override {
|
||||
virtual Status CompactRange(const CompactRangeOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const Slice* /*start*/,
|
||||
const Slice* /*end*/) override {
|
||||
return Status::NotSupported("Not supported operation.");
|
||||
}
|
||||
|
||||
virtual Status SetDBOptions(
|
||||
const std::unordered_map<std::string, std::string>& new_options)
|
||||
const std::unordered_map<std::string, std::string>& /*new_options*/)
|
||||
override {
|
||||
return Status::NotSupported("Not supported operation.");
|
||||
}
|
||||
|
||||
using DB::CompactFiles;
|
||||
virtual Status CompactFiles(const CompactionOptions& compact_options,
|
||||
ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
const int output_level,
|
||||
const int output_path_id = -1) override {
|
||||
virtual Status CompactFiles(
|
||||
const CompactionOptions& /*compact_options*/,
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
const std::vector<std::string>& /*input_file_names*/,
|
||||
const int /*output_level*/, const int /*output_path_id*/ = -1,
|
||||
std::vector<std::string>* const /*output_file_names*/ = nullptr
|
||||
) override {
|
||||
return Status::NotSupported("Not supported operation.");
|
||||
}
|
||||
|
||||
@@ -2382,24 +2426,25 @@ class ModelDB : public DB {
|
||||
}
|
||||
|
||||
Status EnableAutoCompaction(
|
||||
const std::vector<ColumnFamilyHandle*>& column_family_handles) override {
|
||||
const std::vector<ColumnFamilyHandle*>& /*column_family_handles*/)
|
||||
override {
|
||||
return Status::NotSupported("Not supported operation.");
|
||||
}
|
||||
|
||||
using DB::NumberLevels;
|
||||
virtual int NumberLevels(ColumnFamilyHandle* column_family) override {
|
||||
virtual int NumberLevels(ColumnFamilyHandle* /*column_family*/) override {
|
||||
return 1;
|
||||
}
|
||||
|
||||
using DB::MaxMemCompactionLevel;
|
||||
virtual int MaxMemCompactionLevel(
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
return 1;
|
||||
}
|
||||
|
||||
using DB::Level0StopWriteTrigger;
|
||||
virtual int Level0StopWriteTrigger(
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -2408,7 +2453,8 @@ class ModelDB : public DB {
|
||||
virtual Env* GetEnv() const override { return nullptr; }
|
||||
|
||||
using DB::GetOptions;
|
||||
virtual Options GetOptions(ColumnFamilyHandle* column_family) const override {
|
||||
virtual Options GetOptions(
|
||||
ColumnFamilyHandle* /*column_family*/) const override {
|
||||
return options_;
|
||||
}
|
||||
|
||||
@@ -2416,8 +2462,8 @@ class ModelDB : public DB {
|
||||
virtual DBOptions GetDBOptions() const override { return options_; }
|
||||
|
||||
using DB::Flush;
|
||||
virtual Status Flush(const rocksdb::FlushOptions& options,
|
||||
ColumnFamilyHandle* column_family) override {
|
||||
virtual Status Flush(const rocksdb::FlushOptions& /*options*/,
|
||||
ColumnFamilyHandle* /*column_family*/) override {
|
||||
Status ret;
|
||||
return ret;
|
||||
}
|
||||
@@ -2427,38 +2473,45 @@ class ModelDB : public DB {
|
||||
#ifndef ROCKSDB_LITE
|
||||
virtual Status DisableFileDeletions() override { return Status::OK(); }
|
||||
|
||||
virtual Status EnableFileDeletions(bool force) override {
|
||||
virtual Status EnableFileDeletions(bool /*force*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&, uint64_t* size,
|
||||
bool flush_memtable = true) override {
|
||||
virtual Status GetLiveFiles(std::vector<std::string>&, uint64_t* /*size*/,
|
||||
bool /*flush_memtable*/ = true) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& files) override {
|
||||
virtual Status GetSortedWalFiles(VectorLogPtr& /*files*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status DeleteFile(std::string name) override { return Status::OK(); }
|
||||
virtual Status DeleteFile(std::string /*name*/) override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual Status GetUpdatesSince(
|
||||
rocksdb::SequenceNumber, unique_ptr<rocksdb::TransactionLogIterator>*,
|
||||
const TransactionLogIterator::ReadOptions& read_options =
|
||||
const TransactionLogIterator::ReadOptions& /*read_options*/ =
|
||||
TransactionLogIterator::ReadOptions()) override {
|
||||
return Status::NotSupported("Not supported in Model DB");
|
||||
}
|
||||
|
||||
virtual void GetColumnFamilyMetaData(
|
||||
ColumnFamilyHandle* column_family,
|
||||
ColumnFamilyMetaData* metadata) override {}
|
||||
ColumnFamilyHandle* /*column_family*/,
|
||||
ColumnFamilyMetaData* /*metadata*/) override {}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
virtual Status GetDbIdentity(std::string& identity) const override {
|
||||
virtual Status GetDbIdentity(std::string& /*identity*/) const override {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual SequenceNumber GetLatestSequenceNumber() const override { return 0; }
|
||||
|
||||
virtual bool SetPreserveDeletesSequenceNumber(
|
||||
SequenceNumber /*seqnum*/) override {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual ColumnFamilyHandle* DefaultColumnFamily() const override {
|
||||
return nullptr;
|
||||
}
|
||||
@@ -3347,11 +3400,23 @@ TEST_F(DBTest, DynamicMemtableOptions) {
|
||||
{"write_buffer_size", "131072"},
|
||||
}));
|
||||
|
||||
// The existing memtable is still 64KB in size, after it becomes immutable,
|
||||
// the next memtable will be 128KB in size. Write 256KB total, we should
|
||||
// have a 64KB L0 file, a 128KB L0 file, and a memtable with 64KB data
|
||||
gen_l0_kb(256);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 2); // (A)
|
||||
// The existing memtable inflated 64KB->128KB when we invoked SetOptions().
|
||||
// Write 192KB, we should have a 128KB L0 file and a memtable with 64KB data.
|
||||
gen_l0_kb(192);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 1); // (A)
|
||||
ASSERT_LT(SizeAtLevel(0), k128KB + 2 * k5KB);
|
||||
ASSERT_GT(SizeAtLevel(0), k128KB - 4 * k5KB);
|
||||
|
||||
// Decrease buffer size below current usage
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"write_buffer_size", "65536"},
|
||||
}));
|
||||
// The existing memtable became eligible for flush when we reduced its
|
||||
// capacity to 64KB. Two keys need to be added to trigger flush: first causes
|
||||
// memtable to be marked full, second schedules the flush. Then we should have
|
||||
// a 128KB L0 file, a 64KB L0 file, and a memtable with just one key.
|
||||
gen_l0_kb(2);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 2);
|
||||
ASSERT_LT(SizeAtLevel(0), k128KB + k64KB + 2 * k5KB);
|
||||
ASSERT_GT(SizeAtLevel(0), k128KB + k64KB - 4 * k5KB);
|
||||
|
||||
@@ -3469,12 +3534,16 @@ TEST_F(DBTest, GetThreadStatus) {
|
||||
const int kTestCount = 3;
|
||||
const unsigned int kHighPriCounts[kTestCount] = {3, 2, 5};
|
||||
const unsigned int kLowPriCounts[kTestCount] = {10, 15, 3};
|
||||
const unsigned int kBottomPriCounts[kTestCount] = {2, 1, 4};
|
||||
for (int test = 0; test < kTestCount; ++test) {
|
||||
// Change the number of threads in high / low priority pool.
|
||||
env_->SetBackgroundThreads(kHighPriCounts[test], Env::HIGH);
|
||||
env_->SetBackgroundThreads(kLowPriCounts[test], Env::LOW);
|
||||
env_->SetBackgroundThreads(kBottomPriCounts[test], Env::BOTTOM);
|
||||
// Wait to ensure the all threads has been registered
|
||||
unsigned int thread_type_counts[ThreadStatus::NUM_THREAD_TYPES];
|
||||
// TODO(ajkr): it'd be better if SetBackgroundThreads returned only after
|
||||
// all threads have been registered.
|
||||
// Try up to 60 seconds.
|
||||
for (int num_try = 0; num_try < 60000; num_try++) {
|
||||
env_->SleepForMicroseconds(1000);
|
||||
@@ -3489,20 +3558,21 @@ TEST_F(DBTest, GetThreadStatus) {
|
||||
if (thread_type_counts[ThreadStatus::HIGH_PRIORITY] ==
|
||||
kHighPriCounts[test] &&
|
||||
thread_type_counts[ThreadStatus::LOW_PRIORITY] ==
|
||||
kLowPriCounts[test]) {
|
||||
kLowPriCounts[test] &&
|
||||
thread_type_counts[ThreadStatus::BOTTOM_PRIORITY] ==
|
||||
kBottomPriCounts[test]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Verify the total number of threades
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::HIGH_PRIORITY] +
|
||||
thread_type_counts[ThreadStatus::LOW_PRIORITY],
|
||||
kHighPriCounts[test] + kLowPriCounts[test]);
|
||||
// Verify the number of high-priority threads
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::HIGH_PRIORITY],
|
||||
kHighPriCounts[test]);
|
||||
// Verify the number of low-priority threads
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::LOW_PRIORITY],
|
||||
kLowPriCounts[test]);
|
||||
// Verify the number of bottom-priority threads
|
||||
ASSERT_EQ(thread_type_counts[ThreadStatus::BOTTOM_PRIORITY],
|
||||
kBottomPriCounts[test]);
|
||||
}
|
||||
if (i == 0) {
|
||||
// repeat the test with multiple column families
|
||||
@@ -4290,6 +4360,140 @@ TEST_F(DBTest, DynamicCompactionOptions) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_LT(NumTableFilesAtLevel(0), 4);
|
||||
}
|
||||
|
||||
// Test dynamic FIFO copmaction options.
|
||||
// This test covers just option parsing and makes sure that the options are
|
||||
// correctly assigned. Also look at DBOptionsTest.SetFIFOCompactionOptions
|
||||
// test which makes sure that the FIFO compaction funcionality is working
|
||||
// as expected on dynamically changing the options.
|
||||
// Even more FIFOCompactionTests are at DBTest.FIFOCompaction* .
|
||||
TEST_F(DBTest, DynamicFIFOCompactionOptions) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Initial defaults
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
1024 * 1024 * 1024);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 0);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo", "{max_table_files_size=23;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
23);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 0);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compaction_options_fifo", "{ttl=97}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
23);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 97);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"compaction_options_fifo", "{ttl=203;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
23);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 203);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
false);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo", "{allow_compaction=true;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
23);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 203);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo", "{max_table_files_size=31;ttl=19;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
31);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 19);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_fifo",
|
||||
"{max_table_files_size=51;ttl=49;allow_compaction=true;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.max_table_files_size,
|
||||
51);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.ttl, 49);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_fifo.allow_compaction,
|
||||
true);
|
||||
}
|
||||
|
||||
TEST_F(DBTest, DynamicUniversalCompactionOptions) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Initial defaults
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 1);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width,
|
||||
2);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width,
|
||||
UINT_MAX);
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions()
|
||||
.compaction_options_universal.max_size_amplification_percent,
|
||||
200);
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions()
|
||||
.compaction_options_universal.compression_size_percent,
|
||||
-1);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style,
|
||||
kCompactionStopStyleTotalSize);
|
||||
ASSERT_EQ(
|
||||
dbfull()->GetOptions().compaction_options_universal.allow_trivial_move,
|
||||
false);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_universal", "{size_ratio=7;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 7);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width,
|
||||
2);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width,
|
||||
UINT_MAX);
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions()
|
||||
.compaction_options_universal.max_size_amplification_percent,
|
||||
200);
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions()
|
||||
.compaction_options_universal.compression_size_percent,
|
||||
-1);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style,
|
||||
kCompactionStopStyleTotalSize);
|
||||
ASSERT_EQ(
|
||||
dbfull()->GetOptions().compaction_options_universal.allow_trivial_move,
|
||||
false);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"compaction_options_universal", "{min_merge_width=11;}"}}));
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.size_ratio, 7);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.min_merge_width,
|
||||
11);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.max_merge_width,
|
||||
UINT_MAX);
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions()
|
||||
.compaction_options_universal.max_size_amplification_percent,
|
||||
200);
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions()
|
||||
.compaction_options_universal.compression_size_percent,
|
||||
-1);
|
||||
ASSERT_EQ(dbfull()->GetOptions().compaction_options_universal.stop_style,
|
||||
kCompactionStopStyleTotalSize);
|
||||
ASSERT_EQ(
|
||||
dbfull()->GetOptions().compaction_options_universal.allow_trivial_move,
|
||||
false);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, FileCreationRandomFailure) {
|
||||
@@ -4503,7 +4707,7 @@ TEST_F(DBTest, EncodeDecompressedBlockSizeTest) {
|
||||
options.compression = comp;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int kNumKeysWritten = 100000;
|
||||
int kNumKeysWritten = 1000;
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < kNumKeysWritten; ++i) {
|
||||
@@ -4584,7 +4788,7 @@ class DelayedMergeOperator : public MergeOperator {
|
||||
public:
|
||||
explicit DelayedMergeOperator(DBTest* d) : db_test_(d) {}
|
||||
|
||||
virtual bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
virtual bool FullMergeV2(const MergeOperationInput& /*merge_in*/,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
db_test_->env_->addon_time_.fetch_add(1000);
|
||||
merge_out->new_value = "";
|
||||
@@ -4896,55 +5100,84 @@ TEST_F(DBTest, PromoteL0Failure) {
|
||||
status = experimental::PromoteL0(db_, db_->DefaultColumnFamily());
|
||||
ASSERT_TRUE(status.IsInvalidArgument());
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Github issue #596
|
||||
TEST_F(DBTest, HugeNumberOfLevels) {
|
||||
TEST_F(DBTest, CompactRangeWithEmptyBottomLevel) {
|
||||
const int kNumLevels = 2;
|
||||
const int kNumL0Files = 2;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 2 * 1024 * 1024; // 2MB
|
||||
options.max_bytes_for_level_base = 2 * 1024 * 1024; // 2MB
|
||||
options.num_levels = 12;
|
||||
options.max_background_compactions = 10;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = kNumLevels;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 300000; ++i) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
ASSERT_OK(Put(Key(0), RandomString(&rnd, 1024)));
|
||||
Flush();
|
||||
}
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), kNumL0Files);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 0);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), kNumL0Files);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest, AutomaticConflictsWithManualCompaction) {
|
||||
const int kNumL0Files = 50;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 2 * 1024 * 1024; // 2MB
|
||||
options.max_bytes_for_level_base = 2 * 1024 * 1024; // 2MB
|
||||
options.num_levels = 12;
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
// never slowdown / stop
|
||||
options.level0_slowdown_writes_trigger = 999999;
|
||||
options.level0_stop_writes_trigger = 999999;
|
||||
options.max_background_compactions = 10;
|
||||
options.max_bytes_for_level_multiplier = 2;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 300000; ++i) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 1024)));
|
||||
}
|
||||
|
||||
// schedule automatic compactions after the manual one starts, but before it
|
||||
// finishes to ensure conflict.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::BackgroundCompaction:Start",
|
||||
"DBTest::AutomaticConflictsWithManualCompaction:PrePuts"},
|
||||
{"DBTest::AutomaticConflictsWithManualCompaction:PostPuts",
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun"}});
|
||||
std::atomic<int> callback_count(0);
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction()::Conflict",
|
||||
"DBImpl::MaybeScheduleFlushOrCompaction:Conflict",
|
||||
[&](void* arg) { callback_count.fetch_add(1); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
CompactRangeOptions croptions;
|
||||
croptions.exclusive_manual_compaction = false;
|
||||
ASSERT_OK(db_->CompactRange(croptions, nullptr, nullptr));
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// put two keys to ensure no trivial move
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
port::Thread manual_compaction_thread([this]() {
|
||||
CompactRangeOptions croptions;
|
||||
croptions.exclusive_manual_compaction = true;
|
||||
ASSERT_OK(db_->CompactRange(croptions, nullptr, nullptr));
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("DBTest::AutomaticConflictsWithManualCompaction:PrePuts");
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
// put two keys to ensure no trivial move
|
||||
for (int j = 0; j < 2; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
TEST_SYNC_POINT("DBTest::AutomaticConflictsWithManualCompaction:PostPuts");
|
||||
|
||||
ASSERT_GE(callback_count.load(), 1);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
for (int i = 0; i < 300000; ++i) {
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(i)));
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
manual_compaction_thread.join();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
|
||||
// Github issue #595
|
||||
@@ -5160,6 +5393,46 @@ TEST_F(DBTest, HardLimit) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class WriteStallListener : public EventListener {
|
||||
public:
|
||||
WriteStallListener() : cond_(&mutex_),
|
||||
condition_(WriteStallCondition::kNormal),
|
||||
expected_(WriteStallCondition::kNormal),
|
||||
expected_set_(false)
|
||||
{}
|
||||
void OnStallConditionsChanged(const WriteStallInfo& info) override {
|
||||
MutexLock l(&mutex_);
|
||||
condition_ = info.condition.cur;
|
||||
if (expected_set_ &&
|
||||
condition_ == expected_) {
|
||||
cond_.Signal();
|
||||
expected_set_ = false;
|
||||
}
|
||||
}
|
||||
bool CheckCondition(WriteStallCondition expected) {
|
||||
MutexLock l(&mutex_);
|
||||
if (expected != condition_) {
|
||||
expected_ = expected;
|
||||
expected_set_ = true;
|
||||
while (expected != condition_) {
|
||||
// We bail out on timeout 500 milliseconds
|
||||
const uint64_t timeout_us = 500000;
|
||||
if (cond_.TimedWait(timeout_us)) {
|
||||
expected_set_ = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
port::Mutex mutex_;
|
||||
port::CondVar cond_;
|
||||
WriteStallCondition condition_;
|
||||
WriteStallCondition expected_;
|
||||
bool expected_set_;
|
||||
};
|
||||
|
||||
TEST_F(DBTest, SoftLimit) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
@@ -5175,6 +5448,8 @@ TEST_F(DBTest, SoftLimit) {
|
||||
options.max_bytes_for_level_multiplier = 10;
|
||||
options.max_background_compactions = 1;
|
||||
options.compression = kNoCompression;
|
||||
WriteStallListener* listener = new WriteStallListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
Reopen(options);
|
||||
|
||||
@@ -5214,6 +5489,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
Flush();
|
||||
}
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed));
|
||||
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
@@ -5224,6 +5500,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
// The L1 file size is around 30KB.
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal));
|
||||
|
||||
// Only allow one compactin going through.
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
@@ -5258,6 +5535,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
// doesn't trigger soft_pending_compaction_bytes_limit
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal));
|
||||
|
||||
// Create 3 L0 files, making score of L0 to be 3, higher than L0.
|
||||
for (int i = 0; i < 3; i++) {
|
||||
@@ -5278,11 +5556,13 @@ TEST_F(DBTest, SoftLimit) {
|
||||
// triggerring soft_pending_compaction_bytes_limit
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1), 1);
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed));
|
||||
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilSleeping();
|
||||
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kNormal));
|
||||
|
||||
// shrink level base so L2 will hit soft limit easier.
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
@@ -5292,6 +5572,7 @@ TEST_F(DBTest, SoftLimit) {
|
||||
Put("", "");
|
||||
Flush();
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(listener->CheckCondition(WriteStallCondition::kDelayed));
|
||||
|
||||
sleeping_task_low.WaitUntilSleeping();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -5477,6 +5758,50 @@ TEST_F(DBTest, PauseBackgroundWorkTest) {
|
||||
// now it's done
|
||||
ASSERT_TRUE(done.load());
|
||||
}
|
||||
|
||||
// Keep spawning short-living threads that create an iterator and quit.
|
||||
// Meanwhile in another thread keep flushing memtables.
|
||||
// This used to cause a deadlock.
|
||||
TEST_F(DBTest, ThreadLocalPtrDeadlock) {
|
||||
std::atomic<int> flushes_done{0};
|
||||
std::atomic<int> threads_destroyed{0};
|
||||
auto done = [&] {
|
||||
return flushes_done.load() > 10;
|
||||
};
|
||||
|
||||
port::Thread flushing_thread([&] {
|
||||
for (int i = 0; !done(); ++i) {
|
||||
ASSERT_OK(db_->Put(WriteOptions(), Slice("hi"),
|
||||
Slice(std::to_string(i).c_str())));
|
||||
ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
int cnt = ++flushes_done;
|
||||
fprintf(stderr, "Flushed %d times\n", cnt);
|
||||
}
|
||||
});
|
||||
|
||||
std::vector<port::Thread> thread_spawning_threads(10);
|
||||
for (auto& t: thread_spawning_threads) {
|
||||
t = port::Thread([&] {
|
||||
while (!done()) {
|
||||
{
|
||||
port::Thread tmp_thread([&] {
|
||||
auto it = db_->NewIterator(ReadOptions());
|
||||
delete it;
|
||||
});
|
||||
tmp_thread.join();
|
||||
}
|
||||
++threads_destroyed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& t: thread_spawning_threads) {
|
||||
t.join();
|
||||
}
|
||||
flushing_thread.join();
|
||||
fprintf(stderr, "Done. Flushed %d times, destroyed %d threads\n",
|
||||
flushes_done.load(), threads_destroyed.load());
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+205
-29
@@ -11,6 +11,7 @@
|
||||
#include <functional>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/read_callback.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/persistent_cache.h"
|
||||
@@ -497,9 +498,9 @@ TEST_F(DBTest2, WalFilterTest) {
|
||||
apply_option_at_record_index_(apply_option_for_record_index),
|
||||
current_record_index_(0) {}
|
||||
|
||||
virtual WalProcessingOption LogRecord(const WriteBatch& batch,
|
||||
WriteBatch* new_batch,
|
||||
bool* batch_changed) const override {
|
||||
virtual WalProcessingOption LogRecord(
|
||||
const WriteBatch& /*batch*/, WriteBatch* /*new_batch*/,
|
||||
bool* /*batch_changed*/) const override {
|
||||
WalFilter::WalProcessingOption option_to_return;
|
||||
|
||||
if (current_record_index_ == apply_option_at_record_index_) {
|
||||
@@ -873,11 +874,10 @@ TEST_F(DBTest2, WalFilterTestWithColumnFamilies) {
|
||||
cf_name_id_map_ = cf_name_id_map;
|
||||
}
|
||||
|
||||
virtual WalProcessingOption LogRecordFound(unsigned long long log_number,
|
||||
const std::string& log_file_name,
|
||||
const WriteBatch& batch,
|
||||
WriteBatch* new_batch,
|
||||
bool* batch_changed) override {
|
||||
virtual WalProcessingOption LogRecordFound(
|
||||
unsigned long long log_number, const std::string& /*log_file_name*/,
|
||||
const WriteBatch& batch, WriteBatch* /*new_batch*/,
|
||||
bool* /*batch_changed*/) override {
|
||||
class LogRecordBatchHandler : public WriteBatch::Handler {
|
||||
private:
|
||||
const std::map<uint32_t, uint64_t> & cf_log_number_map_;
|
||||
@@ -1026,6 +1026,7 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
const size_t kL0FileBytes = 128 << 10;
|
||||
const size_t kApproxPerBlockOverheadBytes = 50;
|
||||
const int kNumL0Files = 5;
|
||||
const int kZstdTrainFactor = 16;
|
||||
|
||||
Options options;
|
||||
options.env = CurrentOptions().env; // Make sure to use any custom env that the test is configured with.
|
||||
@@ -1058,17 +1059,34 @@ TEST_F(DBTest2, PresetCompressionDict) {
|
||||
for (auto compression_type : compression_types) {
|
||||
options.compression = compression_type;
|
||||
size_t prev_out_bytes;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
// First iteration: compress without preset dictionary
|
||||
// Second iteration: compress with preset dictionary
|
||||
// To make sure the compression dictionary was actually used, we verify
|
||||
// the compressed size is smaller in the second iteration. Also in the
|
||||
// second iteration, verify the data we get out is the same data we put
|
||||
// in.
|
||||
if (i) {
|
||||
options.compression_opts.max_dict_bytes = kBlockSizeBytes;
|
||||
} else {
|
||||
options.compression_opts.max_dict_bytes = 0;
|
||||
// Third iteration (zstd only): compress with zstd-trained dictionary
|
||||
//
|
||||
// To make sure the compression dictionary has the intended effect, we
|
||||
// verify the compressed size is smaller in successive iterations. Also in
|
||||
// the non-first iterations, verify the data we get out is the same data
|
||||
// we put in.
|
||||
switch (i) {
|
||||
case 0:
|
||||
options.compression_opts.max_dict_bytes = 0;
|
||||
options.compression_opts.zstd_max_train_bytes = 0;
|
||||
break;
|
||||
case 1:
|
||||
options.compression_opts.max_dict_bytes = kBlockSizeBytes;
|
||||
options.compression_opts.zstd_max_train_bytes = 0;
|
||||
break;
|
||||
case 2:
|
||||
if (compression_type != kZSTD) {
|
||||
continue;
|
||||
}
|
||||
options.compression_opts.max_dict_bytes = kBlockSizeBytes;
|
||||
options.compression_opts.zstd_max_train_bytes =
|
||||
kZstdTrainFactor * kBlockSizeBytes;
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
@@ -1133,7 +1151,7 @@ class CompactionCompressionListener : public EventListener {
|
||||
}
|
||||
|
||||
if (db_options_->bottommost_compression != kDisableCompressionOption &&
|
||||
ci.output_level == bottommost_level && ci.output_level >= 2) {
|
||||
ci.output_level == bottommost_level) {
|
||||
ASSERT_EQ(ci.compression, db_options_->bottommost_compression);
|
||||
} else if (db_options_->compression_per_level.size() != 0) {
|
||||
ASSERT_EQ(ci.compression,
|
||||
@@ -1212,7 +1230,7 @@ class CompactionStallTestListener : public EventListener {
|
||||
public:
|
||||
CompactionStallTestListener() : compacted_files_cnt_(0) {}
|
||||
|
||||
void OnCompactionCompleted(DB* db, const CompactionJobInfo& ci) override {
|
||||
void OnCompactionCompleted(DB* /*db*/, const CompactionJobInfo& ci) override {
|
||||
ASSERT_EQ(ci.cf_name, "default");
|
||||
ASSERT_EQ(ci.base_input_level, 0);
|
||||
ASSERT_EQ(ci.compaction_reason, CompactionReason::kLevelL0FilesNum);
|
||||
@@ -1801,11 +1819,29 @@ TEST_F(DBTest2, ReadAmpBitmap) {
|
||||
|
||||
#ifndef OS_SOLARIS // GetUniqueIdFromFile is not implemented
|
||||
TEST_F(DBTest2, ReadAmpBitmapLiveInCacheAfterDBClose) {
|
||||
if (dbname_.find("dev/shm") != std::string::npos) {
|
||||
// /dev/shm dont support getting a unique file id, this mean that
|
||||
// running this test on /dev/shm will fail because lru_cache will load
|
||||
// the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
{
|
||||
const int kIdBufLen = 100;
|
||||
char id_buf[kIdBufLen];
|
||||
#ifndef OS_WIN
|
||||
// You can't open a directory on windows using random access file
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
ASSERT_OK(env_->NewRandomAccessFile(dbname_, &file, EnvOptions()));
|
||||
if (file->GetUniqueId(id_buf, kIdBufLen) == 0) {
|
||||
// fs holding db directory doesn't support getting a unique file id,
|
||||
// this means that running this test will fail because lru_cache will load
|
||||
// the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
}
|
||||
#else
|
||||
std::unique_ptr<Directory> dir;
|
||||
ASSERT_OK(env_->NewDirectory(dbname_, &dir));
|
||||
if (dir->GetUniqueId(id_buf, kIdBufLen) == 0) {
|
||||
// fs holding db directory doesn't support getting a unique file id,
|
||||
// this means that running this test will fail because lru_cache will load
|
||||
// the blocks again regardless of them being already in the cache
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
uint32_t bytes_per_bit[2] = {1, 16};
|
||||
for (size_t k = 0; k < 2; k++) {
|
||||
@@ -1918,19 +1954,19 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) {
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
|
||||
auto get_stat = [](std::string level_str, LevelStatType type,
|
||||
std::map<std::string, double> props) {
|
||||
std::map<std::string, std::string> props) {
|
||||
auto prop_str =
|
||||
level_str + "." +
|
||||
"compaction." + level_str + "." +
|
||||
InternalStats::compaction_level_stats.at(type).property_name.c_str();
|
||||
auto prop_item = props.find(prop_str);
|
||||
return prop_item == props.end() ? 0 : prop_item->second;
|
||||
return prop_item == props.end() ? 0 : std::stod(prop_item->second);
|
||||
};
|
||||
|
||||
// Trivial move 2 files to L2
|
||||
ASSERT_EQ("0,0,2", FilesPerLevel());
|
||||
// Also test that the stats GetMapProperty API reporting the same result
|
||||
{
|
||||
std::map<std::string, double> prop;
|
||||
std::map<std::string, std::string> prop;
|
||||
ASSERT_TRUE(dbfull()->GetMapProperty("rocksdb.cfstats", &prop));
|
||||
ASSERT_EQ(0, get_stat("L0", LevelStatType::NUM_FILES, prop));
|
||||
ASSERT_EQ(0, get_stat("L1", LevelStatType::NUM_FILES, prop));
|
||||
@@ -1966,7 +2002,7 @@ TEST_F(DBTest2, AutomaticCompactionOverlapManualCompaction) {
|
||||
|
||||
// Test that the stats GetMapProperty API reporting 1 file in L2
|
||||
{
|
||||
std::map<std::string, double> prop;
|
||||
std::map<std::string, std::string> prop;
|
||||
ASSERT_TRUE(dbfull()->GetMapProperty("rocksdb.cfstats", &prop));
|
||||
ASSERT_EQ(1, get_stat("L2", LevelStatType::NUM_FILES, prop));
|
||||
}
|
||||
@@ -2315,16 +2351,156 @@ TEST_F(DBTest2, ReduceLevel) {
|
||||
Put("foo", "bar");
|
||||
Flush();
|
||||
MoveFilesToLevel(6);
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 1;
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
options.num_levels = 3;
|
||||
Reopen(options);
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
// Test that ReadCallback is actually used in both memtbale and sst tables
|
||||
TEST_F(DBTest2, ReadCallbackTest) {
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.num_levels = 7;
|
||||
Reopen(options);
|
||||
std::vector<const Snapshot*> snapshots;
|
||||
// Try to create a db with multiple layers and a memtable
|
||||
const std::string key = "foo";
|
||||
const std::string value = "bar";
|
||||
// This test assumes that the seq start with 1 and increased by 1 after each
|
||||
// write batch of size 1. If that behavior changes, the test needs to be
|
||||
// updated as well.
|
||||
// TODO(myabandeh): update this test to use the seq number that is returned by
|
||||
// the DB instead of assuming what seq the DB used.
|
||||
int i = 1;
|
||||
for (; i < 10; i++) {
|
||||
Put(key, value + std::to_string(i));
|
||||
// Take a snapshot to avoid the value being removed during compaction
|
||||
auto snapshot = dbfull()->GetSnapshot();
|
||||
snapshots.push_back(snapshot);
|
||||
}
|
||||
Flush();
|
||||
for (; i < 20; i++) {
|
||||
Put(key, value + std::to_string(i));
|
||||
// Take a snapshot to avoid the value being removed during compaction
|
||||
auto snapshot = dbfull()->GetSnapshot();
|
||||
snapshots.push_back(snapshot);
|
||||
}
|
||||
Flush();
|
||||
MoveFilesToLevel(6);
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("0,0,0,0,0,0,2", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
for (; i < 30; i++) {
|
||||
Put(key, value + std::to_string(i));
|
||||
auto snapshot = dbfull()->GetSnapshot();
|
||||
snapshots.push_back(snapshot);
|
||||
}
|
||||
Flush();
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ("1,0,0,0,0,0,2", FilesPerLevel());
|
||||
#endif // !ROCKSDB_LITE
|
||||
// And also add some values to the memtable
|
||||
for (; i < 40; i++) {
|
||||
Put(key, value + std::to_string(i));
|
||||
auto snapshot = dbfull()->GetSnapshot();
|
||||
snapshots.push_back(snapshot);
|
||||
}
|
||||
|
||||
class TestReadCallback : public ReadCallback {
|
||||
public:
|
||||
explicit TestReadCallback(SequenceNumber snapshot) : snapshot_(snapshot) {}
|
||||
virtual bool IsCommitted(SequenceNumber seq) override {
|
||||
return seq <= snapshot_;
|
||||
}
|
||||
|
||||
private:
|
||||
SequenceNumber snapshot_;
|
||||
};
|
||||
|
||||
for (int seq = 1; seq < i; seq++) {
|
||||
PinnableSlice pinnable_val;
|
||||
ReadOptions roptions;
|
||||
TestReadCallback callback(seq);
|
||||
bool dont_care = true;
|
||||
Status s = dbfull()->GetImpl(roptions, dbfull()->DefaultColumnFamily(), key,
|
||||
&pinnable_val, &dont_care, &callback);
|
||||
ASSERT_TRUE(s.ok());
|
||||
// Assuming that after each Put the DB increased seq by one, the value and
|
||||
// seq number must be equal since we also inc value by 1 after each Put.
|
||||
ASSERT_EQ(value + std::to_string(seq), pinnable_val.ToString());
|
||||
}
|
||||
|
||||
for (auto snapshot : snapshots) {
|
||||
dbfull()->ReleaseSnapshot(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTest2, LiveFilesOmitObsoleteFiles) {
|
||||
// Regression test for race condition where an obsolete file is returned to
|
||||
// user as a "live file" but then deleted, all while file deletions are
|
||||
// disabled.
|
||||
//
|
||||
// It happened like this:
|
||||
//
|
||||
// 1. [flush thread] Log file "x.log" found by FindObsoleteFiles
|
||||
// 2. [user thread] DisableFileDeletions, GetSortedWalFiles are called and the
|
||||
// latter returned "x.log"
|
||||
// 3. [flush thread] PurgeObsoleteFiles deleted "x.log"
|
||||
// 4. [user thread] Reading "x.log" failed
|
||||
//
|
||||
// Unfortunately the only regression test I can come up with involves sleep.
|
||||
// We cannot set SyncPoints to repro since, once the fix is applied, the
|
||||
// SyncPoints would cause a deadlock as the repro's sequence of events is now
|
||||
// prohibited.
|
||||
//
|
||||
// Instead, if we sleep for a second between Find and Purge, and ensure the
|
||||
// read attempt happens after purge, then the sequence of events will almost
|
||||
// certainly happen on the old code.
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::BackgroundCallFlush:FilesFound",
|
||||
"DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered"},
|
||||
{"DBImpl::PurgeObsoleteFiles:End",
|
||||
"DBTest2::LiveFilesOmitObsoleteFiles:LiveFilesCaptured"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::PurgeObsoleteFiles:Begin",
|
||||
[&](void* arg) { env_->SleepForMicroseconds(1000000); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Put("key", "val");
|
||||
FlushOptions flush_opts;
|
||||
flush_opts.wait = false;
|
||||
db_->Flush(flush_opts);
|
||||
TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:FlushTriggered");
|
||||
|
||||
db_->DisableFileDeletions();
|
||||
VectorLogPtr log_files;
|
||||
db_->GetSortedWalFiles(log_files);
|
||||
TEST_SYNC_POINT("DBTest2::LiveFilesOmitObsoleteFiles:LiveFilesCaptured");
|
||||
for (const auto& log_file : log_files) {
|
||||
ASSERT_OK(env_->FileExists(LogFileName(dbname_, log_file->LogNumber())));
|
||||
}
|
||||
|
||||
db_->EnableFileDeletions();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+22
-14
@@ -288,12 +288,11 @@ Options DBTestBase::GetOptions(
|
||||
Options options = default_options;
|
||||
BlockBasedTableOptions table_options;
|
||||
bool set_block_based_table_factory = true;
|
||||
#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \
|
||||
!defined(OS_AIX)
|
||||
#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \
|
||||
!defined(OS_AIX)
|
||||
rocksdb::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"NewRandomAccessFile:O_DIRECT");
|
||||
rocksdb::SyncPoint::GetInstance()->ClearCallBack(
|
||||
"NewWritableFile:O_DIRECT");
|
||||
rocksdb::SyncPoint::GetInstance()->ClearCallBack("NewWritableFile:O_DIRECT");
|
||||
#endif
|
||||
|
||||
bool can_allow_mmap = IsMemoryMappedAccessSupported();
|
||||
@@ -466,7 +465,7 @@ Options DBTestBase::GetOptions(
|
||||
options.use_direct_io_for_flush_and_compaction = true;
|
||||
options.compaction_readahead_size = 2 * 1024 * 1024;
|
||||
#if !defined(OS_MACOSX) && !defined(OS_WIN) && !defined(OS_SOLARIS) && \
|
||||
!defined(OS_AIX)
|
||||
!defined(OS_AIX) && !defined(OS_OPENBSD)
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"NewWritableFile:O_DIRECT", [&](void* arg) {
|
||||
int* val = static_cast<int*>(arg);
|
||||
@@ -487,7 +486,7 @@ Options DBTestBase::GetOptions(
|
||||
}
|
||||
case kConcurrentWALWrites: {
|
||||
// This options optimize 2PC commit path
|
||||
options.concurrent_prepare = true;
|
||||
options.two_write_queues = true;
|
||||
options.manual_wal_flush = true;
|
||||
break;
|
||||
}
|
||||
@@ -668,6 +667,10 @@ Status DBTestBase::SingleDelete(int cf, const std::string& k) {
|
||||
return db_->SingleDelete(WriteOptions(), handles_[cf], k);
|
||||
}
|
||||
|
||||
bool DBTestBase::SetPreserveDeletesSequenceNumber(SequenceNumber sn) {
|
||||
return db_->SetPreserveDeletesSequenceNumber(sn);
|
||||
}
|
||||
|
||||
std::string DBTestBase::Get(const std::string& k, const Snapshot* snapshot) {
|
||||
ReadOptions options;
|
||||
options.verify_checksums = true;
|
||||
@@ -864,7 +867,6 @@ size_t DBTestBase::CountLiveFiles() {
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
return metadata.size();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
|
||||
std::string property;
|
||||
@@ -925,6 +927,7 @@ std::string DBTestBase::FilesPerLevel(int cf) {
|
||||
result.resize(last_non_zero_offset);
|
||||
return result;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
size_t DBTestBase::CountFiles() {
|
||||
std::vector<std::string> files;
|
||||
@@ -994,6 +997,7 @@ void DBTestBase::MoveFilesToLevel(int level, int cf) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void DBTestBase::DumpFileCounts(const char* label) {
|
||||
fprintf(stderr, "---\n%s:\n", label);
|
||||
fprintf(stderr, "maxoverlap: %" PRIu64 "\n",
|
||||
@@ -1005,6 +1009,7 @@ void DBTestBase::DumpFileCounts(const char* label) {
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
std::string DBTestBase::DumpSSTableList() {
|
||||
std::string property;
|
||||
@@ -1138,27 +1143,30 @@ UpdateStatus DBTestBase::updateInPlaceSmallerVarintSize(char* prevValue,
|
||||
}
|
||||
}
|
||||
|
||||
UpdateStatus DBTestBase::updateInPlaceLargerSize(char* prevValue,
|
||||
uint32_t* prevSize,
|
||||
UpdateStatus DBTestBase::updateInPlaceLargerSize(char* /*prevValue*/,
|
||||
uint32_t* /*prevSize*/,
|
||||
Slice delta,
|
||||
std::string* newValue) {
|
||||
*newValue = std::string(delta.size(), 'c');
|
||||
return UpdateStatus::UPDATED;
|
||||
}
|
||||
|
||||
UpdateStatus DBTestBase::updateInPlaceNoAction(char* prevValue,
|
||||
uint32_t* prevSize, Slice delta,
|
||||
std::string* newValue) {
|
||||
UpdateStatus DBTestBase::updateInPlaceNoAction(char* /*prevValue*/,
|
||||
uint32_t* /*prevSize*/,
|
||||
Slice /*delta*/,
|
||||
std::string* /*newValue*/) {
|
||||
return UpdateStatus::UPDATE_FAILED;
|
||||
}
|
||||
|
||||
// Utility method to test InplaceUpdate
|
||||
void DBTestBase::validateNumberOfEntries(int numValues, int cf) {
|
||||
ScopedArenaIterator iter;
|
||||
Arena arena;
|
||||
auto options = CurrentOptions();
|
||||
InternalKeyComparator icmp(options.comparator);
|
||||
RangeDelAggregator range_del_agg(icmp, {} /* snapshots */);
|
||||
// This should be defined after range_del_agg so that it destructs the
|
||||
// assigned iterator before it range_del_agg is already destructed.
|
||||
ScopedArenaIterator iter;
|
||||
if (cf != 0) {
|
||||
iter.set(
|
||||
dbfull()->NewInternalIterator(&arena, &range_del_agg, handles_[cf]));
|
||||
@@ -1170,7 +1178,7 @@ void DBTestBase::validateNumberOfEntries(int numValues, int cf) {
|
||||
int seq = numValues;
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey;
|
||||
ikey.sequence = -1;
|
||||
ikey.clear();
|
||||
ASSERT_EQ(ParseInternalKey(iter->key(), &ikey), true);
|
||||
|
||||
// checks sequence number for updates
|
||||
|
||||
+43
-3
@@ -137,8 +137,8 @@ class SpecialMemTableRep : public MemTableRep {
|
||||
// Insert key into the list.
|
||||
// REQUIRES: nothing that compares equal to key is currently in the list.
|
||||
virtual void Insert(KeyHandle handle) override {
|
||||
memtable_->Insert(handle);
|
||||
num_entries_++;
|
||||
memtable_->Insert(handle);
|
||||
}
|
||||
|
||||
// Returns true iff an entry that compares equal to key is in the list.
|
||||
@@ -187,7 +187,7 @@ class SpecialSkipListFactory : public MemTableRepFactory {
|
||||
using MemTableRepFactory::CreateMemTableRep;
|
||||
virtual MemTableRep* CreateMemTableRep(
|
||||
const MemTableRep::KeyComparator& compare, Allocator* allocator,
|
||||
const SliceTransform* transform, Logger* logger) override {
|
||||
const SliceTransform* transform, Logger* /*logger*/) override {
|
||||
return new SpecialMemTableRep(
|
||||
allocator, factory_.CreateMemTableRep(compare, allocator, transform, 0),
|
||||
num_entries_flush_);
|
||||
@@ -445,6 +445,9 @@ class SpecialEnv : public EnvWrapper {
|
||||
r->reset(new CountingFile(std::move(*r), &random_read_counter_,
|
||||
&random_read_bytes_counter_));
|
||||
}
|
||||
if (s.ok() && soptions.compaction_readahead_size > 0) {
|
||||
compaction_readahead_size_ = soptions.compaction_readahead_size;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -570,6 +573,39 @@ class SpecialEnv : public EnvWrapper {
|
||||
bool no_slowdown_;
|
||||
|
||||
std::atomic<bool> is_wal_sync_thread_safe_{true};
|
||||
|
||||
std::atomic<size_t> compaction_readahead_size_;
|
||||
};
|
||||
|
||||
class MockTimeEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit MockTimeEnv(Env* base) : EnvWrapper(base) {}
|
||||
|
||||
virtual Status GetCurrentTime(int64_t* time) override {
|
||||
assert(time != nullptr);
|
||||
assert(current_time_ <=
|
||||
static_cast<uint64_t>(std::numeric_limits<int64_t>::max()));
|
||||
*time = static_cast<int64_t>(current_time_);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual uint64_t NowMicros() override {
|
||||
assert(current_time_ <= std::numeric_limits<uint64_t>::max() / 1000000);
|
||||
return current_time_ * 1000000;
|
||||
}
|
||||
|
||||
virtual uint64_t NowNanos() override {
|
||||
assert(current_time_ <= std::numeric_limits<uint64_t>::max() / 1000000000);
|
||||
return current_time_ * 1000000000;
|
||||
}
|
||||
|
||||
void set_current_time(uint64_t time) {
|
||||
assert(time >= current_time_);
|
||||
current_time_ = time;
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t current_time_ = 0;
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
@@ -798,6 +834,8 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
Status SingleDelete(int cf, const std::string& k);
|
||||
|
||||
bool SetPreserveDeletesSequenceNumber(SequenceNumber sn);
|
||||
|
||||
std::string Get(const std::string& k, const Snapshot* snapshot = nullptr);
|
||||
|
||||
std::string Get(int cf, const std::string& k,
|
||||
@@ -825,13 +863,13 @@ class DBTestBase : public testing::Test {
|
||||
size_t TotalLiveFiles(int cf = 0);
|
||||
|
||||
size_t CountLiveFiles();
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
int NumTableFilesAtLevel(int level, int cf = 0);
|
||||
|
||||
double CompressionRatioAtLevel(int level, int cf = 0);
|
||||
|
||||
int TotalTableFiles(int cf = 0, int levels = -1);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Return spread of files per level
|
||||
std::string FilesPerLevel(int cf = 0);
|
||||
@@ -859,7 +897,9 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
void MoveFilesToLevel(int level, int cf = 0);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void DumpFileCounts(const char* label);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
std::string DumpSSTableList();
|
||||
|
||||
|
||||
@@ -56,9 +56,9 @@ void VerifyCompactionResult(
|
||||
|
||||
class KeepFilter : public CompactionFilter {
|
||||
public:
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value, bool* value_changed) const
|
||||
override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -88,9 +88,9 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
class DelayFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit DelayFilter(DBTestBase* d) : db_test(d) {}
|
||||
virtual bool Filter(int level, const Slice& key, const Slice& value,
|
||||
std::string* new_value,
|
||||
bool* value_changed) const override {
|
||||
virtual bool Filter(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*value*/, std::string* /*new_value*/,
|
||||
bool* /*value_changed*/) const override {
|
||||
db_test->env_->addon_time_.fetch_add(1000);
|
||||
return true;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ class DelayFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DelayFilterFactory(DBTestBase* d) : db_test(d) {}
|
||||
virtual std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
const CompactionFilter::Context& /*context*/) override {
|
||||
return std::unique_ptr<CompactionFilter>(new DelayFilter(db_test));
|
||||
}
|
||||
|
||||
@@ -374,6 +374,181 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSizeAmplification) {
|
||||
ASSERT_EQ(NumSortedRuns(1), 1);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionSizeAmplification) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 1;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
// Initial setup of compaction_options_universal will prevent universal
|
||||
// compaction from happening
|
||||
options.compaction_options_universal.size_ratio = 100;
|
||||
options.compaction_options_universal.min_merge_width = 100;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int total_picked_compactions = 0;
|
||||
int total_size_amp_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UniversalCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
if (arg) {
|
||||
total_picked_compactions++;
|
||||
Compaction* c = static_cast<Compaction*>(arg);
|
||||
if (c->compaction_reason() ==
|
||||
CompactionReason::kUniversalSizeAmplification) {
|
||||
total_size_amp_compactions++;
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
MutableCFOptions mutable_cf_options;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_idx = 0;
|
||||
|
||||
// Generate two files in Level 0. Both files are approx the same size.
|
||||
for (int num = 0; num < options.level0_file_num_compaction_trigger - 1;
|
||||
num++) {
|
||||
// Write 110KB (11 values, each 10K)
|
||||
for (int i = 0; i < 11; i++) {
|
||||
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
||||
key_idx++;
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
ASSERT_EQ(NumSortedRuns(1), num + 1);
|
||||
}
|
||||
ASSERT_EQ(NumSortedRuns(1), 2);
|
||||
|
||||
// Flush whatever is remaining in memtable. This is typically
|
||||
// small, which should not trigger size ratio based compaction
|
||||
// but could instead trigger size amplification if it's set
|
||||
// to 110.
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Verify compaction did not happen
|
||||
ASSERT_EQ(NumSortedRuns(1), 3);
|
||||
|
||||
// Trigger compaction if size amplification exceeds 110% without reopening DB
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions(handles_[1])
|
||||
.compaction_options_universal.max_size_amplification_percent,
|
||||
200);
|
||||
ASSERT_OK(dbfull()->SetOptions(handles_[1],
|
||||
{{"compaction_options_universal",
|
||||
"{max_size_amplification_percent=110;}"}}));
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions(handles_[1])
|
||||
.compaction_options_universal.max_size_amplification_percent,
|
||||
110);
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(110, mutable_cf_options.compaction_options_universal
|
||||
.max_size_amplification_percent);
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Verify that size amplification did happen
|
||||
ASSERT_EQ(NumSortedRuns(1), 1);
|
||||
ASSERT_EQ(total_picked_compactions, 1);
|
||||
ASSERT_EQ(total_size_amp_compactions, 1);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, DynamicUniversalCompactionReadAmplification) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 1;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
// Initial setup of compaction_options_universal will prevent universal
|
||||
// compaction from happening
|
||||
options.compaction_options_universal.max_size_amplification_percent = 2000;
|
||||
options.compaction_options_universal.size_ratio = 0;
|
||||
options.compaction_options_universal.min_merge_width = 100;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int total_picked_compactions = 0;
|
||||
int total_size_ratio_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UniversalCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
if (arg) {
|
||||
total_picked_compactions++;
|
||||
Compaction* c = static_cast<Compaction*>(arg);
|
||||
if (c->compaction_reason() == CompactionReason::kUniversalSizeRatio) {
|
||||
total_size_ratio_compactions++;
|
||||
}
|
||||
}
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
MutableCFOptions mutable_cf_options;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_idx = 0;
|
||||
|
||||
// Generate three files in Level 0. All files are approx the same size.
|
||||
for (int num = 0; num < options.level0_file_num_compaction_trigger; num++) {
|
||||
// Write 110KB (11 values, each 10K)
|
||||
for (int i = 0; i < 11; i++) {
|
||||
ASSERT_OK(Put(1, Key(key_idx), RandomString(&rnd, 10000)));
|
||||
key_idx++;
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
ASSERT_EQ(NumSortedRuns(1), num + 1);
|
||||
}
|
||||
ASSERT_EQ(NumSortedRuns(1), options.level0_file_num_compaction_trigger);
|
||||
|
||||
// Flush whatever is remaining in memtable. This is typically small, about
|
||||
// 30KB.
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Verify compaction did not happen
|
||||
ASSERT_EQ(NumSortedRuns(1), options.level0_file_num_compaction_trigger + 1);
|
||||
ASSERT_EQ(total_picked_compactions, 0);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
handles_[1],
|
||||
{{"compaction_options_universal",
|
||||
"{min_merge_width=2;max_merge_width=2;size_ratio=100;}"}}));
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions(handles_[1])
|
||||
.compaction_options_universal.min_merge_width,
|
||||
2);
|
||||
ASSERT_EQ(dbfull()
|
||||
->GetOptions(handles_[1])
|
||||
.compaction_options_universal.max_merge_width,
|
||||
2);
|
||||
ASSERT_EQ(
|
||||
dbfull()->GetOptions(handles_[1]).compaction_options_universal.size_ratio,
|
||||
100);
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_GetLatestMutableCFOptions(handles_[1],
|
||||
&mutable_cf_options));
|
||||
ASSERT_EQ(mutable_cf_options.compaction_options_universal.size_ratio, 100);
|
||||
ASSERT_EQ(mutable_cf_options.compaction_options_universal.min_merge_width, 2);
|
||||
ASSERT_EQ(mutable_cf_options.compaction_options_universal.max_merge_width, 2);
|
||||
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
// Files in L0 are approx: 0.3 (30KB), 1, 1, 1.
|
||||
// On compaction: the files are below the size amp threshold, so we
|
||||
// fallthrough to checking read amp conditions. The configured size ratio is
|
||||
// not big enough to take 0.3 into consideration. So the next files 1 and 1
|
||||
// are compacted together first as they satisfy size ratio condition and
|
||||
// (min_merge_width, max_merge_width) condition, to give out a file size of 2.
|
||||
// Next, the newly generated 2 and the last file 1 are compacted together. So
|
||||
// at the end: #sortedRuns = 2, #picked_compactions = 2, and all the picked
|
||||
// ones are size ratio based compactions.
|
||||
ASSERT_EQ(NumSortedRuns(1), 2);
|
||||
// If max_merge_width had not been changed dynamically above, and if it
|
||||
// continued to be the default value of UINIT_MAX, total_picked_compactions
|
||||
// would have been 1.
|
||||
ASSERT_EQ(total_picked_compactions, 2);
|
||||
ASSERT_EQ(total_size_ratio_compactions, 2);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, CompactFilesOnUniversalCompaction) {
|
||||
const int kTestKeySize = 16;
|
||||
const int kTestValueSize = 984;
|
||||
|
||||
+36
-2
@@ -730,7 +730,8 @@ class RecoveryTestHelper {
|
||||
batch.Put(key, value);
|
||||
WriteBatchInternal::SetSequence(&batch, seq);
|
||||
current_log_writer->AddRecord(WriteBatchInternal::Contents(&batch));
|
||||
versions->SetLastToBeWrittenSequence(seq);
|
||||
versions->SetLastAllocatedSequence(seq);
|
||||
versions->SetLastPublishedSequence(seq);
|
||||
versions->SetLastSequence(seq);
|
||||
}
|
||||
}
|
||||
@@ -876,6 +877,39 @@ TEST_F(DBWALTest, kAbsoluteConsistency) {
|
||||
}
|
||||
}
|
||||
|
||||
// Test scope:
|
||||
// We don't expect the data store to be opened if there is any inconsistency
|
||||
// between WAL and SST files
|
||||
TEST_F(DBWALTest, kPointInTimeRecoveryCFConsistency) {
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
|
||||
// Create DB with multiple column families.
|
||||
CreateAndReopenWithCF({"one", "two"}, options);
|
||||
ASSERT_OK(Put(1, "key1", "val1"));
|
||||
ASSERT_OK(Put(2, "key2", "val2"));
|
||||
|
||||
// Record the offset at this point
|
||||
Env* env = options.env;
|
||||
int wal_file_id = RecoveryTestHelper::kWALFileOffset + 1;
|
||||
std::string fname = LogFileName(dbname_, wal_file_id);
|
||||
uint64_t offset_to_corrupt;
|
||||
ASSERT_OK(env->GetFileSize(fname, &offset_to_corrupt));
|
||||
ASSERT_GT(offset_to_corrupt, 0);
|
||||
|
||||
ASSERT_OK(Put(1, "key3", "val3"));
|
||||
// Corrupt WAL at location of key3
|
||||
RecoveryTestHelper::InduceCorruption(
|
||||
fname, static_cast<size_t>(offset_to_corrupt), static_cast<size_t>(4));
|
||||
ASSERT_OK(Put(2, "key4", "val4"));
|
||||
ASSERT_OK(Put(1, "key5", "val5"));
|
||||
Flush(2);
|
||||
|
||||
// PIT recovery & verify
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
ASSERT_NOK(TryReopenWithColumnFamilies({"default", "one", "two"}, options));
|
||||
}
|
||||
|
||||
// Test scope:
|
||||
// - We expect to open data store under all circumstances
|
||||
// - We expect only data upto the point where the first error was encountered
|
||||
@@ -1007,7 +1041,7 @@ TEST_F(DBWALTest, AvoidFlushDuringRecovery) {
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v11", Get("foo"));
|
||||
ASSERT_EQ("v12", Get("bar"));
|
||||
ASSERT_EQ(2, TotalTableFiles());
|
||||
ASSERT_EQ(3, TotalTableFiles());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, WalCleanupAfterAvoidFlushDuringRecovery) {
|
||||
|
||||
Loaded 100 of 647 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user