mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 11:02:36 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4844d698b9 | ||
|
|
157da07b6d | ||
|
|
951ae63e50 | ||
|
|
8df5fdedba | ||
|
|
3c56f032cd | ||
|
|
368afc9d22 | ||
|
|
5339a5e6e6 | ||
|
|
24c3b2b21e | ||
|
|
f0b881629f | ||
|
|
8ee2ee8952 | ||
|
|
af28d114d6 | ||
|
|
a34c1e3373 | ||
|
|
165cec6ef6 | ||
|
|
d7d6a9a41d | ||
|
|
55ab150b46 | ||
|
|
cba883c538 | ||
|
|
c4ffd74608 | ||
|
|
ac7e52c2ba | ||
|
|
28d8501876 | ||
|
|
e8bbb13a97 | ||
|
|
b06b191362 | ||
|
|
1cca091298 | ||
|
|
de28a25533 | ||
|
|
0fcb6dbed7 | ||
|
|
52ee07b021 | ||
|
|
66a91e2607 | ||
|
|
67036c0406 | ||
|
|
0e2da497c6 | ||
|
|
6d61358a09 | ||
|
|
2d9d36ea46 | ||
|
|
9377518981 | ||
|
|
1ec75ee76b | ||
|
|
607628d349 | ||
|
|
ce1be2ce37 | ||
|
|
f7669b40ba | ||
|
|
22696b0881 | ||
|
|
c1865e0f7f | ||
|
|
a88677d2cf | ||
|
|
80c75593ed | ||
|
|
5051755e35 | ||
|
|
4fd08f4b8b | ||
|
|
e14fbaae26 | ||
|
|
3c2262400f | ||
|
|
ee0e2201e0 | ||
|
|
5a0e9a4cfa | ||
|
|
9447a85404 | ||
|
|
32149059f9 | ||
|
|
4590b53a4b | ||
|
|
85bb308258 | ||
|
|
8ce1b8440a | ||
|
|
380e651afc | ||
|
|
1613fa9490 | ||
|
|
6a14d55bd9 | ||
|
|
de47e2bd4d | ||
|
|
f099af4c76 | ||
|
|
db74b1a219 | ||
|
|
b18f9c9eac | ||
|
|
4e395e875e | ||
|
|
2482d5fb45 | ||
|
|
7541c7a79e | ||
|
|
c8513cde0c | ||
|
|
b49b92cf28 | ||
|
|
c7004840d2 | ||
|
|
4ad928e170 | ||
|
|
e9b2af87f8 | ||
|
|
23a057007c | ||
|
|
dade61ac26 | ||
|
|
cce702a6e4 | ||
|
|
3586901f8f | ||
|
|
b2ce59537c | ||
|
|
4b3438d2d6 | ||
|
|
a081f798b0 | ||
|
|
b10d65c2a4 | ||
|
|
f85f99bf69 | ||
|
|
7b81095171 | ||
|
|
7c95868378 | ||
|
|
ea9e0757ff | ||
|
|
2a9c97108e | ||
|
|
d76ddf327d | ||
|
|
cec2c6436b | ||
|
|
badbff65b7 | ||
|
|
4a16c32ece | ||
|
|
99c4af7160 | ||
|
|
f57bc1d034 | ||
|
|
5440675c3d | ||
|
|
6584cec8f2 | ||
|
|
817eeb29b4 | ||
|
|
ffdf6eee19 | ||
|
|
ecf9003860 | ||
|
|
72f8cc703c | ||
|
|
6a17b07ca8 | ||
|
|
78837f5d61 | ||
|
|
4cc37f59e5 | ||
|
|
ff17a2abf3 | ||
|
|
49d88be021 | ||
|
|
0b63f51fbc | ||
|
|
3981345be1 | ||
|
|
2a2ebb6f5e | ||
|
|
2fc2fd92a9 | ||
|
|
3771e37970 | ||
|
|
236756f2cf | ||
|
|
64a0082c69 | ||
|
|
dd7a748cff | ||
|
|
4fe12baa66 | ||
|
|
6525ce4caf | ||
|
|
a117891b4b | ||
|
|
b248e98cf9 | ||
|
|
deda159b55 | ||
|
|
e408e98c8c | ||
|
|
a297643f2e | ||
|
|
d11c09d9e2 | ||
|
|
d367555027 | ||
|
|
ebdfe34cc4 | ||
|
|
d1be594636 | ||
|
|
6056d6317d | ||
|
|
76a67cf741 | ||
|
|
67c1ae8831 | ||
|
|
b693ba68b5 | ||
|
|
db3dfb164e | ||
|
|
87c91bd876 | ||
|
|
2914de64e8 | ||
|
|
8b79422b52 | ||
|
|
4beffe001d | ||
|
|
821bcb0b39 | ||
|
|
5370f44a83 | ||
|
|
56dd034115 | ||
|
|
ccecf3f4fb | ||
|
|
1b0069ce2d | ||
|
|
638c49f24f | ||
|
|
6b8e9c68b7 | ||
|
|
c38b075e7e | ||
|
|
8f399e3fee | ||
|
|
98d0b78eac | ||
|
|
9fd68b7fb6 | ||
|
|
59ddb50591 | ||
|
|
f4d986364e | ||
|
|
7882cb9773 | ||
|
|
44f5cc57a5 | ||
|
|
7c4615cf1f | ||
|
|
5bb0a7f73d | ||
|
|
86396cc189 | ||
|
|
c1db098dcf | ||
|
|
7da2eaf0d9 | ||
|
|
34723b4c4a | ||
|
|
7cc0dbd66f | ||
|
|
9253767a6a | ||
|
|
f35b16f246 | ||
|
|
c3a4bea5dc | ||
|
|
8234faabfd | ||
|
|
4990c0d1a5 | ||
|
|
7323e4c8ab | ||
|
|
ee027fc19f | ||
|
|
e4609a749b | ||
|
|
2306167d30 | ||
|
|
343304e1d3 | ||
|
|
cdc4eb6892 | ||
|
|
712dd27e67 | ||
|
|
0155c73dee | ||
|
|
c49ea68c55 | ||
|
|
726c2f7e5b | ||
|
|
d51dc96a79 | ||
|
|
c116b47804 | ||
|
|
64046e581c | ||
|
|
9ae92f50b2 | ||
|
|
7c01d65344 | ||
|
|
8796934af7 | ||
|
|
5e2c796589 | ||
|
|
557748ff7b | ||
|
|
811ee21112 | ||
|
|
afad5bd1c5 | ||
|
|
6920cde893 | ||
|
|
e72ea485ed | ||
|
|
9c8ac144bd | ||
|
|
d4c45428af | ||
|
|
d3bfd33972 | ||
|
|
7efd9c25c4 | ||
|
|
50b8d29b9c | ||
|
|
5c858ddd20 | ||
|
|
e5b5f12b81 | ||
|
|
0ce258f9b3 | ||
|
|
ac0d93b08f | ||
|
|
1ae46094de | ||
|
|
8745f013ff | ||
|
|
e12270dfee | ||
|
|
bbd6a5a184 | ||
|
|
9498069fc9 | ||
|
|
f8061a237e | ||
|
|
16e225f70d | ||
|
|
a4955b39ac | ||
|
|
ae0ad719de | ||
|
|
b2a8016df1 | ||
|
|
c6654588bd | ||
|
|
2a6d0cde72 | ||
|
|
ee8bf2e41f | ||
|
|
f85df120f2 | ||
|
|
89e4c48820 | ||
|
|
b06ca5f860 | ||
|
|
4ea0ab3cc5 | ||
|
|
12767b3130 | ||
|
|
a9d512a76b | ||
|
|
d5a51d4de3 | ||
|
|
89f319c2df | ||
|
|
b50632920d | ||
|
|
b9a97181aa | ||
|
|
663afef884 | ||
|
|
32604e6601 | ||
|
|
9ab38c45ad | ||
|
|
08ab1d83ac | ||
|
|
f9b14be495 | ||
|
|
68f3eb746c | ||
|
|
e70020e4f6 | ||
|
|
7bedd94406 | ||
|
|
68a8e6b8fa | ||
|
|
e70ba4e40e | ||
|
|
2a282e5f54 | ||
|
|
d9cfaa2b16 | ||
|
|
9430333f84 | ||
|
|
515b11ffab | ||
|
|
876cb8bfb4 | ||
|
|
21c55bdb6e | ||
|
|
4b95253587 | ||
|
|
c6a8665b37 | ||
|
|
880ee363eb | ||
|
|
6797e6ffac | ||
|
|
dda6c72ac8 | ||
|
|
56222f57df | ||
|
|
15b7a4ab88 | ||
|
|
6ea41f8527 | ||
|
|
efd013d6d8 | ||
|
|
e6f68faf99 | ||
|
|
816ae098ea | ||
|
|
e295da1266 | ||
|
|
8e6b38d895 | ||
|
|
296545a2c7 | ||
|
|
61dbfbb6ce | ||
|
|
907f24d0e1 | ||
|
|
0f691c4b5b | ||
|
|
7c190070b4 | ||
|
|
e1b3ee8a79 | ||
|
|
cd4178a015 | ||
|
|
b954847fca | ||
|
|
a00bf1b3cf | ||
|
|
01f77cb192 | ||
|
|
eb53c05a35 | ||
|
|
32df9733d1 | ||
|
|
5aaef91d4a | ||
|
|
a45ee83181 | ||
|
|
38fae9e65d | ||
|
|
1a11c934d2 | ||
|
|
9b5adea97f | ||
|
|
95d96eeeb1 | ||
|
|
ff45d1b547 | ||
|
|
cb2476a0ca | ||
|
|
6b7167651f | ||
|
|
9eb0b53954 | ||
|
|
1fe3bf8298 | ||
|
|
43692793e3 | ||
|
|
95c192475d | ||
|
|
197b832afa | ||
|
|
bdb1d19a69 | ||
|
|
b726bf5961 | ||
|
|
faa7eb3b99 | ||
|
|
c4cef07f1b | ||
|
|
892e9d3047 | ||
|
|
4f2b0946d1 | ||
|
|
3b7ed677de | ||
|
|
56ac686292 | ||
|
|
3fc713ed92 | ||
|
|
343507afb1 | ||
|
|
aa432be4b9 | ||
|
|
8cd9f04feb | ||
|
|
8a4ee7e90b | ||
|
|
af6248d8b8 | ||
|
|
0d7b261236 | ||
|
|
6576fa05af | ||
|
|
c4e19b77e8 | ||
|
|
fa813f7478 | ||
|
|
d6b79e2fd0 | ||
|
|
a52e4d7d02 | ||
|
|
f9bd66779f | ||
|
|
88a2776db5 | ||
|
|
e87d5df1ae | ||
|
|
7b79238b65 | ||
|
|
4939fc3892 | ||
|
|
3a2bccc845 | ||
|
|
cf8adc971e | ||
|
|
30a24f2d3d | ||
|
|
d26a848074 | ||
|
|
249e796dfc | ||
|
|
162c9170dd | ||
|
|
8366e10ffc | ||
|
|
0babce57f7 | ||
|
|
812dbfb483 | ||
|
|
550bf895e9 | ||
|
|
886af59105 | ||
|
|
e3b1e3dfa6 | ||
|
|
f5177c761f | ||
|
|
97fd2a6383 | ||
|
|
7c919deccc | ||
|
|
9a33a723ba | ||
|
|
81f6b33d93 | ||
|
|
bc8af90e8c | ||
|
|
8100ec2cd1 | ||
|
|
7360db39e6 | ||
|
|
2a79af1c54 | ||
|
|
6faddd7c55 | ||
|
|
5009b5326b | ||
|
|
2d05eaeb28 | ||
|
|
bde7d1055f | ||
|
|
ca3db54788 | ||
|
|
20699df843 | ||
|
|
e9c1face6c | ||
|
|
fcc47fa5f1 | ||
|
|
56887f6cb8 | ||
|
|
a683d4aba9 | ||
|
|
53a4bd8a69 | ||
|
|
3e86869616 | ||
|
|
b2973eaaeb | ||
|
|
5b197bff41 | ||
|
|
281fbdddc2 | ||
|
|
2ae15b2d50 | ||
|
|
00a0587257 | ||
|
|
5091dfc1ed | ||
|
|
edc764e91b | ||
|
|
8ff59b2b40 | ||
|
|
1ba452226f | ||
|
|
972c895c35 | ||
|
|
a73b26f601 | ||
|
|
098da83483 | ||
|
|
54db29b8f0 | ||
|
|
d755c62f92 | ||
|
|
fda098461b | ||
|
|
0fee896841 | ||
|
|
10d46b9c8f | ||
|
|
774a6aa296 | ||
|
|
3070ed9021 | ||
|
|
e42dc9192e | ||
|
|
5647fa427c | ||
|
|
0d65acec0c | ||
|
|
19dd5a61cd | ||
|
|
5aca977be8 | ||
|
|
1147e5b05a | ||
|
|
6e6622abb9 | ||
|
|
e532877940 | ||
|
|
a791a2cf2d | ||
|
|
af0c9ac01d | ||
|
|
02ec8154e5 | ||
|
|
630b732cb3 | ||
|
|
62d5480984 | ||
|
|
842958651f | ||
|
|
88acd932f6 | ||
|
|
3a276b0cbe | ||
|
|
8cf0f86d39 | ||
|
|
27ad170712 | ||
|
|
936973d145 | ||
|
|
21c047ab49 | ||
|
|
71c7eed91c | ||
|
|
5d85fdb2c5 | ||
|
|
c40c4cae14 | ||
|
|
590e2617ee | ||
|
|
8dfa980cba | ||
|
|
f62fbd2c85 | ||
|
|
57461fba8c | ||
|
|
0e20000171 | ||
|
|
472c06e903 | ||
|
|
157e0633e7 | ||
|
|
23d4cf483c | ||
|
|
345fd73faf | ||
|
|
8fc75de327 | ||
|
|
9dd50d9902 | ||
|
|
5d660258e7 | ||
|
|
d379d110ee | ||
|
|
21f847eda5 | ||
|
|
99765ed855 | ||
|
|
def2f7bd0e | ||
|
|
f89caa127b | ||
|
|
8f1214531e | ||
|
|
11f329bd40 | ||
|
|
2b2a898e0b | ||
|
|
34df1c94d5 | ||
|
|
7947aba68c | ||
|
|
847e471db6 | ||
|
|
4e7e41ba77 | ||
|
|
1d725ca51d | ||
|
|
0e77246ba9 | ||
|
|
2073cf3775 | ||
|
|
26adaad438 | ||
|
|
bb98ca3c80 | ||
|
|
1f2dca0eaa | ||
|
|
f6e404c20a | ||
|
|
7383b64b3a | ||
|
|
0e665c3998 | ||
|
|
3c69f77c67 | ||
|
|
533cda90ce | ||
|
|
c70a9335de | ||
|
|
05c5c39a7c | ||
|
|
a6254f2bd4 | ||
|
|
2ead115116 | ||
|
|
1f0142ce19 | ||
|
|
43afd72bee | ||
|
|
bac3be7c46 | ||
|
|
f6aedb62c0 | ||
|
|
a08c8c851a | ||
|
|
5c06e0814c | ||
|
|
aab91b8d8f | ||
|
|
a400336398 | ||
|
|
fa3536d202 | ||
|
|
40123b3805 | ||
|
|
49815e3841 | ||
|
|
c1af07ce84 | ||
|
|
e61ba052b3 | ||
|
|
560358dc93 | ||
|
|
5c1c904877 | ||
|
|
c27061dae7 | ||
|
|
a657ee9a9c | ||
|
|
8a66c85e90 | ||
|
|
1b8a2e8fdd | ||
|
|
0460e9dcce | ||
|
|
f548da33e8 | ||
|
|
d86f9b9c3f | ||
|
|
4b31723433 | ||
|
|
bfb6b1b8a8 | ||
|
|
258459ed54 | ||
|
|
fca5aa6fcc | ||
|
|
e1951b6f28 | ||
|
|
730f7e2e21 | ||
|
|
a9b3c47c8e | ||
|
|
3f16a836a4 | ||
|
|
269f6b2e2d | ||
|
|
04dec2a359 | ||
|
|
72c73cdc8b | ||
|
|
4d02bfa3a6 | ||
|
|
8f65feafc0 | ||
|
|
0d590d9991 | ||
|
|
7ccb8d6ef3 | ||
|
|
e3c6ba37dd | ||
|
|
967476eaee | ||
|
|
9790b94c92 | ||
|
|
a4ea345b04 | ||
|
|
24a24f013d | ||
|
|
ff4b3fb5b4 | ||
|
|
cba752d588 | ||
|
|
6e801b0bd1 | ||
|
|
1b166928c7 | ||
|
|
b8cf9130f8 | ||
|
|
21441c09bd | ||
|
|
4ea6e051ee | ||
|
|
f3bb024fd6 | ||
|
|
6e56a114be | ||
|
|
1d2e4ef747 | ||
|
|
a92049e3e7 | ||
|
|
e8115cea45 | ||
|
|
6a14f7a976 | ||
|
|
c6c770a1ac | ||
|
|
992a8f83b7 | ||
|
|
029022b0f1 | ||
|
|
a06faa6327 | ||
|
|
e7899c6618 | ||
|
|
0f428c5619 | ||
|
|
6d4832a998 | ||
|
|
af70f9ac6d | ||
|
|
54de13abac | ||
|
|
0850bc5147 | ||
|
|
4032145adc | ||
|
|
843d2e3137 | ||
|
|
ad573b9027 | ||
|
|
1c80dfab24 | ||
|
|
eb73980853 | ||
|
|
ac0e54b4c6 | ||
|
|
d719b095dc | ||
|
|
1995e34d6a | ||
|
|
7c14abf2c7 | ||
|
|
f3eb0b5b8c | ||
|
|
7b78d623f7 | ||
|
|
995353e46a | ||
|
|
24110ce90c | ||
|
|
b71c4e613f | ||
|
|
90ffed1f93 | ||
|
|
99a3bf8f62 | ||
|
|
b54c347424 | ||
|
|
c146c9be18 | ||
|
|
6da70c5815 | ||
|
|
6f01687aae | ||
|
|
644f978c18 | ||
|
|
5bd4022fec | ||
|
|
7a6045a3c7 | ||
|
|
73a847ef89 | ||
|
|
ee221d2de0 | ||
|
|
874c96ac1d | ||
|
|
6cbffd50d0 | ||
|
|
b95510ddf4 | ||
|
|
6356b4d516 | ||
|
|
9385fd72c0 | ||
|
|
a2466c8851 | ||
|
|
c3c389d542 | ||
|
|
ec84bef24a | ||
|
|
725184b04e | ||
|
|
290883d94a | ||
|
|
23089fd281 | ||
|
|
792762c42c | ||
|
|
40b840f294 | ||
|
|
6affd45d84 | ||
|
|
47833e0ab9 | ||
|
|
e5c614e1df | ||
|
|
1b1adebe8f | ||
|
|
6d436a3f85 | ||
|
|
994d9bc82b | ||
|
|
9d35ae649e | ||
|
|
cea8ed9702 | ||
|
|
4b6833aec1 | ||
|
|
3894603fef | ||
|
|
c9d668c584 | ||
|
|
083cadc7e9 | ||
|
|
80b74a6c64 | ||
|
|
7c14d11eb9 | ||
|
|
535af525d6 | ||
|
|
09be5cad5b | ||
|
|
1aeca97337 | ||
|
|
dfc3de8b7d | ||
|
|
c2c8fe47f0 | ||
|
|
19ef3de57e | ||
|
|
b345b36620 | ||
|
|
dff4c48ede | ||
|
|
0353b853cb | ||
|
|
b885f33a56 | ||
|
|
71303e04e7 | ||
|
|
63cf15bb9f | ||
|
|
e208575b2d | ||
|
|
13e6c8e97a | ||
|
|
ae21d71e94 | ||
|
|
8e0e22f76b | ||
|
|
5675d5037f | ||
|
|
91f0f1f5ef | ||
|
|
a23c6052c8 | ||
|
|
30d72ee43c | ||
|
|
0e3cc2cf1f | ||
|
|
2448f80375 | ||
|
|
114a1b8792 | ||
|
|
0522990358 | ||
|
|
0930e5e99f | ||
|
|
3b977bcdc8 | ||
|
|
1518b733eb | ||
|
|
ada88b63f5 | ||
|
|
2391ef7214 | ||
|
|
ab4c62332e | ||
|
|
d02eb8d00b | ||
|
|
9278097f86 | ||
|
|
cc87075d63 | ||
|
|
8a1a603fdb | ||
|
|
f38540b12a | ||
|
|
200654067a | ||
|
|
f2c43a4a27 | ||
|
|
b55e2165be | ||
|
|
9b51987521 | ||
|
|
2feafa3db9 | ||
|
|
a558830f8f | ||
|
|
51c9464dfc | ||
|
|
925b5d0025 | ||
|
|
63e8f1b55b | ||
|
|
994b3bd693 | ||
|
|
21700a5106 | ||
|
|
24420947d9 | ||
|
|
3bdbe89614 | ||
|
|
78711524b7 | ||
|
|
e7c64fb115 | ||
|
|
99ffb3d533 | ||
|
|
07bb12d97d | ||
|
|
ad2fdaa823 | ||
|
|
be9816b3d9 | ||
|
|
e3802531f1 | ||
|
|
e7cc49cbdf | ||
|
|
3996770d0b | ||
|
|
4e85b74790 | ||
|
|
ec458dcdeb | ||
|
|
583157f710 | ||
|
|
b9d4fa890b | ||
|
|
60e34baef2 | ||
|
|
136b8e0cad | ||
|
|
2dcbb3b4f3 | ||
|
|
b1fafcaca6 | ||
|
|
5f8741a69d | ||
|
|
43bbb56198 | ||
|
|
d7ae42b0f8 | ||
|
|
fbbb8a6144 | ||
|
|
e182f03c1e | ||
|
|
7d371863e5 | ||
|
|
fbea4dc660 | ||
|
|
780d2b04cb | ||
|
|
4f1c74a46e | ||
|
|
f8c2189307 | ||
|
|
44756260ae | ||
|
|
522de4f59e | ||
|
|
be22271265 | ||
|
|
4ecc03c039 | ||
|
|
2ca0994cf7 | ||
|
|
90aff0c444 | ||
|
|
72224104d3 | ||
|
|
33d568611d | ||
|
|
02e62ebbc8 | ||
|
|
9cad56861a | ||
|
|
3d29f91466 | ||
|
|
3ff98bd209 | ||
|
|
b9cc42a72c | ||
|
|
f76b260eff | ||
|
|
774922c680 | ||
|
|
17b879b91e | ||
|
|
f11b0df121 | ||
|
|
6b03f93d4f | ||
|
|
697fab820a | ||
|
|
58379bfb55 | ||
|
|
1a2cc27e01 | ||
|
|
e778c34e78 | ||
|
|
5bd3da1c59 | ||
|
|
b2ae5950ba | ||
|
|
0267655dad | ||
|
|
08304c0867 | ||
|
|
fd664a27b5 | ||
|
|
580fede347 | ||
|
|
54f6b9e162 | ||
|
|
790252805d | ||
|
|
e8e6cf0173 | ||
|
|
d9620239d2 | ||
|
|
f71fc77b7c | ||
|
|
ee8cc35201 | ||
|
|
765597fa78 | ||
|
|
f0161c37b0 | ||
|
|
188bb2e7ad | ||
|
|
0f2d2fcff6 | ||
|
|
f8e02c7825 | ||
|
|
0f2fdfe23a | ||
|
|
c5af85ecad | ||
|
|
e84137c8ae | ||
|
|
76e8beeeb9 | ||
|
|
0e7e6f6e4b | ||
|
|
18eb563058 | ||
|
|
e16438bb86 | ||
|
|
b73fbbaf64 |
No files matched your search
@@ -32,6 +32,7 @@ build/
|
||||
ldb
|
||||
manifest_dump
|
||||
sst_dump
|
||||
column_aware_encoding_exp
|
||||
util/build_version.cc
|
||||
build_tools/VALGRIND_LOGS/
|
||||
coverage/COVERAGE_REPORT
|
||||
@@ -64,3 +65,7 @@ java/javadoc
|
||||
scan_build_report/
|
||||
t
|
||||
LOG
|
||||
|
||||
db_logs/
|
||||
tp2/
|
||||
fbcode/
|
||||
+24
-26
@@ -1,41 +1,39 @@
|
||||
sudo: false
|
||||
language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
compiler:
|
||||
- clang
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env: COMPILER=clang++-3.6
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6']
|
||||
packages: ['clang-3.6', 'clang-format-3.6', 'zlib1g-dev', 'libbz2-dev', 'libsnappy-dev', 'curl']
|
||||
- os: osx
|
||||
compiler: clang
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.6']
|
||||
packages: ['clang-3.6' , 'zlib1g-dev', 'libbz2-dev', 'libsnappy-dev', 'curl', 'gflags']
|
||||
env:
|
||||
# Run all tests before db_block_cache_test (db_test, db_test2)
|
||||
- JOB_NAME=unittests ROCKSDBTESTS_END=db_block_cache_test
|
||||
# Run all tests starting from db_block_cache_test (db_block_cache_test, db_iter_test, ...)
|
||||
- JOB_NAME=unittests ROCKSDBTESTS_START=db_block_cache_test
|
||||
# Run java tests
|
||||
- JOB_NAME=java_test
|
||||
# Build ROCKSDB_LITE
|
||||
- JOB_NAME=lite_build
|
||||
|
||||
install:
|
||||
# Build gflags
|
||||
# TODO(noetzli): Remove when gflags available through Travis
|
||||
- pushd /tmp/ && curl -L https://github.com/gflags/gflags/archive/v2.1.2.tar.gz -o gflags.tar.gz && tar xfz gflags.tar.gz && cd gflags-2.1.2 && cmake . && make && popd
|
||||
# Download clang-format-diff.py to check source code formatting
|
||||
- pushd /tmp/ && curl -L http://llvm.org/svn/llvm-project/cfe/trunk/tools/clang-format/clang-format-diff.py -o clang-format-diff.py && chmod +x clang-format-diff.py && popd
|
||||
|
||||
before_script:
|
||||
# Add gflags to include/library paths
|
||||
# TODO(noetzli): Remove when gflags available through Travis
|
||||
- export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/tmp/gflags-2.1.2/lib"
|
||||
- export LIBRARY_PATH="$LIBRARY_PATH:/tmp/gflags-2.1.2/lib"
|
||||
- export CPLUS_INCLUDE_PATH="$CPLUS_INCLUDE_PATH:/tmp/gflags-2.1.2/include"
|
||||
- if [ -n "${COMPILER}" ]; then CXX=${COMPILER}; fi
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'osx' ]]; then brew install gflags snappy; fi
|
||||
- ulimit -n 2000 || true
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'linux' ]]; then CXX=clang++-3.6; fi
|
||||
# Limit the maximum number of open file descriptors to 8192
|
||||
- ulimit -n 8192 || true
|
||||
|
||||
# Lousy hack to disable use and testing of fallocate, which doesn't behave quite
|
||||
# as EnvPosixTest::AllocateTest expects within the Travis OpenVZ environment.
|
||||
script:
|
||||
- if [[ "${TRAVIS_OS_NAME}" == 'linux' ]]; then OPT=-DTRAVIS CLANG_FORMAT_DIFF=/tmp/clang-format-diff.py make format || true; fi
|
||||
- OPT=-DTRAVIS V=1 make -j4 check && OPT=-DTRAVIS V=1 make clean jclean rocksdbjava jtest && make clean && OPT="-DTRAVIS -DROCKSDB_LITE" V=1 make -j4 static_lib
|
||||
|
||||
- if [[ "${JOB_NAME}" == 'unittests' ]]; then OPT=-DTRAVIS V=1 make -j4 check_some; fi
|
||||
- if [[ "${JOB_NAME}" == 'java_test' ]]; then OPT=-DTRAVIS V=1 make clean jclean rocksdbjava jtest; fi
|
||||
- if [[ "${JOB_NAME}" == 'lite_build' ]]; then OPT="-DTRAVIS -DROCKSDB_LITE" V=1 make -j4 static_lib; fi
|
||||
notifications:
|
||||
email:
|
||||
- leveldb@fb.com
|
||||
|
||||
+55
-12
@@ -32,8 +32,8 @@ project(rocksdb)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
execute_process(COMMAND powershell -Command "Get-Date -format MM_dd_yyyy" OUTPUT_VARIABLE DATE)
|
||||
execute_process(COMMAND powershell -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})
|
||||
string(REGEX REPLACE "(..)_(..)_..(..).*" "\\1/\\2/\\3" DATE "${DATE}")
|
||||
string(REGEX REPLACE "(..):(.....).*" " \\1:\\2" TIME "${TIME}")
|
||||
string(CONCAT GIT_DATE_TIME ${DATE} ${TIME})
|
||||
|
||||
find_package(Git)
|
||||
@@ -44,7 +44,7 @@ else()
|
||||
set(GIT_SHA 0)
|
||||
endif()
|
||||
|
||||
string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA ${GIT_SHA})
|
||||
string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA "${GIT_SHA}")
|
||||
|
||||
set(BUILD_VERSION_CC ${CMAKE_CURRENT_SOURCE_DIR}/util/build_version.cc)
|
||||
|
||||
@@ -82,7 +82,7 @@ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oxt /Zp8 /Gm- /Gy /MD")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG")
|
||||
|
||||
add_definitions(-DWIN32 -DOS_WIN -D_MBCS -DWIN64)
|
||||
add_definitions(-DWIN32 -DOS_WIN -D_MBCS -DWIN64 -DNOMINMAX)
|
||||
|
||||
include_directories(${PROJECT_SOURCE_DIR})
|
||||
include_directories(${PROJECT_SOURCE_DIR}/include)
|
||||
@@ -90,7 +90,7 @@ include_directories(${PROJECT_SOURCE_DIR}/third-party/gtest-1.7.0/fused-src)
|
||||
|
||||
set(ROCKSDB_LIBS rocksdblib${ARTIFACT_SUFFIX})
|
||||
set(THIRDPARTY_LIBS ${THIRDPARTY_LIBS} gtest)
|
||||
set(SYSTEM_LIBS Shlwapi.lib Rpcrt4.lib)
|
||||
set(SYSTEM_LIBS ${SYSTEM_LIBS} Shlwapi.lib Rpcrt4.lib)
|
||||
|
||||
set(LIBS ${ROCKSDB_LIBS} ${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
|
||||
|
||||
@@ -113,6 +113,7 @@ set(SOURCES
|
||||
db/db_impl.cc
|
||||
db/db_impl_debug.cc
|
||||
db/db_impl_experimental.cc
|
||||
db/db_impl_add_file.cc
|
||||
db/db_impl_readonly.cc
|
||||
db/db_info_dumper.cc
|
||||
db/db_iter.cc
|
||||
@@ -133,7 +134,6 @@ set(SOURCES
|
||||
db/merge_helper.cc
|
||||
db/merge_operator.cc
|
||||
db/repair.cc
|
||||
db/slice.cc
|
||||
db/snapshot_impl.cc
|
||||
db/table_cache.cc
|
||||
db/table_properties_collector.cc
|
||||
@@ -153,9 +153,12 @@ set(SOURCES
|
||||
memtable/skiplistrep.cc
|
||||
memtable/vectorrep.cc
|
||||
port/stack_trace.cc
|
||||
port/win/io_win.cc
|
||||
port/win/env_win.cc
|
||||
port/win/env_default.cc
|
||||
port/win/port_win.cc
|
||||
port/win/win_logger.cc
|
||||
port/win/xpress_win.cc
|
||||
table/adaptive_table_factory.cc
|
||||
table/block.cc
|
||||
table/block_based_filter_block.cc
|
||||
@@ -163,7 +166,6 @@ set(SOURCES
|
||||
table/block_based_table_factory.cc
|
||||
table/block_based_table_reader.cc
|
||||
table/block_builder.cc
|
||||
table/block_hash_index.cc
|
||||
table/block_prefix_index.cc
|
||||
table/bloom_block.cc
|
||||
table/cuckoo_table_builder.cc
|
||||
@@ -182,6 +184,7 @@ set(SOURCES
|
||||
table/plain_table_index.cc
|
||||
table/plain_table_key_coding.cc
|
||||
table/plain_table_reader.cc
|
||||
table/persistent_cache_helper.cc
|
||||
table/table_properties.cc
|
||||
table/two_level_iterator.cc
|
||||
tools/sst_dump_tool.cc
|
||||
@@ -190,7 +193,8 @@ set(SOURCES
|
||||
util/arena.cc
|
||||
util/bloom.cc
|
||||
util/build_version.cc
|
||||
util/cache.cc
|
||||
util/cf_options.cc
|
||||
util/clock_cache.cc
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
util/comparator.cc
|
||||
@@ -199,6 +203,7 @@ set(SOURCES
|
||||
util/delete_scheduler.cc
|
||||
util/dynamic_bloom.cc
|
||||
util/env.cc
|
||||
util/env_chroot.cc
|
||||
util/env_hdfs.cc
|
||||
util/event_logger.cc
|
||||
util/file_util.cc
|
||||
@@ -207,17 +212,17 @@ set(SOURCES
|
||||
util/filter_policy.cc
|
||||
util/hash.cc
|
||||
util/histogram.cc
|
||||
util/histogram_windowing.cc
|
||||
util/instrumented_mutex.cc
|
||||
util/iostats_context.cc
|
||||
util/lru_cache.cc
|
||||
tools/ldb_cmd.cc
|
||||
tools/ldb_tool.cc
|
||||
util/logging.cc
|
||||
util/log_buffer.cc
|
||||
util/memenv.cc
|
||||
util/murmurhash.cc
|
||||
util/mutable_cf_options.cc
|
||||
util/options.cc
|
||||
util/options_builder.cc
|
||||
util/options_helper.cc
|
||||
util/options_parser.cc
|
||||
util/options_sanity_check.cc
|
||||
@@ -225,6 +230,7 @@ set(SOURCES
|
||||
util/perf_level.cc
|
||||
util/random.cc
|
||||
util/rate_limiter.cc
|
||||
util/sharded_cache.cc
|
||||
util/slice.cc
|
||||
util/statistics.cc
|
||||
util/status.cc
|
||||
@@ -234,19 +240,24 @@ set(SOURCES
|
||||
util/testharness.cc
|
||||
util/testutil.cc
|
||||
util/thread_local.cc
|
||||
util/threadpool_imp.cc
|
||||
util/thread_status_impl.cc
|
||||
util/thread_status_updater.cc
|
||||
util/thread_status_util.cc
|
||||
util/thread_status_util_debug.cc
|
||||
util/transaction_test_util.cc
|
||||
util/xfunc.cc
|
||||
util/xxhash.cc
|
||||
utilities/backupable/backupable_db.cc
|
||||
utilities/blob_db/blob_db.cc
|
||||
utilities/checkpoint/checkpoint.cc
|
||||
utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
|
||||
utilities/date_tiered/date_tiered_db_impl.cc
|
||||
utilities/document/document_db.cc
|
||||
utilities/document/json_document.cc
|
||||
utilities/document/json_document_builder.cc
|
||||
utilities/env_mirror.cc
|
||||
utilities/env_registry.cc
|
||||
utilities/flashcache/flashcache.cc
|
||||
utilities/geodb/geodb_impl.cc
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
@@ -254,9 +265,17 @@ set(SOURCES
|
||||
utilities/merge_operators/string_append/stringappend.cc
|
||||
utilities/merge_operators/string_append/stringappend2.cc
|
||||
utilities/merge_operators/put.cc
|
||||
utilities/merge_operators/max.cc
|
||||
utilities/merge_operators/uint64add.cc
|
||||
utilities/option_change_migration/option_change_migration.cc
|
||||
utilities/options/options_util.cc
|
||||
utilities/persistent_cache/block_cache_tier.cc
|
||||
utilities/persistent_cache/block_cache_tier_file.cc
|
||||
utilities/persistent_cache/block_cache_tier_metadata.cc
|
||||
utilities/persistent_cache/persistent_cache_tier.cc
|
||||
utilities/persistent_cache/volatile_tier_impl.cc
|
||||
utilities/redis/redis_lists.cc
|
||||
utilities/simulator_cache/sim_cache.cc
|
||||
utilities/spatialdb/spatial_db.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
utilities/transactions/optimistic_transaction_impl.cc
|
||||
@@ -270,6 +289,9 @@ set(SOURCES
|
||||
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
|
||||
utilities/col_buf_encoder.cc
|
||||
utilities/col_buf_decoder.cc
|
||||
utilities/column_aware_encoding_util.cc
|
||||
)
|
||||
|
||||
# For test util library that is build only in DEBUG mode
|
||||
@@ -278,6 +300,7 @@ set(TESTUTIL_SOURCE
|
||||
db/db_test_util.cc
|
||||
table/mock_table.cc
|
||||
util/mock_env.cc
|
||||
util/fault_injection_test_env.cc
|
||||
util/thread_status_updater_debug.cc
|
||||
)
|
||||
|
||||
@@ -308,10 +331,13 @@ set(APPS
|
||||
tools/db_stress.cc
|
||||
tools/write_stress.cc
|
||||
tools/db_repl_stress.cc
|
||||
tools/ldb.cc
|
||||
tools/sst_dump.cc
|
||||
tools/dump/rocksdb_dump.cc
|
||||
tools/dump/rocksdb_undump.cc
|
||||
util/cache_bench.cc
|
||||
utilities/persistent_cache/hash_table_bench.cc
|
||||
utilities/column_aware_encoding_exp.cc
|
||||
)
|
||||
|
||||
set(C_TESTS db/c_test.c)
|
||||
@@ -333,12 +359,18 @@ set(TESTS
|
||||
db/db_inplace_update_test.cc
|
||||
db/db_iter_test.cc
|
||||
db/db_log_iter_test.cc
|
||||
db/db_options_test.cc
|
||||
db/db_properties_test.cc
|
||||
db/db_table_properties_test.cc
|
||||
db/db_tailing_iter_test.cc
|
||||
db/db_test.cc
|
||||
db/db_test2.cc
|
||||
db/db_block_cache_test.cc
|
||||
db/db_bloom_filter_test.cc
|
||||
db/db_flush_test.cc
|
||||
db/db_iterator_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/external_sst_file_test.cc
|
||||
db/db_universal_compaction_test.cc
|
||||
db/db_wal_test.cc
|
||||
db/dbformat_test.cc
|
||||
@@ -358,6 +390,7 @@ set(TESTS
|
||||
db/perf_context_test.cc
|
||||
db/plain_table_db_test.cc
|
||||
db/prefix_test.cc
|
||||
db/repair_test.cc
|
||||
db/skiplist_test.cc
|
||||
db/table_properties_collector_test.cc
|
||||
db/version_builder_test.cc
|
||||
@@ -367,8 +400,8 @@ set(TESTS
|
||||
db/write_batch_test.cc
|
||||
db/write_callback_test.cc
|
||||
db/write_controller_test.cc
|
||||
db/db_io_failure_test.cc
|
||||
table/block_based_filter_block_test.cc
|
||||
table/block_hash_index_test.cc
|
||||
table/block_test.cc
|
||||
table/cuckoo_table_builder_test.cc
|
||||
table/cuckoo_table_reader_test.cc
|
||||
@@ -387,6 +420,7 @@ set(TESTS
|
||||
util/crc32c_test.cc
|
||||
util/delete_scheduler_test.cc
|
||||
util/dynamic_bloom_test.cc
|
||||
util/env_basic_test.cc
|
||||
util/env_test.cc
|
||||
util/event_logger_test.cc
|
||||
util/filelock_test.cc
|
||||
@@ -394,21 +428,29 @@ set(TESTS
|
||||
util/heap_test.cc
|
||||
util/histogram_test.cc
|
||||
util/iostats_context_test.cc
|
||||
util/memenv_test.cc
|
||||
util/lru_cache_test.cc
|
||||
util/mock_env_test.cc
|
||||
util/options_settable_test.cc
|
||||
util/options_test.cc
|
||||
util/rate_limiter_test.cc
|
||||
util/slice_transform_test.cc
|
||||
util/statistics_test.cc
|
||||
util/thread_list_test.cc
|
||||
util/thread_local_test.cc
|
||||
utilities/backupable/backupable_db_test.cc
|
||||
utilities/blob_db/blob_db_test.cc
|
||||
utilities/checkpoint/checkpoint_test.cc
|
||||
utilities/date_tiered/date_tiered_test.cc
|
||||
utilities/document/document_db_test.cc
|
||||
utilities/document/json_document_test.cc
|
||||
utilities/env_registry_test.cc
|
||||
utilities/geodb/geodb_test.cc
|
||||
utilities/memory/memory_test.cc
|
||||
utilities/merge_operators/string_append/stringappend_test.cc
|
||||
utilities/option_change_migration/option_change_migration_test.cc
|
||||
utilities/options/options_util_test.cc
|
||||
utilities/persistent_cache/hash_table_test.cc
|
||||
utilities/persistent_cache/persistent_cache_test.cc
|
||||
utilities/redis/redis_lists_test.cc
|
||||
utilities/spatialdb/spatial_db_test.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
@@ -416,6 +458,7 @@ set(TESTS
|
||||
utilities/transactions/transaction_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
utilities/column_aware_encoding_test.cc
|
||||
)
|
||||
|
||||
set(EXES ${APPS})
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
# RocksDB default options change log
|
||||
## 4.8.0 (5/2/2016)
|
||||
* options.max_open_files changes from 5000 to -1. It improves performance, but users need to set file descriptor limit to be large enough and watch memory usage for index and bloom filters.
|
||||
* options.base_background_compactions changes from max_background_compactions to 1. When users set higher max_background_compactions but the write throughput is not high, the writes are less spiky to disks.
|
||||
* options.wal_recovery_mode changes from kTolerateCorruptedTailRecords to kPointInTimeRecovery. Avoid some false positive when file system or hardware reorder the writes for file data and metadata.
|
||||
|
||||
## 4.7.0 (4/8/2016)
|
||||
* options.write_buffer_size changes from 4MB to 64MB.
|
||||
* options.target_file_size_base changes from 2MB to 64MB.
|
||||
* options.max_bytes_for_level_base changes from 10MB to 256MB.
|
||||
* options.soft_pending_compaction_bytes_limit changes from 0 (disabled) to 64GB.
|
||||
* options.hard_pending_compaction_bytes_limit changes from 0 (disabled) to 256GB.
|
||||
* table_cache_numshardbits changes from 4 to 6.
|
||||
* max_file_opening_threads changes from 1 to 16.
|
||||
+68
-10
@@ -1,10 +1,68 @@
|
||||
# Rocksdb Change Log
|
||||
## 4.12.0 (9/12/2016)
|
||||
### Public API Change
|
||||
* CancelAllBackgroundWork() flushes all memtables for databases containing writes that have bypassed the WAL (writes issued with WriteOptions::disableWAL=true) before shutting down background threads.
|
||||
* Merge options source_compaction_factor, max_grandparent_overlap_bytes and expanded_compaction_factor into max_compaction_bytes.
|
||||
* Remove ImmutableCFOptions.
|
||||
* Add a compression type ZSTD, which can work with ZSTD 0.8.0 or up. Still keep ZSTDNotFinal for compatibility reasons.
|
||||
|
||||
### New Features
|
||||
* Introduce NewClockCache, which is based on CLOCK algorithm with better concurrent performance in some cases. It can be used to replace the default LRU-based block cache and table cache. To use it, RocksDB need to be linked with TBB lib.
|
||||
* Change ticker/histogram statistics implementations to accumulate data in thread-local storage, which improves CPU performance by reducing cache coherency costs. Callers of CreateDBStatistics do not need to change anything to use this feature.
|
||||
* Block cache mid-point insertion, where index and filter block are inserted into LRU block cache with higher priority. The feature can be enabled by setting BlockBasedTableOptions::cache_index_and_filter_blocks_with_high_priority to true and high_pri_pool_ratio > 0 when creating NewLRUCache.
|
||||
|
||||
## 4.11.0 (8/1/2016)
|
||||
### Public API Change
|
||||
* options.memtable_prefix_bloom_huge_page_tlb_size => memtable_huge_page_size. When it is set, RocksDB will try to allocate memory from huge page for memtable too, rather than just memtable bloom filter.
|
||||
|
||||
### New Features
|
||||
* A tool to migrate DB after options change. See include/rocksdb/utilities/option_change_migration.h.
|
||||
* Add ReadOptions.background_purge_on_iterator_cleanup. If true, we avoid file deletion when destorying iterators.
|
||||
|
||||
## 4.10.0 (7/5/2016)
|
||||
### Public API Change
|
||||
* options.memtable_prefix_bloom_bits changes to options.memtable_prefix_bloom_bits_ratio and deprecate options.memtable_prefix_bloom_probes
|
||||
* enum type CompressionType and PerfLevel changes from char to unsigned char. Value of all PerfLevel shift by one.
|
||||
* Deprecate options.filter_deletes.
|
||||
|
||||
### New Features
|
||||
* Add avoid_flush_during_recovery option.
|
||||
* Add a read option background_purge_on_iterator_cleanup to avoid deleting files in foreground when destroying iterators. Instead, a job is scheduled in high priority queue and would be executed in a separate background thread.
|
||||
* RepairDB support for column families. RepairDB now associates data with non-default column families using information embedded in the SST/WAL files (4.7 or later). For data written by 4.6 or earlier, RepairDB associates it with the default column family.
|
||||
* Add options.write_buffer_manager which allows users to control total memtable sizes across multiple DB instances.
|
||||
|
||||
## 4.9.0 (6/9/2016)
|
||||
### Public API changes
|
||||
* Add bottommost_compression option, This option can be used to set a specific compression algorithm for the bottommost level (Last level containing files in the DB).
|
||||
* Introduce CompactionJobInfo::compression, This field state the compression algorithm used to generate the output files of the compaction.
|
||||
* Deprecate BlockBaseTableOptions.hash_index_allow_collision=false
|
||||
* Deprecate options builder (GetOptions()).
|
||||
|
||||
### New Features
|
||||
* Introduce NewSimCache() in rocksdb/utilities/sim_cache.h. This function creates a block cache that is able to give simulation results (mainly hit rate) of simulating block behavior with a configurable cache size.
|
||||
|
||||
## 4.8.0 (5/2/2016)
|
||||
### Public API Change
|
||||
* Allow preset compression dictionary for improved compression of block-based tables. This is supported for zlib, zstd, and lz4. The compression dictionary's size is configurable via CompressionOptions::max_dict_bytes.
|
||||
* Delete deprecated classes for creating backups (BackupableDB) and restoring from backups (RestoreBackupableDB). Now, BackupEngine should be used for creating backups, and BackupEngineReadOnly should be used for restorations. For more details, see https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F
|
||||
* Expose estimate of per-level compression ratio via DB property: "rocksdb.compression-ratio-at-levelN".
|
||||
* Added EventListener::OnTableFileCreationStarted. EventListener::OnTableFileCreated will be called on failure case. User can check creation status via TableFileCreationInfo::status.
|
||||
|
||||
### New Features
|
||||
* Add ReadOptions::readahead_size. If non-zero, NewIterator will create a new table reader which performs reads of the given size.
|
||||
|
||||
## 4.7.0 (4/8/2016)
|
||||
### Public API Change
|
||||
* rename options compaction_measure_io_stats to report_bg_io_stats and include flush too.
|
||||
* Change some default options. Now default options will optimize for server-workloads. Also enable slowdown and full stop triggers for pending compaction bytes. These changes may cause sub-optimal performance or significant increase of resource usage. To avoid these risks, users can open existing RocksDB with options extracted from RocksDB option files. See https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File for how to use RocksDB option files. Or you can call Options.OldDefaults() to recover old defaults. DEFAULT_OPTIONS_HISTORY.md will track change history of default options.
|
||||
|
||||
## 4.6.0 (3/10/2016)
|
||||
### Public API Changes
|
||||
* Change default of BlockBasedTableOptions.format_version to 2. It means default DB created by 4.6 or up cannot be opened by RocksDB version 3.9 or earlier.
|
||||
* Added strict_capacity_limit option to NewLRUCache. If the flag is set to true, insert to cache will fail if no enough capacity can be free. Signiture of Cache::Insert() is updated accordingly.
|
||||
* Tickers [NUMBER_DB_NEXT, NUMBER_DB_PREV, NUMBER_DB_NEXT_FOUND, NUMBER_DB_PREV_FOUND, ITER_BYTES_READ] are not updated immediately. The are updated when the Iterator is deleted.
|
||||
* Added strict_capacity_limit option to NewLRUCache. If the flag is set to true, insert to cache will fail if no enough capacity can be free. Signature of Cache::Insert() is updated accordingly.
|
||||
* Tickers [NUMBER_DB_NEXT, NUMBER_DB_PREV, NUMBER_DB_NEXT_FOUND, NUMBER_DB_PREV_FOUND, ITER_BYTES_READ] are not updated immediately. The are updated when the Iterator is deleted.
|
||||
* Add monotonically increasing counter (DB property "rocksdb.current-super-version-number") that increments upon any change to the LSM tree.
|
||||
|
||||
### New Features
|
||||
* Add CompactionPri::kMinOverlappingRatio, a compaction picking mode friendly to write amplification.
|
||||
* Deprecate Iterator::IsKeyPinned() and replace it with Iterator::GetProperty() with prop_name="rocksdb.iterator.is.key.pinned"
|
||||
@@ -18,7 +76,7 @@
|
||||
### New Features
|
||||
* ldb tool now supports operations to non-default column families.
|
||||
* Add kPersistedTier to ReadTier. This option allows Get and MultiGet to read only the persited data and skip mem-tables if writes were done with disableWAL = true.
|
||||
* Add DBOptions::sst_file_manager. Use NewSstFileManager() in include/rocksdb/sst_file_manager.h to create a SstFileManager that can be used to track the total size of SST files and control the SST files deletion rate.
|
||||
* Add DBOptions::sst_file_manager. Use NewSstFileManager() in include/rocksdb/sst_file_manager.h to create a SstFileManager that can be used to track the total size of SST files and control the SST files deletion rate.
|
||||
|
||||
## 4.4.0 (1/14/2016)
|
||||
### Public API Changes
|
||||
@@ -117,8 +175,8 @@
|
||||
* options.hard_rate_limit is deprecated.
|
||||
* When options.soft_rate_limit or options.level0_slowdown_writes_trigger is triggered, the way to slow down writes is changed to: write rate to DB is limited to to options.delayed_write_rate.
|
||||
* DB::GetApproximateSizes() adds a parameter to allow the estimation to include data in mem table, with default to be not to include. It is now only supported in skip list mem table.
|
||||
* DB::CompactRange() now accept CompactRangeOptions instead of multiple paramters. CompactRangeOptions is defined in include/rocksdb/options.h.
|
||||
* CompactRange() will now skip bottommost level compaction for level based compaction if there is no compaction filter, bottommost_level_compaction is introduced in CompactRangeOptions to control when it's possbile to skip bottommost level compaction. This mean that if you want the compaction to produce a single file you need to set bottommost_level_compaction to BottommostLevelCompaction::kForce.
|
||||
* DB::CompactRange() now accept CompactRangeOptions instead of multiple parameters. CompactRangeOptions is defined in include/rocksdb/options.h.
|
||||
* CompactRange() will now skip bottommost level compaction for level based compaction if there is no compaction filter, bottommost_level_compaction is introduced in CompactRangeOptions to control when it's possible to skip bottommost level compaction. This mean that if you want the compaction to produce a single file you need to set bottommost_level_compaction to BottommostLevelCompaction::kForce.
|
||||
* Add Cache.GetPinnedUsage() to get the size of memory occupied by entries that are in use by the system.
|
||||
* DB:Open() will fail if the compression specified in Options is not linked with the binary. If you see this failure, recompile RocksDB with compression libraries present on your system. Also, previously our default compression was snappy. This behavior is now changed. Now, the default compression is snappy only if it's available on the system. If it isn't we change the default to kNoCompression.
|
||||
* We changed how we account for memory used in block cache. Previously, we only counted the sum of block sizes currently present in block cache. Now, we count the actual memory usage of the blocks. For example, a block of size 4.5KB will use 8KB memory with jemalloc. This might decrease your memory usage and possibly decrease performance. Increase block cache size if you see this happening after an upgrade.
|
||||
@@ -150,7 +208,7 @@
|
||||
Lower numbered levels will be placed earlier in the db_paths and higher
|
||||
numbered levels will be placed later in the db_paths vector.
|
||||
* Potentially big performance improvements if you're using RocksDB with lots of column families (100-1000)
|
||||
* Added BlockBasedTableOptions.format_version option, which allows user to specify which version of block based table he wants. As a general guidline, newer versions have more features, but might not be readable by older versions of RocksDB.
|
||||
* Added BlockBasedTableOptions.format_version option, which allows user to specify which version of block based table he wants. As a general guideline, newer versions have more features, but might not be readable by older versions of RocksDB.
|
||||
* Added new block based table format (version 2), which you can enable by setting BlockBasedTableOptions.format_version = 2. This format changes how we encode size information in compressed blocks and should help with memory allocations if you're using Zlib or BZip2 compressions.
|
||||
* MemEnv (env that stores data in memory) is now available in default library build. You can create it by calling NewMemEnv().
|
||||
* Add SliceTransform.SameResultWhenAppended() to help users determine it is safe to apply prefix bloom/hash.
|
||||
@@ -226,7 +284,7 @@
|
||||
|
||||
## 3.5.0 (9/3/2014)
|
||||
### New Features
|
||||
* Add include/utilities/write_batch_with_index.h, providing a utilitiy class to query data out of WriteBatch when building it.
|
||||
* Add include/utilities/write_batch_with_index.h, providing a utility class to query data out of WriteBatch when building it.
|
||||
* Move BlockBasedTable related options to BlockBasedTableOptions from Options. Change corresponding JNI interface. Options affected include:
|
||||
no_block_cache, block_cache, block_cache_compressed, block_size, block_size_deviation, block_restart_interval, filter_policy, whole_key_filtering. filter_policy is changed to shared_ptr from a raw pointer.
|
||||
* Remove deprecated options: disable_seek_compaction and db_stats_log_interval
|
||||
@@ -255,7 +313,7 @@
|
||||
### New Features
|
||||
* Added JSON API prototype.
|
||||
* HashLinklist reduces performance outlier caused by skewed bucket by switching data in the bucket from linked list to skip list. Add parameter threshold_use_skiplist in NewHashLinkListRepFactory().
|
||||
* RocksDB is now able to reclaim storage space more effectively during the compaction process. This is done by compensating the size of each deletion entry by the 2X average value size, which makes compaction to be triggerred by deletion entries more easily.
|
||||
* RocksDB is now able to reclaim storage space more effectively during the compaction process. This is done by compensating the size of each deletion entry by the 2X average value size, which makes compaction to be triggered by deletion entries more easily.
|
||||
* Add TimeOut API to write. Now WriteOptions have a variable called timeout_hint_us. With timeout_hint_us set to non-zero, any write associated with this timeout_hint_us may be aborted when it runs longer than the specified timeout_hint_us, and it is guaranteed that any write completes earlier than the specified time-out will not be aborted due to the time-out condition.
|
||||
* Add a rate_limiter option, which controls total throughput of flush and compaction. The throughput is specified in bytes/sec. Flush always has precedence over compaction when available bandwidth is constrained.
|
||||
|
||||
@@ -270,11 +328,11 @@
|
||||
2) It added some complexity to the important code-paths,
|
||||
3) None of our internal customers were really using it.
|
||||
Because of that, Options::disable_seek_compaction is now obsolete. It is still a parameter in Options, so it does not break the build, but it does not have any effect. We plan to completely remove it at some point, so we ask users to please remove this option from your code base.
|
||||
* Add two paramters to NewHashLinkListRepFactory() for logging on too many entries in a hash bucket when flushing.
|
||||
* Add two parameters to NewHashLinkListRepFactory() for logging on too many entries in a hash bucket when flushing.
|
||||
* Added new option BlockBasedTableOptions::hash_index_allow_collision. When enabled, prefix hash index for block-based table will not store prefix and allow hash collision, reducing memory consumption.
|
||||
|
||||
### New Features
|
||||
* PlainTable now supports a new key encoding: for keys of the same prefix, the prefix is only written once. It can be enabled through encoding_type paramter of NewPlainTableFactory()
|
||||
* PlainTable now supports a new key encoding: for keys of the same prefix, the prefix is only written once. It can be enabled through encoding_type parameter of NewPlainTableFactory()
|
||||
* Add AdaptiveTableFactory, which is used to convert from a DB of PlainTable to BlockBasedTabe, or vise versa. It can be created using NewAdaptiveTableFactory()
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
This is the list of all known third-party language bindings for RocksDB. If something is missing, please open a pull request to add it.
|
||||
|
||||
* Java - https://github.com/facebook/rocksdb/tree/master/java
|
||||
* Python - http://pyrocksdb.readthedocs.org/en/latest/
|
||||
* Perl - https://metacpan.org/pod/RocksDB
|
||||
* Node.js - https://npmjs.org/package/rocksdb
|
||||
* Go - https://github.com/tecbot/gorocksdb
|
||||
* Ruby - http://rubygems.org/gems/rocksdb-ruby
|
||||
* Haskell - https://hackage.haskell.org/package/rocksdb-haskell
|
||||
* PHP - https://github.com/Photonios/rocksdb-php
|
||||
* C# - https://github.com/warrenfalk/rocksdb-sharp
|
||||
* Rust - https://github.com/spacejam/rust-rocksdb
|
||||
@@ -15,7 +15,9 @@ ARFLAGS = rs
|
||||
|
||||
# Transform parallel LOG output into something more readable.
|
||||
perl_command = perl -n \
|
||||
-e '@a=split("\t",$$_,-1); $$t=$$a[8]; $$t =~ s,^\./,,;' \
|
||||
-e '@a=split("\t",$$_,-1); $$t=$$a[8];' \
|
||||
-e '$$t =~ /.*if\s\[\[\s"(.*?\.[\w\/]+)/ and $$t=$$1;' \
|
||||
-e '$$t =~ s,^\./,,;' \
|
||||
-e '$$t =~ s, >.*,,; chomp $$t;' \
|
||||
-e '$$t =~ /.*--gtest_filter=(.*?\.[\w\/]+)/ and $$t=$$1;' \
|
||||
-e 'printf "%7.3f %s %s\n", $$a[3], $$a[6] == 0 ? "PASS" : "FAIL", $$t'
|
||||
@@ -128,14 +130,27 @@ am__v_AR_ = $(am__v_AR_$(AM_DEFAULT_VERBOSITY))
|
||||
am__v_AR_0 = @echo " AR " $@;
|
||||
am__v_AR_1 =
|
||||
|
||||
AM_LINK = $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
ifdef ROCKSDB_USE_LIBRADOS
|
||||
LIB_SOURCES += utilities/env_librados.cc
|
||||
LDFLAGS += -lrados
|
||||
endif
|
||||
|
||||
AM_LINK = $(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
# detect what platform we're building on
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; "$(CURDIR)/build_tools/build_detect_platform" "$(CURDIR)/make_config.mk"))
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
CLEAN_FILES += make_config.mk
|
||||
|
||||
missing_make_config_paths := $(shell \
|
||||
grep "\/\S*" -o $(CURDIR)/make_config.mk | \
|
||||
while read path; \
|
||||
do [ -e $$path ] || echo $$path; \
|
||||
done | sort | uniq)
|
||||
|
||||
$(foreach path, $(missing_make_config_paths), \
|
||||
$(warning Warning: $(path) dont exist))
|
||||
|
||||
ifneq ($(PLATFORM), IOS)
|
||||
CFLAGS += -g
|
||||
CXXFLAGS += -g
|
||||
@@ -169,12 +184,22 @@ ifdef COMPILE_WITH_TSAN
|
||||
PLATFORM_CXXFLAGS += -fsanitize=thread -fPIC -DROCKSDB_TSAN_RUN
|
||||
# Turn off -pg when enabling TSAN testing, because that induces
|
||||
# a link failure. TODO: find the root cause
|
||||
pg =
|
||||
else
|
||||
pg = -pg
|
||||
PROFILING_FLAGS =
|
||||
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
|
||||
PLATFORM_CXXFLAGS += -fsanitize=undefined
|
||||
endif
|
||||
|
||||
ifndef DISABLE_JEMALLOC
|
||||
ifdef JEMALLOC
|
||||
PLATFORM_CXXFLAGS += "-DROCKSDB_JEMALLOC"
|
||||
PLATFORM_CCFLAGS += "-DROCKSDB_JEMALLOC"
|
||||
endif
|
||||
EXEC_LDFLAGS := $(JEMALLOC_LIB) $(EXEC_LDFLAGS)
|
||||
PLATFORM_CXXFLAGS += $(JEMALLOC_INCLUDE)
|
||||
PLATFORM_CCFLAGS += $(JEMALLOC_INCLUDE)
|
||||
@@ -226,8 +251,8 @@ util/build_version.cc: FORCE
|
||||
else mv -f $@-t $@; fi
|
||||
|
||||
LIBOBJECTS = $(LIB_SOURCES:.cc=.o)
|
||||
LIBOBJECTS += $(TOOL_SOURCES:.cc=.o)
|
||||
MOCKOBJECTS = $(MOCK_SOURCES:.cc=.o)
|
||||
LIBOBJECTS += $(TOOL_LIB_SOURCES:.cc=.o)
|
||||
MOCKOBJECTS = $(MOCK_LIB_SOURCES:.cc=.o)
|
||||
|
||||
GTEST = $(GTEST_DIR)/gtest/gtest-all.o
|
||||
TESTUTIL = ./util/testutil.o
|
||||
@@ -237,24 +262,32 @@ VALGRIND_VER := $(join $(VALGRIND_VER),valgrind)
|
||||
|
||||
VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
|
||||
BENCHTOOLOBJECTS = $(BENCH_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
BENCHTOOLOBJECTS = $(BENCH_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
|
||||
EXPOBJECTS = $(EXP_LIB_SOURCES:.cc=.o) $(LIBOBJECTS) $(TESTUTIL)
|
||||
|
||||
TESTS = \
|
||||
db_test \
|
||||
db_test2 \
|
||||
db_block_cache_test \
|
||||
db_bloom_filter_test \
|
||||
db_iter_test \
|
||||
db_log_iter_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_dynamic_level_test \
|
||||
db_flush_test \
|
||||
db_inplace_update_test \
|
||||
db_iterator_test \
|
||||
db_options_test \
|
||||
db_sst_test \
|
||||
external_sst_file_test \
|
||||
db_tailing_iter_test \
|
||||
db_universal_compaction_test \
|
||||
db_wal_test \
|
||||
db_io_failure_test \
|
||||
db_properties_test \
|
||||
db_table_properties_test \
|
||||
block_hash_index_test \
|
||||
autovector_test \
|
||||
column_family_test \
|
||||
table_properties_collector_test \
|
||||
@@ -271,6 +304,7 @@ TESTS = \
|
||||
crc32c_test \
|
||||
slice_transform_test \
|
||||
dbformat_test \
|
||||
env_basic_test \
|
||||
env_test \
|
||||
fault_injection_test \
|
||||
filelock_test \
|
||||
@@ -278,17 +312,18 @@ TESTS = \
|
||||
file_reader_writer_test \
|
||||
block_based_filter_block_test \
|
||||
full_filter_block_test \
|
||||
hash_table_test \
|
||||
histogram_test \
|
||||
inlineskiplist_test \
|
||||
log_test \
|
||||
manual_compaction_test \
|
||||
memenv_test \
|
||||
mock_env_test \
|
||||
memtable_list_test \
|
||||
merge_helper_test \
|
||||
memory_test \
|
||||
merge_test \
|
||||
merger_test \
|
||||
util_merge_operators_test \
|
||||
options_file_test \
|
||||
redis_test \
|
||||
reduce_levels_test \
|
||||
@@ -298,9 +333,12 @@ TESTS = \
|
||||
skiplist_test \
|
||||
stringappend_test \
|
||||
ttl_test \
|
||||
date_tiered_test \
|
||||
backupable_db_test \
|
||||
blob_db_test \
|
||||
document_db_test \
|
||||
json_document_test \
|
||||
sim_cache_test \
|
||||
spatial_db_test \
|
||||
version_edit_test \
|
||||
version_set_test \
|
||||
@@ -317,6 +355,7 @@ TESTS = \
|
||||
rate_limiter_test \
|
||||
delete_scheduler_test \
|
||||
options_test \
|
||||
options_settable_test \
|
||||
options_util_test \
|
||||
event_logger_test \
|
||||
cuckoo_table_builder_test \
|
||||
@@ -329,6 +368,7 @@ TESTS = \
|
||||
compaction_job_test \
|
||||
thread_list_test \
|
||||
sst_dump_test \
|
||||
column_aware_encoding_test \
|
||||
compact_files_test \
|
||||
perf_context_test \
|
||||
optimistic_transaction_test \
|
||||
@@ -336,11 +376,36 @@ TESTS = \
|
||||
heap_test \
|
||||
compact_on_deletion_collector_test \
|
||||
compaction_job_stats_test \
|
||||
option_change_migration_test \
|
||||
transaction_test \
|
||||
ldb_cmd_test \
|
||||
iostats_context_test
|
||||
iostats_context_test \
|
||||
persistent_cache_test \
|
||||
statistics_test \
|
||||
lru_cache_test \
|
||||
|
||||
SUBSET := $(shell echo $(TESTS) |sed s/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/)
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_sst_test \
|
||||
db_test \
|
||||
db_universal_compaction_test \
|
||||
db_wal_test \
|
||||
external_sst_file_test \
|
||||
fault_injection_test \
|
||||
inlineskiplist_test \
|
||||
manual_compaction_test \
|
||||
table_test
|
||||
|
||||
SUBSET := $(TESTS)
|
||||
ifdef ROCKSDBTESTS_START
|
||||
SUBSET := $(shell echo $(SUBSET) | sed 's/^.*$(ROCKSDBTESTS_START)/$(ROCKSDBTESTS_START)/')
|
||||
endif
|
||||
|
||||
ifdef ROCKSDBTESTS_END
|
||||
SUBSET := $(shell echo $(SUBSET) | sed 's/$(ROCKSDBTESTS_END).*//')
|
||||
endif
|
||||
|
||||
TOOLS = \
|
||||
sst_dump \
|
||||
@@ -352,8 +417,11 @@ TOOLS = \
|
||||
rocksdb_dump \
|
||||
rocksdb_undump
|
||||
|
||||
TEST_LIBS = \
|
||||
librocksdb_env_basic_test.a
|
||||
|
||||
# TODO: add back forward_iterator_bench, after making it build in all environemnts.
|
||||
BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench
|
||||
BENCHMARKS = db_bench table_reader_bench cache_bench memtablerep_bench column_aware_encoding_exp persistent_cache_bench
|
||||
|
||||
# if user didn't config LIBNAME, set the default
|
||||
ifeq ($(LIBNAME),)
|
||||
@@ -365,6 +433,7 @@ else
|
||||
endif
|
||||
endif
|
||||
LIBRARY = ${LIBNAME}.a
|
||||
TOOLS_LIBRARY = ${LIBNAME}_tools.a
|
||||
|
||||
ROCKSDB_MAJOR = $(shell egrep "ROCKSDB_MAJOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
ROCKSDB_MINOR = $(shell egrep "ROCKSDB_MINOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
@@ -408,7 +477,7 @@ $(SHARED3): $(SHARED4)
|
||||
endif
|
||||
|
||||
$(SHARED4):
|
||||
$(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(LIB_SOURCES) $(TOOL_SOURCES) \
|
||||
$(CXX) $(PLATFORM_SHARED_LDFLAGS)$(SHARED3) $(CXXFLAGS) $(PLATFORM_SHARED_CFLAGS) $(LIB_SOURCES) $(TOOL_LIB_SOURCES) \
|
||||
$(LDFLAGS) -o $@
|
||||
|
||||
endif # PLATFORM_SHARED_EXT
|
||||
@@ -416,10 +485,10 @@ endif # PLATFORM_SHARED_EXT
|
||||
.PHONY: blackbox_crash_test check clean coverage crash_test ldb_tests package \
|
||||
release tags valgrind_check whitebox_crash_test format static_lib shared_lib all \
|
||||
dbg rocksdbjavastatic rocksdbjava install install-static install-shared uninstall \
|
||||
analyze tools
|
||||
analyze tools tools_lib
|
||||
|
||||
|
||||
all: $(LIBRARY) $(BENCHMARKS) tools $(TESTS)
|
||||
all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS)
|
||||
|
||||
static_lib: $(LIBRARY)
|
||||
|
||||
@@ -427,6 +496,10 @@ shared_lib: $(SHARED)
|
||||
|
||||
tools: $(TOOLS)
|
||||
|
||||
tools_lib: $(TOOLS_LIBRARY)
|
||||
|
||||
test_libs: $(TEST_LIBS)
|
||||
|
||||
dbg: $(LIBRARY) $(BENCHMARKS) tools $(TESTS)
|
||||
|
||||
# creates static library and programs
|
||||
@@ -441,7 +514,26 @@ coverage:
|
||||
# Delete intermediate files
|
||||
find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
|
||||
# Extract the names of its tests by running db_test with --gtest_list_tests.
|
||||
ifneq (,$(filter check parallel_check,$(MAKECMDGOALS)),)
|
||||
# Use /dev/shm if it has the sticky bit set (otherwise, /tmp),
|
||||
# and create a randomly-named rocksdb.XXXX directory therein.
|
||||
# We'll use that directory in the "make check" rules.
|
||||
ifeq ($(TMPD),)
|
||||
TMPD := $(shell f=/dev/shm; test -k $$f || f=/tmp; \
|
||||
perl -le 'use File::Temp "tempdir";' \
|
||||
-e 'print tempdir("'$$f'/rocksdb.XXXX", CLEANUP => 0)')
|
||||
endif
|
||||
endif
|
||||
|
||||
# Run all tests in parallel, accumulating per-test logs in t/log-*.
|
||||
#
|
||||
# Each t/run-* file is a tiny generated bourne shell script that invokes one of
|
||||
# sub-tests. Why use a file for this? Because that makes the invocation of
|
||||
# parallel below simpler, which in turn makes the parsing of parallel's
|
||||
# LOG simpler (the latter is for live monitoring as parallel
|
||||
# tests run).
|
||||
#
|
||||
# Test names are extracted by running tests with --gtest_list_tests.
|
||||
# This filter removes the "#"-introduced comments, and expands to
|
||||
# fully-qualified names by changing input like this:
|
||||
#
|
||||
@@ -459,52 +551,33 @@ coverage:
|
||||
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/0
|
||||
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/1
|
||||
#
|
||||
test_names = \
|
||||
./db_test --gtest_list_tests \
|
||||
| perl -n \
|
||||
-e 's/ *\#.*//;' \
|
||||
-e '/^(\s*)(\S+)/; !$$1 and do {$$p=$$2; break};' \
|
||||
-e 'print qq! $$p$$2!'
|
||||
|
||||
ifneq (,$(filter check parallel_check,$(MAKECMDGOALS)),)
|
||||
# Use /dev/shm if it has the sticky bit set (otherwise, /tmp),
|
||||
# and create a randomly-named rocksdb.XXXX directory therein.
|
||||
# We'll use that directory in the "make check" rules.
|
||||
ifeq ($(TMPD),)
|
||||
TMPD := $(shell f=/dev/shm; test -k $$f || f=/tmp; \
|
||||
perl -le 'use File::Temp "tempdir";' \
|
||||
-e 'print tempdir("'$$f'/rocksdb.XXXX", CLEANUP => 0)')
|
||||
endif
|
||||
endif
|
||||
parallel_tests = $(patsubst %,parallel_%,$(PARALLEL_TEST))
|
||||
.PHONY: gen_parallel_tests $(parallel_tests)
|
||||
$(parallel_tests): $(PARALLEL_TEST)
|
||||
$(AM_V_at)TEST_BINARY=$(patsubst parallel_%,%,$@); \
|
||||
TEST_NAMES=` \
|
||||
./$$TEST_BINARY --gtest_list_tests \
|
||||
| perl -n \
|
||||
-e 's/ *\#.*//;' \
|
||||
-e '/^(\s*)(\S+)/; !$$1 and do {$$p=$$2; break};' \
|
||||
-e 'print qq! $$p$$2!'`; \
|
||||
for TEST_NAME in $$TEST_NAMES; do \
|
||||
TEST_SCRIPT=t/run-$$TEST_BINARY-$${TEST_NAME//\//-}; \
|
||||
echo " GEN " $$TEST_SCRIPT; \
|
||||
printf '%s\n' \
|
||||
'#!/bin/sh' \
|
||||
"d=\$(TMPD)$$TEST_SCRIPT" \
|
||||
'mkdir -p $$d' \
|
||||
"TEST_TMPDIR=\$$d $(DRIVER) ./$$TEST_BINARY --gtest_filter=$$TEST_NAME" \
|
||||
> $$TEST_SCRIPT; \
|
||||
chmod a=rx $$TEST_SCRIPT; \
|
||||
done
|
||||
|
||||
ifneq ($(T),)
|
||||
|
||||
# Run all tests in parallel, accumulating per-test logs in t/log-*.
|
||||
|
||||
# t_sanitized is each $(T) with "-" in place of each "/".
|
||||
t_sanitized = $(subst /,-,$(T))
|
||||
|
||||
# t_run is each sanitized name with a leading "t/".
|
||||
t_run = $(patsubst %,t/%,$(t_sanitized))
|
||||
|
||||
# Each t_run file is a tiny generated bourne shell script
|
||||
# that invokes one of db_tests's sub-tests. Why use a file
|
||||
# for this? Because that makes the invocation of parallel
|
||||
# below simpler, which in turn makes the parsing of parallel's
|
||||
# LOG simpler (the latter is for live monitoring as parallel
|
||||
# tests run).
|
||||
filter = --gtest_filter=$(subst -,/,$(@F))
|
||||
$(t_run): Makefile db_test
|
||||
$(AM_V_GEN)mkdir -p t
|
||||
$(AM_V_at)rm -f $@ $@-t
|
||||
$(AM_V_at)printf '%s\n' \
|
||||
'#!/bin/sh' \
|
||||
'd=$(TMPD)/$(@F)' \
|
||||
'mkdir -p $$d' \
|
||||
'TEST_TMPDIR=$$d ./db_test $(filter)' \
|
||||
> $@-t
|
||||
$(AM_V_at)chmod a=rx $@-t
|
||||
$(AM_V_at)mv $@-t $@
|
||||
gen_parallel_tests:
|
||||
$(AM_V_at)mkdir -p t
|
||||
$(AM_V_at)rm -f t/run-*
|
||||
$(MAKE) $(parallel_tests)
|
||||
|
||||
# Reorder input lines (which are one per test) so that the
|
||||
# longest-running tests appear first in the output.
|
||||
@@ -523,7 +596,7 @@ $(t_run): Makefile db_test
|
||||
# 107.816 PASS t/DBTest.EncodeDecompressedBlockSizeTest
|
||||
#
|
||||
slow_test_regexp = \
|
||||
^t/DBTest\.(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$
|
||||
^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 \
|
||||
@@ -534,26 +607,43 @@ prioritize_long_running_tests = \
|
||||
# Run with "make J=200% check" to run two parallel jobs per core.
|
||||
# The default is to run one job per core (J=100%).
|
||||
# See "man parallel" for its "-j ..." option.
|
||||
J = 100%
|
||||
J ?= 100%
|
||||
|
||||
# Use this regexp to select the subset of tests whose names match.
|
||||
tests-regexp = .
|
||||
|
||||
t_run = $(wildcard t/run-*)
|
||||
.PHONY: check_0
|
||||
check_0: $(t_run)
|
||||
check_0:
|
||||
$(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \
|
||||
printf '%s\n' '' \
|
||||
'To monitor subtest <duration,pass/fail,name>,' \
|
||||
' run "make watch-log" in a separate window' ''; \
|
||||
test -t 1 && eta=--eta || eta=; \
|
||||
{ \
|
||||
printf './%s\n' $(filter-out $(PARALLEL_TEST),$(TESTS)); \
|
||||
printf '%s\n' $(t_run); \
|
||||
} \
|
||||
| $(prioritize_long_running_tests) \
|
||||
| grep -E '$(tests-regexp)' \
|
||||
| build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu '{} >& t/log-{/}'
|
||||
|
||||
.PHONY: valgrind_check_0
|
||||
valgrind_check_0:
|
||||
$(AM_V_GEN)export TEST_TMPDIR=$(TMPD); \
|
||||
printf '%s\n' '' \
|
||||
'To monitor subtest <duration,pass/fail,name>,' \
|
||||
' run "make watch-log" in a separate window' ''; \
|
||||
test -t 1 && eta=--eta || eta=; \
|
||||
{ \
|
||||
printf './%s\n' $(filter-out db_test, $(TESTS)); \
|
||||
printf './%s\n' $(filter-out $(PARALLEL_TEST) %skiplist_test options_settable_test, $(TESTS)); \
|
||||
printf '%s\n' $(t_run); \
|
||||
} \
|
||||
| $(prioritize_long_running_tests) \
|
||||
| grep -E '$(tests-regexp)' \
|
||||
| parallel -j$(J) --joblog=LOG $$eta --gnu '{} >& t/log-{/}'
|
||||
endif
|
||||
| build_tools/gnu_parallel -j$(J) --plain --joblog=LOG $$eta --gnu \
|
||||
'(if [[ "{}" == "./"* ]] ; then $(DRIVER) {}; else {}; fi) ' \
|
||||
'>& t/valgrind_log-{/}'
|
||||
|
||||
CLEAN_FILES += t LOG $(TMPD)
|
||||
|
||||
@@ -570,11 +660,11 @@ watch-log:
|
||||
# If J != 1 and GNU parallel is installed, run the tests in parallel,
|
||||
# via the check_0 rule above. Otherwise, run them sequentially.
|
||||
check: all
|
||||
$(MAKE) gen_parallel_tests
|
||||
$(AM_V_GEN)if test "$(J)" != 1 \
|
||||
&& (parallel --gnu --help 2>/dev/null) | \
|
||||
&& (build_tools/gnu_parallel --gnu --help 2>/dev/null) | \
|
||||
grep -q 'GNU Parallel'; \
|
||||
then \
|
||||
t=$$($(test_names)); \
|
||||
$(MAKE) T="$$t" TMPD=$(TMPD) check_0; \
|
||||
else \
|
||||
for t in $(TESTS); do \
|
||||
@@ -596,12 +686,18 @@ ldb_tests: ldb
|
||||
crash_test: whitebox_crash_test blackbox_crash_test
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py --simple blackbox
|
||||
python -u tools/db_crashtest.py --simple blackbox
|
||||
python -u tools/db_crashtest.py blackbox
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
|
||||
whitebox_crash_test: db_stress
|
||||
python -u tools/db_crashtest.py --simple whitebox
|
||||
python -u tools/db_crashtest.py whitebox
|
||||
python -u tools/db_crashtest.py --simple whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD)
|
||||
python -u tools/db_crashtest.py whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD)
|
||||
|
||||
asan_check:
|
||||
$(MAKE) clean
|
||||
@@ -613,14 +709,33 @@ asan_crash_test:
|
||||
COMPILE_WITH_ASAN=1 $(MAKE) crash_test
|
||||
$(MAKE) clean
|
||||
|
||||
ubsan_check:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) check -j32
|
||||
$(MAKE) clean
|
||||
|
||||
ubsan_crash_test:
|
||||
$(MAKE) clean
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) crash_test
|
||||
$(MAKE) clean
|
||||
|
||||
valgrind_check: $(TESTS)
|
||||
for t in $(filter-out %skiplist_test,$(TESTS)); do \
|
||||
$(VALGRIND_VER) $(VALGRIND_OPTS) ./$$t; \
|
||||
ret_code=$$?; \
|
||||
if [ $$ret_code -ne 0 ]; then \
|
||||
exit $$ret_code; \
|
||||
fi; \
|
||||
done
|
||||
$(MAKE) DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" gen_parallel_tests
|
||||
$(AM_V_GEN)if test "$(J)" != 1 \
|
||||
&& (build_tools/gnu_parallel --gnu --help 2>/dev/null) | \
|
||||
grep -q 'GNU Parallel'; \
|
||||
then \
|
||||
$(MAKE) TMPD=$(TMPD) \
|
||||
DRIVER="$(VALGRIND_VER) $(VALGRIND_OPTS)" valgrind_check_0; \
|
||||
else \
|
||||
for t in $(filter-out %skiplist_test options_settable_test,$(TESTS)); do \
|
||||
$(VALGRIND_VER) $(VALGRIND_OPTS) ./$$t; \
|
||||
ret_code=$$?; \
|
||||
if [ $$ret_code -ne 0 ]; then \
|
||||
exit $$ret_code; \
|
||||
fi; \
|
||||
done; \
|
||||
fi
|
||||
|
||||
|
||||
ifneq ($(PAR_TEST),)
|
||||
@@ -629,10 +744,10 @@ parloop:
|
||||
for t in $(PAR_TEST); do \
|
||||
echo "===== Running $$t in parallel $(NUM_PAR)";\
|
||||
if [ $(db_test) -eq 1 ]; then \
|
||||
seq $(J) | v="$$t" parallel --gnu 's=$(TMPD)/rdb-{}; export TEST_TMPDIR=$$s;' \
|
||||
seq $(J) | v="$$t" build_tools/gnu_parallel --gnu --plain 's=$(TMPD)/rdb-{}; export TEST_TMPDIR=$$s;' \
|
||||
'timeout 2m ./db_test --gtest_filter=$$v >> $$s/log-{} 2>1'; \
|
||||
else\
|
||||
seq $(J) | v="./$$t" parallel --gnu 's=$(TMPD)/rdb-{};' \
|
||||
seq $(J) | v="./$$t" build_tools/gnu_parallel --gnu --plain 's=$(TMPD)/rdb-{};' \
|
||||
'export TEST_TMPDIR=$$s; timeout 10m $$v >> $$s/log-{} 2>1'; \
|
||||
fi; \
|
||||
ret_code=$$?; \
|
||||
@@ -644,9 +759,16 @@ parloop:
|
||||
exit $$ret_bad;
|
||||
endif
|
||||
|
||||
test_names = \
|
||||
./db_test --gtest_list_tests \
|
||||
| perl -n \
|
||||
-e 's/ *\#.*//;' \
|
||||
-e '/^(\s*)(\S+)/; !$$1 and do {$$p=$$2; break};' \
|
||||
-e 'print qq! $$p$$2!'
|
||||
|
||||
parallel_check: $(TESTS)
|
||||
$(AM_V_GEN)if test "$(J)" > 1 \
|
||||
&& (parallel --gnu --help 2>/dev/null) | \
|
||||
&& (build_tools/gnu_parallel --gnu --help 2>/dev/null) | \
|
||||
grep -q 'GNU Parallel'; \
|
||||
then \
|
||||
echo Running in parallel $(J); \
|
||||
@@ -656,7 +778,7 @@ parallel_check: $(TESTS)
|
||||
ret_bad=0; \
|
||||
echo $(J);\
|
||||
echo Test Dir: $(TMPD); \
|
||||
seq $(J) | parallel --gnu 's=$(TMPD)/rdb-{}; rm -rf $$s; mkdir $$s'; \
|
||||
seq $(J) | build_tools/gnu_parallel --gnu --plain 's=$(TMPD)/rdb-{}; rm -rf $$s; mkdir $$s'; \
|
||||
$(MAKE) PAR_TEST="$(shell $(test_names))" TMPD=$(TMPD) \
|
||||
J=$(J) db_test=1 parloop; \
|
||||
$(MAKE) PAR_TEST="$(filter-out db_test, $(TESTS))" \
|
||||
@@ -695,6 +817,7 @@ clean:
|
||||
find . -name "*.[oda]" -exec rm -f {} \;
|
||||
find . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm {} \;
|
||||
rm -rf bzip2* snappy* zlib* lz4*
|
||||
cd java; $(MAKE) clean
|
||||
|
||||
tags:
|
||||
ctags * -R
|
||||
@@ -713,16 +836,24 @@ $(LIBRARY): $(LIBOBJECTS)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $(LIBOBJECTS)
|
||||
|
||||
$(TOOLS_LIBRARY): $(BENCH_LIB_SOURCES:.cc=.o) $(TOOL_LIB_SOURCES:.cc=.o) $(LIB_SOURCES:.cc=.o) $(TESTUTIL)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
librocksdb_env_basic_test.a: util/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_V_AR)rm -f $@
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
db_bench: tools/db_bench.o $(BENCHTOOLOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
cache_bench: util/cache_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
memtablerep_bench: db/memtablerep_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
persistent_cache_bench: utilities/persistent_cache/persistent_cache_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
block_hash_index_test: table/block_hash_index_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
memtablerep_bench: db/memtablerep_bench.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
$(AM_LINK)
|
||||
|
||||
db_stress: tools/db_stress.o $(LIBOBJECTS) $(TESTUTIL)
|
||||
@@ -743,7 +874,7 @@ arena_test: util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
autovector_test: util/autovector_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
column_family_test: db/column_family_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
column_family_test: db/column_family_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
table_properties_collector_test: db/table_properties_collector_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
@@ -764,12 +895,18 @@ cache_test: util/cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
coding_test: util/coding_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
option_change_migration_test: utilities/option_change_migration/option_change_migration_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
stringappend_test: utilities/merge_operators/string_append/stringappend_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
redis_test: utilities/redis/redis_lists_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
hash_table_test: utilities/persistent_cache/hash_table_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
histogram_test: util/histogram_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -794,6 +931,9 @@ db_test2: db/db_test2.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
db_block_cache_test: db/db_block_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_bloom_filter_test: db/db_bloom_filter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_log_iter_test: db/db_log_iter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -806,9 +946,24 @@ db_compaction_test: db/db_compaction_test.o db/db_test_util.o $(LIBOBJECTS) $(TE
|
||||
db_dynamic_level_test: db/db_dynamic_level_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_flush_test: db/db_flush_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_inplace_update_test: db/db_inplace_update_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_iterator_test: db/db_iterator_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_options_test: db/db_options_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_sst_test: db/db_sst_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
external_sst_file_test: db/external_sst_file_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_tailing_iter_test: db/db_tailing_iter_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -821,6 +976,9 @@ db_universal_compaction_test: db/db_universal_compaction_test.o db/db_test_util.
|
||||
db_wal_test: db/db_wal_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_io_failure_test: db/db_io_failure_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_properties_test: db/db_properties_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -828,7 +986,7 @@ db_table_properties_test: db/db_table_properties_test.o db/db_test_util.o $(LIBO
|
||||
$(AM_LINK)
|
||||
|
||||
log_write_bench: util/log_write_bench.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK) $(pg)
|
||||
$(AM_LINK) $(PROFILING_FLAGS)
|
||||
|
||||
plain_table_db_test: db/plain_table_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
@@ -837,7 +995,7 @@ comparator_db_test: db/comparator_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
table_reader_bench: table/table_reader_bench.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK) $(pg)
|
||||
$(AM_LINK) $(PROFILING_FLAGS)
|
||||
|
||||
perf_context_test: db/perf_context_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
@@ -848,6 +1006,9 @@ prefix_test: db/prefix_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
backupable_db_test: utilities/backupable/backupable_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_db_test: utilities/blob_db/blob_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
checkpoint_test: utilities/checkpoint/checkpoint_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -857,15 +1018,29 @@ document_db_test: utilities/document/document_db_test.o $(LIBOBJECTS) $(TESTHARN
|
||||
json_document_test: utilities/document/json_document_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sim_cache_test: utilities/simulator_cache/sim_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
spatial_db_test: utilities/spatialdb/spatial_db_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_mirror_test: utilities/env_mirror_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
ifdef ROCKSDB_USE_LIBRADOS
|
||||
env_librados_test: utilities/env_librados_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS) $(COVERAGEFLAGS)
|
||||
endif
|
||||
|
||||
env_registry_test: utilities/env_registry_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
ttl_test: utilities/ttl/ttl_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
date_tiered_test: utilities/date_tiered/date_tiered_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
write_batch_with_index_test: utilities/write_batch_with_index/write_batch_with_index_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -890,6 +1065,9 @@ wal_manager_test: db/wal_manager_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
dbformat_test: db/dbformat_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_basic_test: util/env_basic_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
env_test: util/env_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -965,6 +1143,9 @@ merge_test: db/merge_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
merger_test: table/merger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
util_merge_operators_test: utilities/util_merge_operators_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
options_file_test: db/options_file_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1001,16 +1182,22 @@ compact_files_test: db/compact_files_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
options_test: util/options_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
options_settable_test: util/options_settable_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
options_util_test: utilities/options/options_util_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
db_bench_tool_test: tools/db_bench_tool_test.o $(BENCHTOOLOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
event_logger_test: util/event_logger_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_dump_test: tools/sst_dump_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
memenv_test : util/memenv_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
column_aware_encoding_test: utilities/column_aware_encoding_test.o $(TESTHARNESS) $(EXPOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
optimistic_transaction_test: utilities/transactions/optimistic_transaction_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
@@ -1043,6 +1230,12 @@ transaction_test: utilities/transactions/transaction_test.o $(LIBOBJECTS) $(TEST
|
||||
sst_dump: tools/sst_dump.o $(LIBOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
column_aware_encoding_exp: utilities/column_aware_encoding_exp.o $(EXPOBJECTS)
|
||||
$(AM_LINK)
|
||||
|
||||
repair_test: db/repair_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
ldb_cmd_test: tools/ldb_cmd_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1052,6 +1245,16 @@ ldb: tools/ldb.o $(LIBOBJECTS)
|
||||
iostats_context_test: util/iostats_context_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_V_CCLD)$(CXX) $^ $(EXEC_LDFLAGS) -o $@ $(LDFLAGS)
|
||||
|
||||
persistent_cache_test: utilities/persistent_cache/persistent_cache_test.o db/db_test_util.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
statistics_test: util/statistics_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
lru_cache_test: util/lru_cache_test.o $(LIBOBJECTS) $(TESTHARNESS)
|
||||
$(AM_LINK)
|
||||
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
INSTALL_PATH ?= /usr/local
|
||||
@@ -1122,29 +1325,29 @@ endif
|
||||
|
||||
libz.a:
|
||||
-rm -rf zlib-1.2.8
|
||||
curl -O http://zlib.net/zlib-1.2.8.tar.gz
|
||||
curl -O -L http://zlib.net/zlib-1.2.8.tar.gz
|
||||
tar xvzf zlib-1.2.8.tar.gz
|
||||
cd zlib-1.2.8 && CFLAGS='-fPIC' ./configure --static && make
|
||||
cp zlib-1.2.8/libz.a .
|
||||
|
||||
libbz2.a:
|
||||
-rm -rf bzip2-1.0.6
|
||||
curl -O http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz
|
||||
curl -O -L http://www.bzip.org/1.0.6/bzip2-1.0.6.tar.gz
|
||||
tar xvzf bzip2-1.0.6.tar.gz
|
||||
cd bzip2-1.0.6 && make CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64'
|
||||
cp bzip2-1.0.6/libbz2.a .
|
||||
|
||||
libsnappy.a:
|
||||
-rm -rf snappy-1.1.1
|
||||
curl -O https://snappy.googlecode.com/files/snappy-1.1.1.tar.gz
|
||||
tar xvzf snappy-1.1.1.tar.gz
|
||||
cd snappy-1.1.1 && ./configure --with-pic --enable-static
|
||||
cd snappy-1.1.1 && make
|
||||
cp snappy-1.1.1/.libs/libsnappy.a .
|
||||
-rm -rf snappy-1.1.3
|
||||
curl -O -L https://github.com/google/snappy/releases/download/1.1.3/snappy-1.1.3.tar.gz
|
||||
tar xvzf snappy-1.1.3.tar.gz
|
||||
cd snappy-1.1.3 && ./configure --with-pic --enable-static
|
||||
cd snappy-1.1.3 && make
|
||||
cp snappy-1.1.3/.libs/libsnappy.a .
|
||||
|
||||
liblz4.a:
|
||||
-rm -rf lz4-r127
|
||||
curl -O https://codeload.github.com/Cyan4973/lz4/tar.gz/r127
|
||||
curl -O -L https://codeload.github.com/Cyan4973/lz4/tar.gz/r127
|
||||
mv r127 lz4-r127.tar.gz
|
||||
tar xvzf lz4-r127.tar.gz
|
||||
cd lz4-r127/lib && make CFLAGS='-fPIC' all
|
||||
@@ -1155,7 +1358,7 @@ java_static_libobjects = $(patsubst %,jls/%,$(LIBOBJECTS))
|
||||
CLEAN_FILES += jls
|
||||
|
||||
JAVA_STATIC_FLAGS = -DZLIB -DBZIP2 -DSNAPPY -DLZ4
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-1.2.8 -I./bzip2-1.0.6 -I./snappy-1.1.1 -I./lz4-r127/lib
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-1.2.8 -I./bzip2-1.0.6 -I./snappy-1.1.3 -I./lz4-r127/lib
|
||||
|
||||
$(java_static_libobjects): jls/%.o: %.cc libz.a libbz2.a libsnappy.a liblz4.a
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
@@ -1206,6 +1409,12 @@ rocksdbjava: $(java_libobjects)
|
||||
jclean:
|
||||
cd java;$(MAKE) clean;
|
||||
|
||||
jtest_compile: rocksdbjava
|
||||
cd java;$(MAKE) java_test
|
||||
|
||||
jtest_run:
|
||||
cd java;$(MAKE) run_test
|
||||
|
||||
jtest: rocksdbjava
|
||||
cd java;$(MAKE) sample;$(MAKE) test;
|
||||
|
||||
@@ -1214,7 +1423,7 @@ jdb_bench:
|
||||
|
||||
commit_prereq: build_tools/rocksdb-lego-determinator \
|
||||
build_tools/precommit_checker.py
|
||||
J=$(J) build_tools/precommit_checker.py unit unit_481 clang_unit tsan asan lite
|
||||
J=$(J) build_tools/precommit_checker.py unit unit_481 clang_unit release release_481 clang_release tsan asan ubsan lite unit_non_shm
|
||||
$(MAKE) clean && $(MAKE) jclean && $(MAKE) rocksdbjava;
|
||||
|
||||
xfunc:
|
||||
@@ -1262,7 +1471,7 @@ endif
|
||||
# Source files dependencies detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
all_sources = $(LIB_SOURCES) $(TEST_BENCH_SOURCES) $(MOCK_SOURCES)
|
||||
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)
|
||||
|
||||
# Add proper dependency support so changing a .h file forces a .cc file to
|
||||
|
||||
@@ -1,6 +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)
|
||||
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
It is built on earlier work on LevelDB by Sanjay Ghemawat (sanjay@google.com)
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
This document lists users of RocksDB and their use cases. If you are using RocksDB, please open a pull request and add yourself to the list.
|
||||
|
||||
## Facebook
|
||||
At Facebook, we use RocksDB as a backend for many different stateful services. We're also experimenting with running RocksDB as a storage engine for two databases:
|
||||
At Facebook, we use RocksDB as storage engines in multiple data management services and a backend for many different stateful services, including:
|
||||
|
||||
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.[*]
|
||||
4. Dragan -- 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++.[*]
|
||||
|
||||
[*] https://research.facebook.com/publications/realtime-data-processing-at-facebook/
|
||||
|
||||
## LinkedIn
|
||||
Two different use cases at Linkedin are using RocksDB as a storage engine:
|
||||
|
||||
1. LinkedIn's follow feed for storing user's activities
|
||||
1. LinkedIn's follow feed for storing user's activities. Check out the blog post: https://engineering.linkedin.com/blog/2016/03/followfeed--linkedin-s-feed-made-faster-and-smarter
|
||||
2. Apache Samza, open source framework for stream processing
|
||||
|
||||
Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasundaram: http://www.youtube.com/watch?v=plqVp_OnSzg
|
||||
@@ -52,3 +58,16 @@ Pinterest's Object Retrieval System uses RocksDB for storage: https://www.youtub
|
||||
|
||||
## VWO, Wingify
|
||||
[VWO's](https://vwo.com/) Smart Code checker and URL helper uses RocksDB to store all the URLs where VWO's Smart Code is installed.
|
||||
|
||||
## quasardb
|
||||
[quasardb](https://www.quasardb.net) is a high-performance, distributed, transactional key-value database that integrates well with in-memory analytics engines such as Apache Spark.
|
||||
quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
|
||||
## Netflix
|
||||
[Netflix](http://techblog.netflix.com/2016/05/application-data-caching-using-ssds.html) uses RocksDB on spinning disks to cache application data.
|
||||
|
||||
## TiKV
|
||||
[TiKV](https://github.com/pingcap/tikv) is a GEO-replicated, high-performance, distributed, transactional key-value database. TiKV is powered by Rust and Raft. TiKV uses RocksDB as its persistence layer.
|
||||
|
||||
## Apache Flink
|
||||
[Apache Flink](https://flink.apache.org/news/2016/03/08/release-1.0.0.html) uses RocksDB to store state locally on a machine.
|
||||
+4
-2
@@ -1,8 +1,9 @@
|
||||
version: 1.0.{build}
|
||||
image: Visual Studio 2015
|
||||
before_build:
|
||||
- md %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cd %APPVEYOR_BUILD_FOLDER%\build
|
||||
- cmake -G "Visual Studio 12 Win64" -DOPTDBG=1 ..
|
||||
- cmake -G "Visual Studio 14 2015 Win64" -DOPTDBG=1 -DXPRESS=1 ..
|
||||
- cd ..
|
||||
build:
|
||||
project: build\rocksdb.sln
|
||||
@@ -10,6 +11,7 @@ build:
|
||||
verbosity: minimal
|
||||
test:
|
||||
test_script:
|
||||
- ps: build_tools\run_ci_db_test.ps1 -EnableRerun -Run db_test -Exclude DBTest.Randomized,DBTest.FileCreationRandomFailure -Concurrency 18
|
||||
- ps: build_tools\run_ci_db_test.ps1 -EnableRerun -Run db_test2 -Concurrency 8
|
||||
- ps: build_tools\run_ci_db_test.ps1 -EnableRerun -Run db_test -Exclude DBTest.GroupCommitTest -Concurrency 10
|
||||
- ps: build_tools\run_ci_db_test.ps1 -Run env_test -Concurrency 1
|
||||
|
||||
@@ -6,33 +6,66 @@
|
||||
* @phutil-library-version 2
|
||||
*/
|
||||
|
||||
phutil_register_library_map(array(
|
||||
'__library_version__' => 2,
|
||||
'class' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'cpp_linter/ArcanistCpplintLinter.php',
|
||||
'BaseDirectoryScopedFormatLinter' => 'cpp_linter/BaseDirectoryScopedFormatLinter.php',
|
||||
'FacebookArcanistConfiguration' => 'config/FacebookArcanistConfiguration.php',
|
||||
'FacebookFbcodeLintEngine' => 'lint_engine/FacebookFbcodeLintEngine.php',
|
||||
'FacebookFbcodeUnitTestEngine' => 'unit_engine/FacebookFbcodeUnitTestEngine.php',
|
||||
'FacebookHowtoevenLintEngine' => 'lint_engine/FacebookHowtoevenLintEngine.php',
|
||||
'FacebookHowtoevenLinter' => 'cpp_linter/FacebookHowtoevenLinter.php',
|
||||
'FbcodeClangFormatLinter' => 'cpp_linter/FbcodeClangFormatLinter.php',
|
||||
'FbcodeCppLinter' => 'cpp_linter/FbcodeCppLinter.php',
|
||||
),
|
||||
'function' =>
|
||||
array(
|
||||
),
|
||||
'xmap' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'ArcanistLinter',
|
||||
'BaseDirectoryScopedFormatLinter' => 'ArcanistLinter',
|
||||
'FacebookArcanistConfiguration' => 'ArcanistConfiguration',
|
||||
'FacebookFbcodeLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookFbcodeUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
|
||||
'FacebookHowtoevenLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookHowtoevenLinter' => 'ArcanistLinter',
|
||||
'FbcodeClangFormatLinter' => 'BaseDirectoryScopedFormatLinter',
|
||||
'FbcodeCppLinter' => 'ArcanistLinter',
|
||||
),
|
||||
));
|
||||
if (class_exists('ArcanistWorkflow')) {
|
||||
phutil_register_library_map(array(
|
||||
'__library_version__' => 2,
|
||||
'class' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'cpp_linter/ArcanistCpplintLinter.php',
|
||||
'BaseDirectoryScopedFormatLinter' => 'cpp_linter/BaseDirectoryScopedFormatLinter.php',
|
||||
'FacebookArcanistConfiguration' => 'config/FacebookArcanistConfiguration.php',
|
||||
'FacebookFbcodeLintEngine' => 'lint_engine/FacebookFbcodeLintEngine.php',
|
||||
'FacebookFbcodeUnitTestEngine' => 'unit_engine/FacebookFbcodeUnitTestEngine.php',
|
||||
'FacebookHowtoevenLintEngine' => 'lint_engine/FacebookHowtoevenLintEngine.php',
|
||||
'FacebookHowtoevenLinter' => 'cpp_linter/FacebookHowtoevenLinter.php',
|
||||
'FbcodeClangFormatLinter' => 'cpp_linter/FbcodeClangFormatLinter.php',
|
||||
'FbcodeCppLinter' => 'cpp_linter/FbcodeCppLinter.php',
|
||||
),
|
||||
'function' =>
|
||||
array(
|
||||
),
|
||||
'xmap' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'ArcanistLinter',
|
||||
'BaseDirectoryScopedFormatLinter' => 'ArcanistLinter',
|
||||
'FacebookArcanistConfiguration' => 'ArcanistConfiguration',
|
||||
'FacebookFbcodeLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookFbcodeUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
|
||||
'FacebookHowtoevenLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookHowtoevenLinter' => 'ArcanistLinter',
|
||||
'FbcodeClangFormatLinter' => 'BaseDirectoryScopedFormatLinter',
|
||||
'FbcodeCppLinter' => 'ArcanistLinter',
|
||||
),
|
||||
));
|
||||
} else {
|
||||
phutil_register_library_map(array(
|
||||
'__library_version__' => 2,
|
||||
'class' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'cpp_linter/ArcanistCpplintLinter.php',
|
||||
'BaseDirectoryScopedFormatLinter' => 'cpp_linter/BaseDirectoryScopedFormatLinter.php',
|
||||
'FacebookArcanistConfiguration' => 'config/FacebookOldArcanistConfiguration.php',
|
||||
'FacebookFbcodeLintEngine' => 'lint_engine/FacebookFbcodeLintEngine.php',
|
||||
'FacebookFbcodeUnitTestEngine' => 'unit_engine/FacebookOldFbcodeUnitTestEngine.php',
|
||||
'FacebookHowtoevenLintEngine' => 'lint_engine/FacebookHowtoevenLintEngine.php',
|
||||
'FacebookHowtoevenLinter' => 'cpp_linter/FacebookHowtoevenLinter.php',
|
||||
'FbcodeClangFormatLinter' => 'cpp_linter/FbcodeClangFormatLinter.php',
|
||||
'FbcodeCppLinter' => 'cpp_linter/FbcodeCppLinter.php',
|
||||
),
|
||||
'function' =>
|
||||
array(
|
||||
),
|
||||
'xmap' =>
|
||||
array(
|
||||
'ArcanistCpplintLinter' => 'ArcanistLinter',
|
||||
'BaseDirectoryScopedFormatLinter' => 'ArcanistLinter',
|
||||
'FacebookArcanistConfiguration' => 'ArcanistConfiguration',
|
||||
'FacebookFbcodeLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookFbcodeUnitTestEngine' => 'ArcanistBaseUnitTestEngine',
|
||||
'FacebookHowtoevenLintEngine' => 'ArcanistLintEngine',
|
||||
'FacebookHowtoevenLinter' => 'ArcanistLinter',
|
||||
'FbcodeClangFormatLinter' => 'BaseDirectoryScopedFormatLinter',
|
||||
'FbcodeCppLinter' => 'ArcanistLinter',
|
||||
),
|
||||
));
|
||||
}
|
||||
@@ -4,202 +4,33 @@
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
require('RocksDBCommonHelper.php');
|
||||
|
||||
define("DIFF_COMMAND", "diff");
|
||||
|
||||
class FacebookArcanistConfiguration extends ArcanistConfiguration {
|
||||
|
||||
public function didRunWorkflow($command,
|
||||
ArcanistBaseWorkflow $workflow,
|
||||
ArcanistWorkflow $workflow,
|
||||
$error_code) {
|
||||
if ($command == 'diff' && !$workflow->isRawDiffSource()) {
|
||||
$this->startTestsInJenkins($workflow);
|
||||
$this->startTestsInSandcastle($workflow);
|
||||
// Default options don't terminate on failure, but that's what we want. In
|
||||
// the current case we use assertions intentionally as "terminate on failure
|
||||
// invariants".
|
||||
assert_options(ASSERT_BAIL, true);
|
||||
|
||||
assert($workflow);
|
||||
assert(strlen($command) > 0);
|
||||
|
||||
if ($command == DIFF_COMMAND && !$workflow->isRawDiffSource()) {
|
||||
$diffID = $workflow->getDiffId();
|
||||
|
||||
// When submitting a diff this code path gets executed multiple times in
|
||||
// a row. We only care about the case when ID for the diff is provided
|
||||
// because that's what we need to apply the diff and trigger the tests.
|
||||
if (strlen($diffID) > 0) {
|
||||
assert(is_numeric($diffID));
|
||||
startTestsInSandcastle(true /* $applyDiff */, $workflow, $diffID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
/* Run tests in sandcastle */
|
||||
function postURL($diffID, $url) {
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"click here for sandcastle tests for D' . $diffID . '", '
|
||||
. '"link":"' . $url . '"}\' | '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
shell_exec($cmd);
|
||||
}
|
||||
|
||||
function updateTestCommand($diffID, $test, $status) {
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"' . $test . '", '
|
||||
. '"result":"' . $status . '"}\' | '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
function updateTest($diffID, $test) {
|
||||
shell_exec($this->updateTestCommand($diffID, $test, "waiting"));
|
||||
}
|
||||
|
||||
function getSteps($diffID, $username, $test) {
|
||||
$arcrc_content = exec("cat ~/.arcrc | gzip -f | base64 -w0");
|
||||
|
||||
// Sandcastle machines don't have arc setup. We copy the user certificate
|
||||
// and authenticate using that in sandcastle
|
||||
$setup = array(
|
||||
"name" => "Setup arcrc",
|
||||
"shell" => "echo " . $arcrc_content . " | base64 --decode"
|
||||
. " | gzip -d > ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// arc demands certain permission on its config
|
||||
$fix_permission = array(
|
||||
"name" => "Fix environment",
|
||||
"shell" => "chmod 600 ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// fbcode is a sub-repo. We cannot patch until we add it to ignore otherwise
|
||||
// git thinks it is uncommited change
|
||||
$fix_git_ignore = array(
|
||||
"name" => "Fix git ignore",
|
||||
"shell" => "echo fbcode >> .git/info/exclude",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Patch the code (keep your fingures crossed)
|
||||
$patch = array(
|
||||
"name" => "Patch " . $diffID,
|
||||
"shell" => "HTTPS_PROXY=fwdproxy:8080 arc --arcrc-file ~/.arcrc "
|
||||
. "patch --diff " . $diffID,
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Clean up the user arc config we are using
|
||||
$cleanup = array(
|
||||
"name" => "Arc cleanup",
|
||||
"shell" => "rm -f ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Construct the steps in the order of execution
|
||||
$steps[] = $setup;
|
||||
$steps[] = $fix_permission;
|
||||
$steps[] = $fix_git_ignore;
|
||||
$steps[] = $patch;
|
||||
|
||||
// Run the actual command
|
||||
$this->updateTest($diffID, $test);
|
||||
$cmd = $this->updateTestCommand($diffID, $test, "running") . ";"
|
||||
. "(./build_tools/precommit_checker.py " . $test
|
||||
. "&& "
|
||||
. $this->updateTestCommand($diffID, $test, "pass") . ")"
|
||||
. "|| " . $this->updateTestCommand($diffID, $test, "fail")
|
||||
. "; cat /tmp/precommit-check.log"
|
||||
. "; for f in `ls t/log-*`; do echo \$f; cat \$f; done";
|
||||
|
||||
$run_test = array(
|
||||
"name" => "Run " . $test,
|
||||
"shell" => $cmd,
|
||||
"user" => "root",
|
||||
);
|
||||
|
||||
$steps[] = $run_test;
|
||||
$steps[] = $cleanup;
|
||||
|
||||
return $steps;
|
||||
}
|
||||
|
||||
function startTestsInSandcastle($workflow) {
|
||||
// extract information we need from workflow or CLI
|
||||
$diffID = $workflow->getDiffId();
|
||||
$username = exec("whoami");
|
||||
|
||||
if ($diffID == null || $username == null) {
|
||||
// there is no diff and we can't extract username
|
||||
// we cannot schedule sandcasstle job
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(getenv("ROCKSDB_CHECK_ALL"), 1) == 0) {
|
||||
// extract all tests from the CI definition
|
||||
$output = file_get_contents("build_tools/rocksdb-lego-determinator");
|
||||
preg_match_all('/[ ]{2}([a-zA-Z0-9_]+)[\)]{1}/', $output, $matches);
|
||||
$tests = $matches[1];
|
||||
} else {
|
||||
// manually list of tests we want to run in sandcastle
|
||||
$tests = array(
|
||||
"unit", "unit_481", "clang_unit", "tsan", "asan", "lite", "valgrind"
|
||||
);
|
||||
}
|
||||
|
||||
// construct a job definition for each test and add it to the master plan
|
||||
foreach ($tests as $test) {
|
||||
$arg[] = array(
|
||||
"name" => "RocksDB diff " . $diffID . " test " . $test,
|
||||
"steps" => $this->getSteps($diffID, $username, $test)
|
||||
);
|
||||
}
|
||||
|
||||
// we cannot submit the parallel execution master plan to sandcastle
|
||||
// we need supply the job plan as a determinator
|
||||
// so we construct a small job that will spit out the master job plan
|
||||
// which sandcastle will parse and execute
|
||||
// Why compress ? Otherwise we run over the max string size.
|
||||
$cmd = "echo " . base64_encode(json_encode($arg))
|
||||
. " | gzip -f | base64 -w0";
|
||||
$arg_encoded = shell_exec($cmd);
|
||||
|
||||
$command = array(
|
||||
"name" => "Run diff " . $diffID . "for user " . $username,
|
||||
"steps" => array()
|
||||
);
|
||||
|
||||
$command["steps"][] = array(
|
||||
"name" => "Generate determinator",
|
||||
"shell" => "echo " . $arg_encoded . " | base64 --decode | gzip -d"
|
||||
. " | base64 --decode",
|
||||
"determinator" => true,
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// submit to sandcastle
|
||||
$url = 'https://interngraph.intern.facebook.com/sandcastle/generate?'
|
||||
.'command=SandcastleUniversalCommand'
|
||||
.'&vcs=rocksdb-git&revision=origin%2Fmaster&type=lego'
|
||||
.'&user=krad&alias=rocksdb-precommit'
|
||||
.'&command-args=' . urlencode(json_encode($command));
|
||||
|
||||
$cmd = 'https_proxy= HTTPS_PROXY= curl -s -k -F app=659387027470559 '
|
||||
. '-F token=AeO_3f2Ya3TujjnxGD4 "' . $url . '"';
|
||||
|
||||
$output = shell_exec($cmd);
|
||||
|
||||
// extract sandcastle URL from the response
|
||||
preg_match('/url": "(.+)"/', $output, $sandcastle_url);
|
||||
|
||||
echo "\nSandcastle URL: " . $sandcastle_url[1] . "\n";
|
||||
|
||||
// Ask phabricator to display it on the diff UI
|
||||
$this->postURL($diffID, $sandcastle_url[1]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
/* Send off builds to jenkins */
|
||||
function startTestsInJenkins($workflow) {
|
||||
$diffID = $workflow->getDiffID();
|
||||
if ($diffID === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$results = $workflow->getTestResults();
|
||||
if (!$results) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url = "https://ci-builds.fb.com/view/rocksdb/job/rocksdb_diff_check/"
|
||||
."buildWithParameters?token=AUTH&DIFF_ID=$diffID";
|
||||
system("curl --noproxy '*' \"$url\" > /dev/null 2>&1");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
require('RocksDBCommonHelper.php');
|
||||
|
||||
define("DIFF_COMMAND", "diff");
|
||||
|
||||
class FacebookArcanistConfiguration extends ArcanistConfiguration {
|
||||
|
||||
public function didRunWorkflow($command,
|
||||
ArcanistBaseWorkflow $workflow,
|
||||
$error_code) {
|
||||
// Default options don't terminate on failure, but that's what we want. In
|
||||
// the current case we use assertions intentionally as "terminate on failure
|
||||
// invariants".
|
||||
assert_options(ASSERT_BAIL, true);
|
||||
|
||||
assert($workflow);
|
||||
assert(strlen($command) > 0);
|
||||
|
||||
if ($command == DIFF_COMMAND && !$workflow->isRawDiffSource()) {
|
||||
$diffID = $workflow->getDiffId();
|
||||
|
||||
// When submitting a diff this code path gets executed multiple times in
|
||||
// a row. We only care about the case when ID for the diff is provided
|
||||
// because that's what we need to apply the diff and trigger the tests.
|
||||
if (strlen($diffID) > 0) {
|
||||
assert(is_numeric($diffID));
|
||||
startTestsInSandcastle(true /* $applyDiff */, $workflow, $diffID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
// Name of the environment variables which need to be set by the entity which
|
||||
// triggers continuous runs so that code at the end of the file gets executed
|
||||
// and Sandcastle run starts.
|
||||
define("ENV_POST_RECEIVE_HOOK", "POST_RECEIVE_HOOK");
|
||||
define("ENV_HTTPS_APP_VALUE", "HTTPS_APP_VALUE");
|
||||
define("ENV_HTTPS_TOKEN_VALUE", "HTTPS_TOKEN_VALUE");
|
||||
|
||||
define("PRIMARY_TOKEN_FILE", '/home/krad/.sandcastle');
|
||||
define("SECONDARY_TOKEN_FILE", '$HOME/.sandcastle');
|
||||
define("CONT_RUN_ALIAS", "leveldb");
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
/* Run tests in sandcastle */
|
||||
function postURL($diffID, $url) {
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
assert(strlen($url) > 0);
|
||||
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"click here for sandcastle tests for D' . $diffID . '", '
|
||||
. '"link":"' . $url . '"}\' | '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
shell_exec($cmd);
|
||||
}
|
||||
|
||||
function buildUpdateTestStatusCmd($diffID, $test, $status) {
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
assert(strlen($test) > 0);
|
||||
assert(strlen($status) > 0);
|
||||
|
||||
$cmd = 'echo \'{"diff_id": "' . $diffID . '", '
|
||||
. '"name":"' . $test . '", '
|
||||
. '"result":"' . $status . '"}\' | '
|
||||
. 'http_proxy=fwdproxy.any.facebook.com:8080 '
|
||||
. 'https_proxy=fwdproxy.any.facebook.com:8080 arc call-conduit '
|
||||
. 'differential.updateunitresults';
|
||||
return $cmd;
|
||||
}
|
||||
|
||||
function updateTestStatus($diffID, $test) {
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
assert(strlen($test) > 0);
|
||||
|
||||
shell_exec(buildUpdateTestStatusCmd($diffID, $test, "waiting"));
|
||||
}
|
||||
|
||||
function getSteps($applyDiff, $diffID, $username, $test) {
|
||||
assert(strlen($username) > 0);
|
||||
assert(strlen($test) > 0);
|
||||
|
||||
if ($applyDiff) {
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
|
||||
$arcrc_content = (PHP_OS == "Darwin" ?
|
||||
exec("cat ~/.arcrc | gzip -f | base64") :
|
||||
exec("cat ~/.arcrc | gzip -f | base64 -w0"));
|
||||
assert(strlen($arcrc_content) > 0);
|
||||
|
||||
// Sandcastle machines don't have arc setup. We copy the user certificate
|
||||
// and authenticate using that in Sandcastle.
|
||||
$setup = array(
|
||||
"name" => "Setup arcrc",
|
||||
"shell" => "echo " . $arcrc_content . " | base64 --decode"
|
||||
. " | gzip -d > ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// arc demands certain permission on its config.
|
||||
// also fix the sticky bit issue in sandcastle
|
||||
$fix_permission = array(
|
||||
"name" => "Fix environment",
|
||||
"shell" => "chmod 600 ~/.arcrc && chmod +t /dev/shm",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Construct the steps in the order of execution.
|
||||
$steps[] = $setup;
|
||||
$steps[] = $fix_permission;
|
||||
}
|
||||
|
||||
// fbcode is a sub-repo. We cannot patch until we add it to ignore otherwise
|
||||
// Git thinks it is an uncommited change.
|
||||
$fix_git_ignore = array(
|
||||
"name" => "Fix git ignore",
|
||||
"shell" => "echo fbcode >> .git/info/exclude",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
$steps[] = $fix_git_ignore;
|
||||
|
||||
// This will be the command used to execute particular type of tests.
|
||||
$cmd = "";
|
||||
|
||||
if ($applyDiff) {
|
||||
// Patch the code (keep your fingures crossed).
|
||||
$patch = array(
|
||||
"name" => "Patch " . $diffID,
|
||||
"shell" => "HTTPS_PROXY=fwdproxy:8080 arc --arcrc-file ~/.arcrc "
|
||||
. "patch --nocommit --diff " . $diffID,
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
$steps[] = $patch;
|
||||
|
||||
updateTestStatus($diffID, $test);
|
||||
$cmd = buildUpdateTestStatusCmd($diffID, $test, "running") . "; ";
|
||||
}
|
||||
|
||||
// Run the actual command.
|
||||
$cmd = $cmd . "J=$(nproc) ./build_tools/precommit_checker.py " . $test
|
||||
. "; exit_code=$?; ";
|
||||
|
||||
if ($applyDiff) {
|
||||
$cmd = $cmd . "([[ \$exit_code -eq 0 ]] &&"
|
||||
. buildUpdateTestStatusCmd($diffID, $test, "pass") . ")"
|
||||
. "||" . buildUpdateTestStatusCmd($diffID, $test, "fail")
|
||||
. "; ";
|
||||
}
|
||||
|
||||
// shell command to sort the tests based on exit code and print
|
||||
// the output of the log files.
|
||||
$cat_sorted_logs = "
|
||||
while read code log_file;
|
||||
do echo \"################ cat \$log_file [exit_code : \$code] ################\";
|
||||
cat \$log_file;
|
||||
done < <(tail -n +2 LOG | sort -k7,7n -k4,4gr | awk '{print \$7,\$NF}')";
|
||||
|
||||
// Shell command to cat all log files
|
||||
$cat_all_logs = "for f in `ls t/!(run-*)`; do echo \$f;cat \$f; done";
|
||||
|
||||
// If LOG file exist use it to cat log files sorted by exit code, otherwise
|
||||
// cat everything
|
||||
$logs_cmd = "if [ -f LOG ]; then {$cat_sorted_logs}; else {$cat_all_logs}; fi";
|
||||
|
||||
$cmd = $cmd . " cat /tmp/precommit-check.log"
|
||||
. "; shopt -s extglob; {$logs_cmd}"
|
||||
. "; shopt -u extglob; [[ \$exit_code -eq 0 ]]";
|
||||
assert(strlen($cmd) > 0);
|
||||
|
||||
$run_test = array(
|
||||
"name" => "Run " . $test,
|
||||
"shell" => $cmd,
|
||||
"user" => "root",
|
||||
"parser" => "python build_tools/error_filter.py " . $test,
|
||||
);
|
||||
|
||||
$steps[] = $run_test;
|
||||
|
||||
if ($applyDiff) {
|
||||
// Clean up the user arc config we are using.
|
||||
$cleanup = array(
|
||||
"name" => "Arc cleanup",
|
||||
"shell" => "rm -f ~/.arcrc",
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
$steps[] = $cleanup;
|
||||
}
|
||||
|
||||
assert(count($steps) > 0);
|
||||
return $steps;
|
||||
}
|
||||
|
||||
function getSandcastleConfig() {
|
||||
$sandcastle_config = array();
|
||||
|
||||
// This is a case when we're executed from a continuous run. Fetch the values
|
||||
// from the environment.
|
||||
if (getenv(ENV_POST_RECEIVE_HOOK)) {
|
||||
$sandcastle_config[0] = getenv(ENV_HTTPS_APP_VALUE);
|
||||
$sandcastle_config[1] = getenv(ENV_HTTPS_TOKEN_VALUE);
|
||||
} else {
|
||||
// This is a typical `[p]arc diff` case. Fetch the values from the specific
|
||||
// configuration files.
|
||||
assert(file_exists(PRIMARY_TOKEN_FILE) ||
|
||||
file_exists(SECONDARY_TOKEN_FILE));
|
||||
|
||||
// Try the primary location first, followed by a secondary.
|
||||
if (file_exists(PRIMARY_TOKEN_FILE)) {
|
||||
$cmd = 'cat ' . PRIMARY_TOKEN_FILE;
|
||||
} else {
|
||||
$cmd = 'cat ' . SECONDARY_TOKEN_FILE;
|
||||
}
|
||||
|
||||
assert(strlen($cmd) > 0);
|
||||
$sandcastle_config = explode(':', rtrim(shell_exec($cmd)));
|
||||
}
|
||||
|
||||
// In this case be very explicit about the implications.
|
||||
if (count($sandcastle_config) != 2) {
|
||||
echo "Sandcastle configuration files don't contain valid information " .
|
||||
"or the necessary environment variables aren't defined. Unable " .
|
||||
"to validate the code changes.";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
assert(strlen($sandcastle_config[0]) > 0);
|
||||
assert(strlen($sandcastle_config[1]) > 0);
|
||||
assert(count($sandcastle_config) > 0);
|
||||
|
||||
return $sandcastle_config;
|
||||
}
|
||||
|
||||
// This function can be called either from `[p]arc diff` command or during
|
||||
// the Git post-receive hook.
|
||||
function startTestsInSandcastle($applyDiff, $workflow, $diffID) {
|
||||
// Default options don't terminate on failure, but that's what we want. In
|
||||
// the current case we use assertions intentionally as "terminate on failure
|
||||
// invariants".
|
||||
assert_options(ASSERT_BAIL, true);
|
||||
|
||||
// In case of a diff we'll send notificatios to the author. Else it'll go to
|
||||
// the entire team because failures indicate that build quality has regressed.
|
||||
$username = $applyDiff ? exec("whoami") : CONT_RUN_ALIAS;
|
||||
assert(strlen($username) > 0);
|
||||
|
||||
if ($applyDiff) {
|
||||
assert($workflow);
|
||||
assert(strlen($diffID) > 0);
|
||||
assert(is_numeric($diffID));
|
||||
}
|
||||
|
||||
// List of tests we want to run in Sandcastle.
|
||||
$tests = array("unit", "unit_non_shm", "unit_481", "clang_unit", "tsan",
|
||||
"asan", "lite_test", "valgrind", "release", "release_481",
|
||||
"clang_release", "punit", "clang_analyze", "code_cov",
|
||||
"java_build", "no_compression", "unity", "ubsan");
|
||||
|
||||
$send_email_template = array(
|
||||
'type' => 'email',
|
||||
'triggers' => array('fail'),
|
||||
'emails' => array($username . '@fb.com'),
|
||||
);
|
||||
|
||||
// Construct a job definition for each test and add it to the master plan.
|
||||
foreach ($tests as $test) {
|
||||
$stepName = "RocksDB diff " . $diffID . " test " . $test;
|
||||
|
||||
if (!$applyDiff) {
|
||||
$stepName = "RocksDB continuous integration test " . $test;
|
||||
}
|
||||
|
||||
$arg[] = array(
|
||||
"name" => $stepName,
|
||||
"report" => array($send_email_template),
|
||||
"steps" => getSteps($applyDiff, $diffID, $username, $test)
|
||||
);
|
||||
}
|
||||
|
||||
// We cannot submit the parallel execution master plan to Sandcastle and
|
||||
// need supply the job plan as a determinator. So we construct a small job
|
||||
// that will spit out the master job plan which Sandcastle will parse and
|
||||
// execute. Why compress the job definitions? Otherwise we run over the max
|
||||
// string size.
|
||||
$cmd = "echo " . base64_encode(json_encode($arg))
|
||||
. (PHP_OS == "Darwin" ?
|
||||
" | gzip -f | base64" :
|
||||
" | gzip -f | base64 -w0");
|
||||
assert(strlen($cmd) > 0);
|
||||
|
||||
$arg_encoded = shell_exec($cmd);
|
||||
assert(strlen($arg_encoded) > 0);
|
||||
|
||||
$runName = "Run diff " . $diffID . "for user " . $username;
|
||||
|
||||
if (!$applyDiff) {
|
||||
$runName = "RocksDB continuous integration build and test run";
|
||||
}
|
||||
|
||||
$command = array(
|
||||
"name" => $runName,
|
||||
"steps" => array()
|
||||
);
|
||||
|
||||
$command["steps"][] = array(
|
||||
"name" => "Generate determinator",
|
||||
"shell" => "echo " . $arg_encoded . " | base64 --decode | gzip -d"
|
||||
. " | base64 --decode",
|
||||
"determinator" => true,
|
||||
"user" => "root"
|
||||
);
|
||||
|
||||
// Submit to Sandcastle.
|
||||
$url = 'https://interngraph.intern.facebook.com/sandcastle/generate?'
|
||||
.'command=SandcastleUniversalCommand'
|
||||
.'&vcs=rocksdb-git&revision=origin%2Fmaster&type=lego'
|
||||
.'&user=' . $username . '&alias=rocksdb-precommit'
|
||||
.'&command-args=' . urlencode(json_encode($command));
|
||||
|
||||
// Fetch the configuration necessary to submit a successful HTTPS request.
|
||||
$sandcastle_config = getSandcastleConfig();
|
||||
|
||||
$app = $sandcastle_config[0];
|
||||
$token = $sandcastle_config[1];
|
||||
|
||||
$cmd = 'https_proxy= HTTPS_PROXY= curl -s -k -F app=' . $app . ' '
|
||||
. '-F token=' . $token . ' "' . $url . '"';
|
||||
|
||||
$output = shell_exec($cmd);
|
||||
assert(strlen($output) > 0);
|
||||
|
||||
// Extract Sandcastle URL from the response.
|
||||
preg_match('/url": "(.+)"/', $output, $sandcastle_url);
|
||||
|
||||
assert(count($sandcastle_url) > 0, "Unable to submit Sandcastle request.");
|
||||
assert(strlen($sandcastle_url[1]) > 0, "Unable to extract Sandcastle URL.");
|
||||
|
||||
if ($applyDiff) {
|
||||
echo "\nSandcastle URL: " . $sandcastle_url[1] . "\n";
|
||||
// Ask Phabricator to display it on the diff UI.
|
||||
postURL($diffID, $sandcastle_url[1]);
|
||||
} else {
|
||||
echo "Continuous integration started Sandcastle tests. You can look at ";
|
||||
echo "the progress at:\n" . $sandcastle_url[1] . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Continuous run cript will set the environment variable and based on that
|
||||
// we'll trigger the execution of tests in Sandcastle. In that case we don't
|
||||
// need to apply any diffs and there's no associated workflow either.
|
||||
if (getenv(ENV_POST_RECEIVE_HOOK)) {
|
||||
startTestsInSandcastle(
|
||||
false /* $applyDiff */,
|
||||
NULL /* $workflow */,
|
||||
NULL /* $diffID */);
|
||||
}
|
||||
@@ -44,7 +44,7 @@ abstract class BaseDirectoryScopedFormatLinter extends ArcanistLinter {
|
||||
$futures[$path] = $this->getFormatFuture($path, $changed);
|
||||
}
|
||||
|
||||
foreach (Futures($futures)->limit(8) as $p => $f) {
|
||||
foreach (id(new FutureIterator($futures))->limit(8) as $p => $f) {
|
||||
$this->rawLintOutput[$p] = $f->resolvex();
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -2714,7 +2714,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error):
|
||||
|
||||
# Look for < that is not surrounded by spaces. This is only
|
||||
# triggered if both sides are missing spaces, even though
|
||||
# technically should should flag if at least one side is missing a
|
||||
# technically should flag if at least one side is missing a
|
||||
# space. This is done to avoid some false positives with shifts.
|
||||
match = Search(r'[^\s<]<([^\s=<].*)', reduced_line)
|
||||
if (match and
|
||||
|
||||
@@ -42,14 +42,12 @@ class FacebookFbcodeLintEngine extends ArcanistLintEngine {
|
||||
$python_linter = new ArcanistPEP8Linter();
|
||||
$linters[] = $python_linter;
|
||||
|
||||
if (!$this->getCommitHookMode()) {
|
||||
$cpp_linters = array();
|
||||
$cpp_linters[] = $linters[] = new ArcanistCpplintLinter();
|
||||
$cpp_linters[] = $linters[] = new FbcodeCppLinter();
|
||||
$cpp_linters = array();
|
||||
$cpp_linters[] = $linters[] = new ArcanistCpplintLinter();
|
||||
$cpp_linters[] = $linters[] = new FbcodeCppLinter();
|
||||
|
||||
$clang_format_linter = new FbcodeClangFormatLinter();
|
||||
$linters[] = $clang_format_linter;
|
||||
}
|
||||
$clang_format_linter = new FbcodeClangFormatLinter();
|
||||
$linters[] = $clang_format_linter;
|
||||
|
||||
$spelling_linter = new ArcanistSpellingLinter();
|
||||
$linters[] = $spelling_linter;
|
||||
|
||||
@@ -4,18 +4,14 @@
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
class FacebookFbcodeUnitTestEngine extends ArcanistBaseUnitTestEngine {
|
||||
class FacebookFbcodeUnitTestEngine extends ArcanistUnitTestEngine {
|
||||
|
||||
public function run() {
|
||||
// Here we create a new unit test "jenkins_async_test" and promise we'll
|
||||
// update the results later.
|
||||
// Jenkins updates the results using `arc call-conduit
|
||||
// differential.updateunitresults` call. If you change the name here, also
|
||||
// make sure to change the name in Jenkins script that updates the test
|
||||
// result -- they have to be the same.
|
||||
$result = new ArcanistUnitTestResult();
|
||||
$result->setName("jenkins_async_test");
|
||||
$result->setResult(ArcanistUnitTestResult::RESULT_POSTPONED);
|
||||
return array($result);
|
||||
// For a call to `arc call-conduit differential.updateunitresults` to
|
||||
// succeed we need at least one entry here.
|
||||
$result = new ArcanistUnitTestResult();
|
||||
$result->setName("dummy_placeholder_entry");
|
||||
$result->setResult(ArcanistUnitTestResult::RESULT_PASS);
|
||||
return array($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
class FacebookFbcodeUnitTestEngine extends ArcanistBaseUnitTestEngine {
|
||||
|
||||
public function run() {
|
||||
// For a call to `arc call-conduit differential.updateunitresults` to
|
||||
// succeed we need at least one entry here.
|
||||
$result = new ArcanistUnitTestResult();
|
||||
$result->setName("dummy_placeholder_entry");
|
||||
$result->setResult(ArcanistUnitTestResult::RESULT_PASS);
|
||||
return array($result);
|
||||
}
|
||||
}
|
||||
@@ -19,22 +19,21 @@
|
||||
#
|
||||
# The PLATFORM_CCFLAGS and PLATFORM_CXXFLAGS might include the following:
|
||||
#
|
||||
# -DLEVELDB_PLATFORM_POSIX if cstdatomic is present
|
||||
# -DLEVELDB_PLATFORM_NOATOMIC if it is not
|
||||
# -DROCKSDB_PLATFORM_POSIX if posix-platform based
|
||||
# -DSNAPPY if the Snappy library is present
|
||||
# -DLZ4 if the LZ4 library is present
|
||||
# -DZSTD if the ZSTD library is present
|
||||
# -DNUMA if the NUMA library is present
|
||||
# -DTBB if the TBB library is present
|
||||
#
|
||||
# Using gflags in rocksdb:
|
||||
# Our project depends on gflags, which requires users to take some extra steps
|
||||
# before they can compile the whole repository:
|
||||
# 1. Install gflags. You may download it from here:
|
||||
# https://code.google.com/p/gflags/
|
||||
# 2. Once install, add the include path/lib path for gflags to CPATH and
|
||||
# LIBRARY_PATH respectively. If installed with default mode, the
|
||||
# lib and include path will be /usr/local/lib and /usr/local/include
|
||||
# Mac user can do this by having brew installed and running brew install gflags
|
||||
# https://gflags.github.io/gflags/ (Mac users can `brew install gflags`)
|
||||
# 2. Once installed, add the include path for gflags to your CPATH env var and
|
||||
# the lib path to LIBRARY_PATH. If installed with default settings, the lib
|
||||
# will be /usr/local/lib and the include path will be /usr/local/include
|
||||
|
||||
OUTPUT=$1
|
||||
if test -z "$OUTPUT"; then
|
||||
@@ -165,7 +164,7 @@ case "$TARGET_OS" in
|
||||
;;
|
||||
OS_ANDROID_CROSSCOMPILE)
|
||||
PLATFORM=OS_ANDROID
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_ANDROID -DLEVELDB_PLATFORM_POSIX"
|
||||
COMMON_FLAGS="$COMMON_FLAGS -fno-builtin-memcmp -D_REENTRANT -DOS_ANDROID -DROCKSDB_PLATFORM_POSIX"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS " # All pthread features are in the Android C library
|
||||
# PORT_FILES=port/android/android.cc
|
||||
CROSS_COMPILE=true
|
||||
@@ -283,7 +282,7 @@ EOF
|
||||
# Test whether numa is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
|
||||
#include <numa.h>
|
||||
#inlcude <numaif.h>
|
||||
#include <numaif.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
@@ -292,15 +291,27 @@ EOF
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -lnuma"
|
||||
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() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DTBB"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltbb"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltbb"
|
||||
fi
|
||||
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
2>/dev/null; then
|
||||
2>/dev/null; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ljemalloc"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ljemalloc"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# jemalloc is not available. Let's try tcmalloc
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null \
|
||||
-ltcmalloc 2>/dev/null; then
|
||||
-ltcmalloc 2>/dev/null; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltcmalloc"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltcmalloc"
|
||||
fi
|
||||
@@ -317,6 +328,55 @@ EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
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;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_PTHREAD_ADAPTIVE_MUTEX"
|
||||
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>
|
||||
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
|
||||
|
||||
# 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
|
||||
fi
|
||||
fi
|
||||
|
||||
# TODO(tec): Fix -Wshorten-64-to-32 errors on FreeBSD and enable the warning.
|
||||
@@ -397,3 +457,7 @@ echo "ROCKSDB_MINOR=$ROCKSDB_MINOR" >> "$OUTPUT"
|
||||
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"
|
||||
if test -n "$JEMALLOC"; then
|
||||
echo "JEMALLOC=1" >> "$OUTPUT"
|
||||
fi
|
||||
Executable
+135
@@ -0,0 +1,135 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2016, Facebook. All rights reserved.
|
||||
#
|
||||
# Overall wrapper script for RocksDB continuous builds. The implementation is a
|
||||
# trivial pulling scheme. We loop infinitely, check if any new changes have been
|
||||
# committed, if yes then trigger a Sandcastle run, and finally go to sleep again
|
||||
# for a certain interval.
|
||||
#
|
||||
|
||||
SRC_GIT_REPO=/data/git/rocksdb
|
||||
error=0
|
||||
|
||||
function log {
|
||||
DATE=`date +%Y-%m-%d:%H:%M:%S`
|
||||
echo $DATE $@
|
||||
}
|
||||
|
||||
function log_err {
|
||||
log "ERROR: $@ Error code: $error."
|
||||
}
|
||||
|
||||
function update_repo_status {
|
||||
# Update the parent first.
|
||||
pushd $SRC_GIT_REPO
|
||||
|
||||
# This is a fatal error. Something in the environment isn't right and we will
|
||||
# terminate the execution.
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "Where is $SRC_GIT_REPO?"
|
||||
exit $error
|
||||
fi
|
||||
|
||||
HTTPS_PROXY=fwdproxy:8080 git fetch -f
|
||||
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "git fetch -f failed."
|
||||
popd
|
||||
return $error
|
||||
fi
|
||||
|
||||
git update-ref refs/heads/master refs/remotes/origin/master
|
||||
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "git update-ref failed."
|
||||
popd
|
||||
return $error
|
||||
fi
|
||||
|
||||
popd
|
||||
|
||||
# We're back in an instance-specific directory. Get the latest changes.
|
||||
git pull --rebase
|
||||
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "git pull --rebase failed."
|
||||
return $error
|
||||
fi
|
||||
}
|
||||
|
||||
#
|
||||
# Execution starts here.
|
||||
#
|
||||
|
||||
# Path to the determinator from the root of the RocksDB repo.
|
||||
CONTRUN_DETERMINATOR=./arcanist_util/config/RocksDBCommonHelper.php
|
||||
|
||||
# Value of the previous commit.
|
||||
PREV_COMMIT=
|
||||
|
||||
log "Starting to monitor for new RocksDB changes ..."
|
||||
log "Running under `pwd` as `whoami`."
|
||||
|
||||
# Paranoia. Make sure that we're using the right branch.
|
||||
git checkout master
|
||||
|
||||
error=$?
|
||||
if [ ! $error -eq 0 ]; then
|
||||
log_err "This is not good. Can't checkout master. Bye-bye!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# We'll run forever and let the execution environment terminate us if we'll
|
||||
# exceed whatever timeout is set for the job.
|
||||
while true;
|
||||
do
|
||||
# Get the latest changes committed.
|
||||
update_repo_status
|
||||
|
||||
error=$?
|
||||
if [ $error -eq 0 ]; then
|
||||
LAST_COMMIT=`git log -1 | head -1 | grep commit | awk '{ print $2; }'`
|
||||
|
||||
log "Last commit is '$LAST_COMMIT', previous commit is '$PREV_COMMIT'."
|
||||
|
||||
if [ "$PREV_COMMIT" == "$LAST_COMMIT" ]; then
|
||||
log "There were no changes since the last time I checked. Going to sleep."
|
||||
else
|
||||
if [ ! -z "$LAST_COMMIT" ]; then
|
||||
log "New code has been committed or previous commit not known. " \
|
||||
"Will trigger the tests."
|
||||
|
||||
PREV_COMMIT=$LAST_COMMIT
|
||||
log "Updated previous commit to '$PREV_COMMIT'."
|
||||
|
||||
#
|
||||
# This is where we'll trigger the Sandcastle run. The values for
|
||||
# HTTPS_APP_VALUE and HTTPS_APP_VALUE will be set in the container we're
|
||||
# running in.
|
||||
#
|
||||
POST_RECEIVE_HOOK=1 php $CONTRUN_DETERMINATOR
|
||||
|
||||
error=$?
|
||||
if [ $error -eq 0 ]; then
|
||||
log "Sandcastle run successfully triggered."
|
||||
else
|
||||
log_err "Failed to trigger Sandcastle run."
|
||||
fi
|
||||
else
|
||||
log_err "Previous commit not updated. Don't know what the last one is."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log_err "Getting latest changes failed. Will skip running tests for now."
|
||||
fi
|
||||
|
||||
# Always sleep, even if errors happens while trying to determine the latest
|
||||
# commit. This will prevent us terminating in case of transient errors.
|
||||
log "Will go to sleep for 5 minutes."
|
||||
sleep 5m
|
||||
done
|
||||
@@ -1,16 +1,17 @@
|
||||
GCC_BASE=/mnt/gvfs/third-party2/gcc/ebc96bc2fb751b5a0300b8d91a95bdf24ac1d88b/4.8.1/centos6-native/cc6c9dc
|
||||
CLANG_BASE=/mnt/gvfs/third-party2/clang/fc904e50a9266b9d7b98cae1993afa0c5aae1440/3.7.1/centos6-native/9d9ecb9/
|
||||
LIBGCC_BASE=/mnt/gvfs/third-party2/libgcc/ea2fd1278810d3af2ea52218d2767e09d786dbd0/4.8.1/gcc-4.8.1-glibc-2.17/8aac7fc
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/f5484f168c0e4d19823d41df052c5870c6e575a4/2.17/gcc-4.8.1-glibc-2.17/99df8fc
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/cbf6f1f209e5bd160bdc5d971744e039f36b1566/1.1.3/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/6d39cb54708049f527e713ad19f2aadb9d3667e8/1.2.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/2ddd45f0853bfc8bb1c27f0f447236a1a26c338a/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/6858fac689e0f92e584224d91bdb0e39f6c8320d/r131/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/cb6c4880fcb4fee471574ba6af63a3882155a16a/0.5.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/c7275a4ceae0aca0929e56964a31dafc53c1ee96/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/40791a3fef9206a77f2c4bc51f8169e5bf10d68e/master/gcc-4.8.1-glibc-2.17/8d31e51
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/ae54a5ed22cdabb1c6446dce4e8ffae5b4446d73/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/303048f72efc92ae079e62dfc84823401aecfd94/trunk/gcc-4.8.1-glibc-2.17/675d945
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/1a48835975c66d30e47770ec419758ed3b9ba010/3.10.62-62_fbk17_03959_ge29cc63/gcc-4.8.1-glibc-2.17/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/a5b8152b2a15ce8a98808cf954fbccec825a97bc/2.25/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/af85c56f424cd5edfc2c97588299b44ecdec96bb/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
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
|
||||
GLIBC_BASE=/mnt/gvfs/third-party2/glibc/d282e6e8f3d20f4e40a516834847bdc038e07973/2.17/gcc-4.8.1-glibc-2.17/99df8fc
|
||||
SNAPPY_BASE=/mnt/gvfs/third-party2/snappy/8c38a4c1e52b4c2cc8a9cdc31b9c947ed7dbfcb4/1.1.3/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZLIB_BASE=/mnt/gvfs/third-party2/zlib/0882df3713c7a84f15abe368dc004581f20b39d7/1.2.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
BZIP2_BASE=/mnt/gvfs/third-party2/bzip2/740325875f6729f42d28deaa2147b0854f3a347e/1.0.6/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LZ4_BASE=/mnt/gvfs/third-party2/lz4/0e790b441e2d9acd68d51e1d2e028f88c6a79ddf/r131/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
ZSTD_BASE=/mnt/gvfs/third-party2/zstd/9455f75ff7f4831dc9fda02a6a0f8c68922fad8f/1.0.0/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
GFLAGS_BASE=/mnt/gvfs/third-party2/gflags/f001a51b2854957676d07306ef3abf67186b5c8b/2.1.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
JEMALLOC_BASE=/mnt/gvfs/third-party2/jemalloc/fc8a13ca1fffa4d0765c716c5a0b49f0c107518f/master/gcc-4.8.1-glibc-2.17/8d31e51
|
||||
NUMA_BASE=/mnt/gvfs/third-party2/numa/17c514c4d102a25ca15f4558be564eeed76f4b6a/2.0.8/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
LIBUNWIND_BASE=/mnt/gvfs/third-party2/libunwind/ad576de2a1ea560c4d3434304f0fc4e079bede42/trunk/gcc-4.8.1-glibc-2.17/675d945
|
||||
TBB_BASE=/mnt/gvfs/third-party2/tbb/9d9a554877d0c5bef330fe818ab7178806dd316a/4.0_update2/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/7c111ff27e0c466235163f00f280a9d617c3d2ec/4.0.9-36_fbk5_2933_gd092e3f/gcc-4.8.1-glibc-2.17/da39a3e
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/b7fd454c4b10c6a81015d4524ed06cdeab558490/2.26/centos6-native/da39a3e
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/d7f4d4d86674a57668e3a96f76f0e17dd0eb8765/3.8.1/gcc-4.8.1-glibc-2.17/c3f970a
|
||||
@@ -0,0 +1,167 @@
|
||||
# Copyright (c) 2016-present, Facebook, Inc. All rights reserved.
|
||||
# This source code is licensed under the BSD-style license found in the
|
||||
# LICENSE file in the root directory of this source tree. An additional grant
|
||||
# of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
'''Filter for error messages in test output:
|
||||
- Receives merged stdout/stderr from test on stdin
|
||||
- Finds patterns of known error messages for test name (first argument)
|
||||
- Prints those error messages to stdout
|
||||
'''
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
|
||||
class ErrorParserBase(object):
|
||||
def parse_error(self, line):
|
||||
'''Parses a line of test output. If it contains an error, returns a
|
||||
formatted message describing the error; otherwise, returns None.
|
||||
Subclasses must override this method.
|
||||
'''
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class GTestErrorParser(ErrorParserBase):
|
||||
'''A parser that remembers the last test that began running so it can print
|
||||
that test's name upon detecting failure.
|
||||
'''
|
||||
_GTEST_NAME_PATTERN = re.compile(r'\[ RUN \] (\S+)$')
|
||||
# format: '<filename or "unknown file">:<line #>: Failure'
|
||||
_GTEST_FAIL_PATTERN = re.compile(r'(unknown file|\S+:\d+): Failure$')
|
||||
|
||||
def __init__(self):
|
||||
self._last_gtest_name = 'Unknown test'
|
||||
|
||||
def parse_error(self, line):
|
||||
gtest_name_match = self._GTEST_NAME_PATTERN.match(line)
|
||||
if gtest_name_match:
|
||||
self._last_gtest_name = gtest_name_match.group(1)
|
||||
return None
|
||||
gtest_fail_match = self._GTEST_FAIL_PATTERN.match(line)
|
||||
if gtest_fail_match:
|
||||
return '%s failed: %s' % (
|
||||
self._last_gtest_name, gtest_fail_match.group(1))
|
||||
return None
|
||||
|
||||
|
||||
class MatchErrorParser(ErrorParserBase):
|
||||
'''A simple parser that returns the whole line if it matches the pattern.
|
||||
'''
|
||||
def __init__(self, pattern):
|
||||
self._pattern = re.compile(pattern)
|
||||
|
||||
def parse_error(self, line):
|
||||
if self._pattern.match(line):
|
||||
return line
|
||||
return None
|
||||
|
||||
|
||||
class CompilerErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# format: '<filename>:<line #>:<column #>: error: <error msg>'
|
||||
super(CompilerErrorParser, self).__init__(r'\S+:\d+:\d+: error:')
|
||||
|
||||
|
||||
class ScanBuildErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(ScanBuildErrorParser, self).__init__(
|
||||
r'scan-build: \d+ bugs found.$')
|
||||
|
||||
|
||||
class DbCrashErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(DbCrashErrorParser, self).__init__(r'\*\*\*.*\^$|TEST FAILED.')
|
||||
|
||||
|
||||
class WriteStressErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(WriteStressErrorParser, self).__init__(
|
||||
r'ERROR: write_stress died with exitcode=\d+')
|
||||
|
||||
|
||||
class AsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(AsanErrorParser, self).__init__(
|
||||
r'==\d+==ERROR: AddressSanitizer:')
|
||||
|
||||
|
||||
class UbsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# format: '<filename>:<line #>:<column #>: runtime error: <error msg>'
|
||||
super(UbsanErrorParser, self).__init__(r'\S+:\d+:\d+: runtime error:')
|
||||
|
||||
|
||||
class ValgrindErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
# just grab the summary, valgrind doesn't clearly distinguish errors
|
||||
# from other log messages.
|
||||
super(ValgrindErrorParser, self).__init__(r'==\d+== ERROR SUMMARY:')
|
||||
|
||||
|
||||
class CompatErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(CompatErrorParser, self).__init__(r'==== .*[Ee]rror.* ====$')
|
||||
|
||||
|
||||
class TsanErrorParser(MatchErrorParser):
|
||||
def __init__(self):
|
||||
super(TsanErrorParser, self).__init__(r'WARNING: ThreadSanitizer:')
|
||||
|
||||
|
||||
_TEST_NAME_TO_PARSERS = {
|
||||
'punit': [CompilerErrorParser, GTestErrorParser],
|
||||
'unit': [CompilerErrorParser, GTestErrorParser],
|
||||
'release': [CompilerErrorParser, GTestErrorParser],
|
||||
'unit_481': [CompilerErrorParser, GTestErrorParser],
|
||||
'release_481': [CompilerErrorParser, GTestErrorParser],
|
||||
'clang_unit': [CompilerErrorParser, GTestErrorParser],
|
||||
'clang_release': [CompilerErrorParser, GTestErrorParser],
|
||||
'clang_analyze': [CompilerErrorParser, ScanBuildErrorParser],
|
||||
'code_cov': [CompilerErrorParser, GTestErrorParser],
|
||||
'unity': [CompilerErrorParser, GTestErrorParser],
|
||||
'lite': [CompilerErrorParser],
|
||||
'lite_test': [CompilerErrorParser, GTestErrorParser],
|
||||
'stress_crash': [CompilerErrorParser, DbCrashErrorParser],
|
||||
'write_stress': [CompilerErrorParser, WriteStressErrorParser],
|
||||
'asan': [CompilerErrorParser, GTestErrorParser, AsanErrorParser],
|
||||
'asan_crash': [CompilerErrorParser, AsanErrorParser, DbCrashErrorParser],
|
||||
'ubsan': [CompilerErrorParser, GTestErrorParser, UbsanErrorParser],
|
||||
'ubsan_crash': [CompilerErrorParser, UbsanErrorParser, DbCrashErrorParser],
|
||||
'valgrind': [CompilerErrorParser, GTestErrorParser, ValgrindErrorParser],
|
||||
'tsan': [CompilerErrorParser, GTestErrorParser, TsanErrorParser],
|
||||
'format_compatible': [CompilerErrorParser, CompatErrorParser],
|
||||
'run_format_compatible': [CompilerErrorParser, CompatErrorParser],
|
||||
'no_compression': [CompilerErrorParser, GTestErrorParser],
|
||||
'run_no_compression': [CompilerErrorParser, GTestErrorParser],
|
||||
'regression': [CompilerErrorParser],
|
||||
'run_regression': [CompilerErrorParser],
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 2:
|
||||
return 'Usage: %s <test name>' % sys.argv[0]
|
||||
test_name = sys.argv[1]
|
||||
if test_name not in _TEST_NAME_TO_PARSERS:
|
||||
return 'Unknown test name: %s' % test_name
|
||||
|
||||
error_parsers = []
|
||||
for parser_cls in _TEST_NAME_TO_PARSERS[test_name]:
|
||||
error_parsers.append(parser_cls())
|
||||
|
||||
for line in sys.stdin:
|
||||
line = line.strip()
|
||||
for error_parser in error_parsers:
|
||||
error_msg = error_parser.parse_error(line)
|
||||
if error_msg is not None:
|
||||
print(error_msg)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -9,7 +9,7 @@ source "$BASEDIR/dependencies_4.8.1.sh"
|
||||
|
||||
# location of libgcc
|
||||
LIBGCC_INCLUDE="$LIBGCC_BASE/include"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/libs"
|
||||
LIBGCC_LIBS=" -L $LIBGCC_BASE/lib"
|
||||
|
||||
# location of glibc
|
||||
GLIBC_INCLUDE="$GLIBC_BASE/include"
|
||||
@@ -48,13 +48,17 @@ NUMA_LIB=" $NUMA_BASE/lib/libnuma.a"
|
||||
# location of libunwind
|
||||
LIBUNWIND="$LIBUNWIND_BASE/lib/libunwind.a"
|
||||
|
||||
# location of tbb
|
||||
TBB_INCLUDE=" -isystem $TBB_BASE/include/"
|
||||
TBB_LIBS="$TBB_BASE/lib/libtbb.a"
|
||||
|
||||
# use Intel SSE support for checksum calculations
|
||||
export USE_SSE=1
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP2_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE"
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP2_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
@@ -66,6 +70,7 @@ if [ -z "$USE_CLANG" ]; then
|
||||
CFLAGS="-B$BINUTILS/gold -m64 -mtune=generic"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
else
|
||||
# clang
|
||||
CLANG_BIN="$CLANG_BASE/bin"
|
||||
@@ -89,13 +94,15 @@ fi
|
||||
|
||||
CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE"
|
||||
CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2 -DLZ4 -DZSTD -DNUMA"
|
||||
CFLAGS+=" -DSNAPPY -DGFLAGS=google -DZLIB -DBZIP2 -DLZ4 -DZSTD -DNUMA -DTBB"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB"
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP2_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/gcc-4.8.1-glibc-2.17/lib"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
|
||||
|
||||
Executable
+7936
File diff suppressed because it is too large.
Load diff
@@ -12,17 +12,15 @@ import re
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
#
|
||||
# Simple logger
|
||||
#
|
||||
|
||||
|
||||
class Log:
|
||||
|
||||
LOG_FILE = "/tmp/precommit-check.log"
|
||||
|
||||
def __init__(self):
|
||||
self.filename = Log.LOG_FILE
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.f = open(self.filename, 'w+', 0)
|
||||
|
||||
def caption(self, str):
|
||||
@@ -52,9 +50,9 @@ class Log:
|
||||
|
||||
class Env(object):
|
||||
|
||||
def __init__(self, tests):
|
||||
def __init__(self, logfile, tests):
|
||||
self.tests = tests
|
||||
self.log = Log()
|
||||
self.log = Log(logfile)
|
||||
|
||||
def shell(self, cmd, path=os.getcwd()):
|
||||
if path:
|
||||
@@ -87,15 +85,16 @@ class Env(object):
|
||||
|
||||
class PreCommitChecker(Env):
|
||||
|
||||
def __init__(self, tests):
|
||||
Env.__init__(self, tests)
|
||||
def __init__(self, args):
|
||||
Env.__init__(self, args.logfile, args.tests)
|
||||
self.ignore_failure = args.ignore_failure
|
||||
|
||||
#
|
||||
# Get commands for a given job from the determinator file
|
||||
#
|
||||
def get_commands(self, test):
|
||||
status, out = self.GetOutput(
|
||||
"build_tools/rocksdb-lego-determinator %s" % test, ".")
|
||||
"RATIO=1 build_tools/rocksdb-lego-determinator %s" % test, ".")
|
||||
return status, out
|
||||
|
||||
#
|
||||
@@ -144,6 +143,7 @@ class PreCommitChecker(Env):
|
||||
self.print_row("TEST", "RESULT")
|
||||
self.print_separator()
|
||||
|
||||
result = True
|
||||
for test in self.tests:
|
||||
start_time = time.time()
|
||||
self.print_test(test)
|
||||
@@ -152,11 +152,14 @@ class PreCommitChecker(Env):
|
||||
if not result:
|
||||
self.log.error("Error running test %s" % test)
|
||||
self.print_result("FAIL (%dm)" % elapsed_min)
|
||||
return False
|
||||
self.print_result("PASS (%dm)" % elapsed_min)
|
||||
if not self.ignore_failure:
|
||||
return False
|
||||
result = False
|
||||
else:
|
||||
self.print_result("PASS (%dm)" % elapsed_min)
|
||||
|
||||
self.print_separator()
|
||||
return True
|
||||
return result
|
||||
|
||||
#
|
||||
# Print a line
|
||||
@@ -182,17 +185,24 @@ class PreCommitChecker(Env):
|
||||
#
|
||||
parser = argparse.ArgumentParser(description='RocksDB pre-commit checker.')
|
||||
|
||||
# --log <logfile>
|
||||
parser.add_argument('--logfile', default='/tmp/precommit-check.log',
|
||||
help='Log file. Default is /tmp/precommit-check.log')
|
||||
# --ignore_failure
|
||||
parser.add_argument('--ignore_failure', action='store_true', default=False,
|
||||
help='Stop when an error occurs')
|
||||
# <test ....>
|
||||
parser.add_argument('test', nargs='+',
|
||||
help='CI test(s) to run. e.g: unit punit asan tsan')
|
||||
|
||||
print("Please follow log %s" % Log.LOG_FILE)
|
||||
parser.add_argument('tests', nargs='+',
|
||||
help='CI test(s) to run. e.g: unit punit asan tsan ubsan')
|
||||
|
||||
args = parser.parse_args()
|
||||
checker = PreCommitChecker(args.test)
|
||||
checker = PreCommitChecker(args)
|
||||
|
||||
print("Please follow log %s" % checker.log.filename)
|
||||
|
||||
if not checker.run_tests():
|
||||
print("Error running tests. Please check log file %s" % Log.LOG_FILE)
|
||||
print("Error running tests. Please check log file %s"
|
||||
% checker.log.filename)
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
@@ -8,7 +8,7 @@
|
||||
# Input Value
|
||||
# -------------------------------------------------------------------------
|
||||
# EMAIL Email address to report on trigger conditions
|
||||
# ONCAL Email address to raise a task on failure
|
||||
# ONCALL Email address to raise a task on failure
|
||||
# TRIGGER Trigger conditions for email. Valid values are fail, warn, all
|
||||
# SUBSCRIBER Email addresss to add as subscriber for task
|
||||
#
|
||||
@@ -42,6 +42,9 @@ if [ ! -z $ONCALL ]; then
|
||||
},"
|
||||
fi
|
||||
|
||||
# For now, create the tasks using only the dedicated task creation tool.
|
||||
CREATE_TASK=
|
||||
|
||||
REPORT=
|
||||
if [[ ! -z $REPORT_EMAIL || ! -z $CREATE_TASK ]]; then
|
||||
REPORT="'report': [
|
||||
@@ -56,19 +59,51 @@ fi
|
||||
CLEANUP_ENV="
|
||||
{
|
||||
'name':'Cleanup environment',
|
||||
'shell':'rm -rf /dev/shm/rocksdb && mkdir /dev/shm/rocksdb && make clean',
|
||||
'shell':'rm -rf /dev/shm/rocksdb && mkdir /dev/shm/rocksdb && (chmod +t /dev/shm || true) && make clean',
|
||||
'user':'root'
|
||||
}"
|
||||
|
||||
# We will eventually set the RATIO to 1, but we want do this
|
||||
# in steps. RATIO=$(nproc) will make it work as J=1
|
||||
if [ -z $RATIO ]; then
|
||||
RATIO=$(nproc)
|
||||
fi
|
||||
|
||||
if [ -z $PARALLEL_J ]; then
|
||||
PARALLEL_J="J=$(expr $(nproc) / ${RATIO})"
|
||||
fi
|
||||
|
||||
if [ -z $PARALLEL_j ]; then
|
||||
PARALLEL_j="-j$(expr $(nproc) / ${RATIO})"
|
||||
fi
|
||||
|
||||
PARALLELISM="$PARALLEL_J $PARALLEL_j"
|
||||
|
||||
DEBUG="OPT=-g"
|
||||
SHM="TEST_TMPDIR=/dev/shm/rocksdb"
|
||||
GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
|
||||
ASAN="COMPILE_WITH_ASAN=1"
|
||||
CLANG="USE_CLANG=1"
|
||||
LITE="OPT=-DROCKSDB_LITE"
|
||||
LITE="OPT=\"-DROCKSDB_LITE -g\""
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
UBSAN="COMPILE_WITH_UBSAN=1"
|
||||
DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
|
||||
PARSER="'parser':'egrep \'Failure|^#|Abort|Expected|Actual|GoogleTestFailure|^==\''"
|
||||
HTTP_PROXY="https_proxy=http://fwdproxy.29.prn1:8080 http_proxy=http://fwdproxy.29.prn1:8080 ftp_proxy=http://fwdproxy.29.prn1:8080"
|
||||
SETUP_JAVA_ENV="export $HTTP_PROXY; export JAVA_HOME=/usr/local/jdk-7u10-64/; export PATH=\$PATH:\$JAVA_HOME/bin"
|
||||
PARSER="'parser':'python build_tools/error_filter.py $1'"
|
||||
|
||||
CONTRUN_NAME="ROCKSDB_CONTRUN_NAME"
|
||||
|
||||
# This code is getting called under various scenarios. What we care about is to
|
||||
# understand when it's called from nightly contruns because in that case we'll
|
||||
# create tasks for any failures. To follow the existing pattern, we'll check
|
||||
# the value of $ONCALL. If it's a diff then just call `false` to make sure
|
||||
# that errors will be properly propagated to the caller.
|
||||
if [ ! -z $ONCALL ]; then
|
||||
TASK_CREATION_TOOL="/usr/local/bin/mysql_mtr_filter --rocksdb"
|
||||
else
|
||||
TASK_CREATION_TOOL="false"
|
||||
fi
|
||||
|
||||
ARTIFACTS=" 'artifacts': [
|
||||
{
|
||||
@@ -107,14 +142,14 @@ PARALLEL_UNIT_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug version',
|
||||
'shell':'$DEBUG make -j$(nproc) all && $SHM make check > /dev/null 2>&1 || cat t/log-*',
|
||||
'shell':'$DEBUG make -j$(nproc) all && $SHM make check > /dev/null 2>&1 || $CONTRUN_NAME=punit_check $TASK_CREATION_TOOL || cat t/log-*',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug version under gcc-4.8.1',
|
||||
'shell':'$GCC_481 $DEBUG make -j$(nproc) all && $SHM make check > /dev/null 2>&1 || cat t/log-*',
|
||||
'shell':'$GCC_481 $DEBUG make -j$(nproc) all && $SHM make check > /dev/null 2>&1 || $CONTRUN_NAME=punit_check_gcc481 $TASK_CREATION_TOOL || cat t/log-*',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -134,7 +169,49 @@ UNIT_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug version',
|
||||
'shell':'$SHM $DEBUG make J=1 check',
|
||||
'shell':'$SHM $DEBUG make $PARALLELISM check || $CONTRUN_NAME=check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB unit test not under /dev/shm
|
||||
#
|
||||
UNIT_TEST_NON_SHM_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Unit Test',
|
||||
'oncall':'$ONCALL',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug version',
|
||||
'timeout': 86400,
|
||||
'shell':'$DEBUG make $PARALLELISM check || $CONTRUN_NAME=non_shm_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB release build and unit tests
|
||||
#
|
||||
RELEASE_BUILD_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Release Build',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB release',
|
||||
'shell':'make $PARALLEL_j release || $CONTRUN_NAME=release $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -154,7 +231,27 @@ UNIT_TEST_COMMANDS_481="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug version',
|
||||
'shell':'$SHM $GCC_481 $DEBUG make J=1 check',
|
||||
'shell':'$SHM $GCC_481 $DEBUG make $PARALLELISM check || $CONTRUN_NAME=unit_gcc_481_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB release build and unit tests
|
||||
#
|
||||
RELEASE_BUILD_COMMANDS_481="[
|
||||
{
|
||||
'name':'Rocksdb Release on GCC 4.8.1',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB release on GCC 4.8.1',
|
||||
'shell':'$GCC_481 make $PARALLEL_j release || $CONTRUN_NAME=release_gcc481 $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -174,7 +271,27 @@ CLANG_UNIT_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug',
|
||||
'shell':'$CLANG $SHM $DEBUG make J=1 check',
|
||||
'shell':'$CLANG $SHM $DEBUG make $PARALLELISM check || $CONTRUN_NAME=clang_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB release build with CLANG
|
||||
#
|
||||
CLANG_RELEASE_BUILD_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb CLANG Release Build',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB release',
|
||||
'shell':'$CLANG make $PARALLEL_j release|| $CONTRUN_NAME=clang_release $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -194,7 +311,7 @@ CLANG_ANALYZE_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'RocksDB build and analyze',
|
||||
'shell':'$CLANG $SHM $DEBUG make J=1 analyze',
|
||||
'shell':'$CLANG $SHM $DEBUG make $PARALLEL_j analyze || $CONTRUN_NAME=clang_analyze $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -214,7 +331,7 @@ CODE_COV_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build, test and collect code coverage info',
|
||||
'shell':'$SHM $DEBUG make J=1 coverage',
|
||||
'shell':'$SHM $DEBUG make $PARALLELISM coverage || $CONTRUN_NAME=coverage $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -234,7 +351,7 @@ UNITY_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build, test unity test',
|
||||
'shell':'$SHM $DEBUG V=1 make J=1 unity_test',
|
||||
'shell':'$SHM $DEBUG V=1 make J=1 unity_test || $CONTRUN_NAME=unity_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -254,7 +371,7 @@ LITE_BUILD_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB debug version',
|
||||
'shell':'$LITE $DEBUG make J=1 static_lib',
|
||||
'shell':'$LITE make J=1 static_lib || $CONTRUN_NAME=lite_static_lib $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -274,7 +391,7 @@ LITE_UNIT_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB debug version',
|
||||
'shell':'$SHM $LITE $DEBUG make J=1 check',
|
||||
'shell':'$SHM $LITE make J=1 check || $CONTRUN_NAME=lite_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -295,14 +412,14 @@ STRESS_CRASH_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'$SHM $DEBUG make J=1 db_stress',
|
||||
'shell':'$SHM $DEBUG make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug crash tests',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG make J=1 crash_test',
|
||||
'shell':'$SHM $DEBUG make J=1 crash_test || $CONTRUN_NAME=crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -323,7 +440,7 @@ WRITE_STRESS_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB write stress tests',
|
||||
'shell':'make write_stress && python tools/write_stress_runner.py --runtime_sec=3600 --db=/tmp/rocksdb_write_stress',
|
||||
'shell':'make write_stress && python tools/write_stress_runner.py --runtime_sec=3600 --db=/tmp/rocksdb_write_stress || $CONTRUN_NAME=write_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -345,7 +462,7 @@ ASAN_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Test RocksDB debug under ASAN',
|
||||
'shell':'set -o pipefail && $SHM $ASAN $DEBUG make J=1 asan_check |& /usr/facebook/ops/scripts/asan_symbolize.py -d',
|
||||
'shell':'set -o pipefail && ($SHM $ASAN $DEBUG make $PARALLELISM asan_check || $CONTRUN_NAME=asan_check $TASK_CREATION_TOOL) |& /usr/facebook/ops/scripts/asan_symbolize.py -d',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -367,7 +484,49 @@ ASAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug asan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG make J=1 asan_crash_test',
|
||||
'shell':'$SHM $DEBUG make J=1 asan_crash_test || $CONTRUN_NAME=asan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB test under undefined behavior sanitizer
|
||||
#
|
||||
UBSAN_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Unit Test under UBSAN',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Test RocksDB debug under UBSAN',
|
||||
'shell':'set -o pipefail && $SHM $UBSAN $DEBUG make $PARALLELISM ubsan_check || $CONTRUN_NAME=ubsan_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB crash testing under udnefined behavior sanitizer
|
||||
#
|
||||
UBSAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb crash test under UBSAN',
|
||||
'oncall':'$ONCALL',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug ubsan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG make J=1 ubsan_crash_test || $CONTRUN_NAME=ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -383,11 +542,13 @@ VALGRIND_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Unit Test under valgrind',
|
||||
'oncall':'$ONCALL',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Run RocksDB debug unit tests',
|
||||
'shell':'$DISABLE_JEMALLOC $SHM $DEBUG make valgrind_check',
|
||||
'timeout': 86400,
|
||||
'shell':'$DISABLE_JEMALLOC $SHM $DEBUG make $PARALLELISM valgrind_check || $CONTRUN_NAME=valgrind_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -403,11 +564,13 @@ TSAN_UNIT_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Unit Test under TSAN',
|
||||
'oncall':'$ONCALL',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Run RocksDB debug unit test',
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN make J=1 check',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN make $PARALLELISM check || $CONTRUN_NAME=tsan_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -429,7 +592,7 @@ TSAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Compile and run',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN make J=1 crash_test',
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN CRASH_TEST_KILL_ODD=1887 make J=1 crash_test || $CONTRUN_NAME=tsan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -438,8 +601,6 @@ TSAN_CRASH_TEST_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
TSAN_CRASH_TEST_COMMANDS=$DISABLE_COMMANDS
|
||||
|
||||
#
|
||||
# RocksDB format compatible
|
||||
#
|
||||
@@ -462,9 +623,9 @@ run_format_compatible()
|
||||
if [ -e "build_detect_platform" ]
|
||||
then
|
||||
sed "s/tcmalloc/nothingnothingnothing/g" build_detect_platform > $TEST_TMPDIR/temp_build_file
|
||||
rm -rf build_detect_platform
|
||||
rm -rf build_detect_platform
|
||||
cp $TEST_TMPDIR/temp_build_file build_detect_platform
|
||||
chmod +x build_detect_platform
|
||||
chmod +x build_detect_platform
|
||||
fi
|
||||
|
||||
make ldb -j32
|
||||
@@ -493,7 +654,7 @@ FORMAT_COMPATIBLE_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Run RocksDB debug unit test',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_format_compatible',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_format_compatible || $CONTRUN_NAME=run_format_compatible $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -526,7 +687,7 @@ NO_COMPRESSION_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Run RocksDB debug unit test',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_no_compression',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_no_compression || $CONTRUN_NAME=run_no_compression $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -583,7 +744,7 @@ REGRESSION_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Make and run script',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_regression',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_regression || $CONTRUN_NAME=run_regression $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -592,6 +753,27 @@ REGRESSION_COMMANDS="[
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB Java build
|
||||
#
|
||||
JAVA_BUILD_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Java Build',
|
||||
'oncall':'$ONCALL',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB for Java',
|
||||
'shell':'$SETUP_JAVA_ENV; $SHM make rocksdbjava || $CONTRUN_NAME=rocksdbjava $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
|
||||
case $1 in
|
||||
punit)
|
||||
echo $PARALLEL_UNIT_TEST_COMMANDS
|
||||
@@ -599,12 +781,24 @@ case $1 in
|
||||
unit)
|
||||
echo $UNIT_TEST_COMMANDS
|
||||
;;
|
||||
unit_non_shm)
|
||||
echo $UNIT_TEST_NON_SHM_COMMANDS
|
||||
;;
|
||||
release)
|
||||
echo $RELEASE_BUILD_COMMANDS
|
||||
;;
|
||||
unit_481)
|
||||
echo $UNIT_TEST_COMMANDS_481
|
||||
;;
|
||||
release_481)
|
||||
echo $RELEASE_BUILD_COMMANDS_481
|
||||
;;
|
||||
clang_unit)
|
||||
echo $CLANG_UNIT_TEST_COMMANDS
|
||||
;;
|
||||
clang_release)
|
||||
echo $CLANG_RELEASE_BUILD_COMMANDS
|
||||
;;
|
||||
clang_analyze)
|
||||
echo $CLANG_ANALYZE_COMMANDS
|
||||
;;
|
||||
@@ -632,6 +826,12 @@ case $1 in
|
||||
asan_crash)
|
||||
echo $ASAN_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
ubsan)
|
||||
echo $UBSAN_TEST_COMMANDS
|
||||
;;
|
||||
ubsan_crash)
|
||||
echo $UBSAN_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
valgrind)
|
||||
echo $VALGRIND_TEST_COMMANDS
|
||||
;;
|
||||
@@ -659,6 +859,9 @@ case $1 in
|
||||
run_regression)
|
||||
run_regression
|
||||
;;
|
||||
java_build)
|
||||
echo $JAVA_BUILD_TEST_COMMANDS
|
||||
;;
|
||||
*)
|
||||
echo "Invalid determinator command"
|
||||
;;
|
||||
|
||||
@@ -9,7 +9,7 @@ Param(
|
||||
[string]$WorkFolder = "", # Direct tests to use that folder
|
||||
[int]$Limit = -1, # -1 means run all otherwise limit for testing purposes
|
||||
[string]$Exclude = "", # Expect a comma separated list, no spaces
|
||||
[string]$Run = "db_test", # Run db_test|tests|testname1,testname2...
|
||||
[string]$Run = "db_test", # Run db_test|db_test2|tests|testname1,testname2...
|
||||
# Number of async tasks that would run concurrently. Recommend a number below 64.
|
||||
# However, CPU utlization really depends on the storage media. Recommend ram based disk.
|
||||
# a value of 1 will run everything serially
|
||||
@@ -46,15 +46,25 @@ if($WorkFolder -eq "") {
|
||||
$Env:TEST_TMPDIR = $WorkFolder
|
||||
}
|
||||
|
||||
Write-Output "Root: $RootFolder, WorkFolder: $WorkFolder"
|
||||
|
||||
# Use JEMALLOC executables
|
||||
if($EnableJE) {
|
||||
$db_test = -Join ($BinariesFolder, "db_test_je.exe")
|
||||
} else {
|
||||
$db_test = -Join ($BinariesFolder, "db_test.exe")
|
||||
if($Run -ceq "db_test" -or
|
||||
$Run -ceq "db_test2" ) {
|
||||
|
||||
$file_name = $Run
|
||||
|
||||
if($EnableJE) {
|
||||
$file_name += "_je"
|
||||
}
|
||||
|
||||
$file_name += ".exe"
|
||||
|
||||
$db_test = -Join ($BinariesFolder, $file_name)
|
||||
|
||||
Write-Output "Binaries: $BinariesFolder db_test: $db_test"
|
||||
}
|
||||
|
||||
Write-Output "Root: $RootFolder, WorkFolder: $WorkFolder"
|
||||
Write-Output "Binaries: $BinariesFolder db_test: $db_test"
|
||||
|
||||
#Exclusions that we do not want to run
|
||||
$ExcludeTests = New-Object System.Collections.Generic.HashSet[string]
|
||||
@@ -153,7 +163,8 @@ function Discover-TestBinaries([string]$Pattern, $HashTable) {
|
||||
|
||||
$TestsToRun = [ordered]@{}
|
||||
|
||||
if($Run -ceq "db_test") {
|
||||
if($Run -ceq "db_test" -or
|
||||
$Run -ceq "db_test2") {
|
||||
Normalize-DbTests -HashTable $TestsToRun
|
||||
} elseif($Run -ceq "tests") {
|
||||
if($EnableJE) {
|
||||
@@ -225,7 +236,8 @@ function RunJobs($TestToLog, [int]$ConcurrencyVal, [bool]$AddForRerun)
|
||||
Write-Host "Starting $k"
|
||||
$log_path = ($TestToLog.$k)
|
||||
|
||||
if($Run -ceq "db_test") {
|
||||
if($Run -ceq "db_test" -or
|
||||
$Run -ceq "db_test2") {
|
||||
$job = Start-Job -Name $k -ScriptBlock $InvokeTestCase -ArgumentList @($db_test,$k,$log_path)
|
||||
} else {
|
||||
[string]$Exe = -Join ($BinariesFolder, $k)
|
||||
|
||||
@@ -65,15 +65,15 @@ echo "Writing dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/4.9.x/centos6-native/*/`
|
||||
CLANG_BASE=`ls -d1 /mnt/gvfs/third-party2/clang/fc904e50a9266b9d7b98cae1993afa0c5aae1440/3.7.1/centos6-native/*/ | head -n1`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos6-native/*/`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
|
||||
# Libraries locations
|
||||
get_lib_base libgcc 4.9.x
|
||||
get_lib_base glibc 2.20
|
||||
get_lib_base snappy LATEST
|
||||
get_lib_base glibc 2.20
|
||||
get_lib_base snappy LATEST gcc-4.9-glibc-2.20
|
||||
get_lib_base zlib LATEST
|
||||
get_lib_base bzip2 LATEST
|
||||
get_lib_base lz4 LATEST
|
||||
@@ -82,10 +82,11 @@ 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-4.9-glibc-2.20
|
||||
|
||||
get_lib_base kernel-headers LATEST
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
get_lib_base valgrind LATEST
|
||||
get_lib_base valgrind 3.10.0 gcc-4.9-glibc-2.20
|
||||
|
||||
git diff $OUTPUT
|
||||
|
||||
@@ -102,7 +103,7 @@ echo "Writing 4.8.1 dependencies to $OUTPUT"
|
||||
|
||||
# Compilers locations
|
||||
GCC_BASE=`readlink -f $TP2_LATEST/gcc/4.8.1/centos6-native/*/`
|
||||
CLANG_BASE=`ls -d1 /mnt/gvfs/third-party2/clang/fc904e50a9266b9d7b98cae1993afa0c5aae1440/3.7.1/centos6-native/*/ | head -n1`
|
||||
CLANG_BASE=`readlink -f $TP2_LATEST/llvm-fb/stable/centos6-native/*/`
|
||||
|
||||
log_variable GCC_BASE
|
||||
log_variable CLANG_BASE
|
||||
@@ -119,6 +120,7 @@ get_lib_base gflags LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base jemalloc LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base numa LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base libunwind LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base tbb 4.0_update2 gcc-4.8.1-glibc-2.17
|
||||
|
||||
get_lib_base kernel-headers LATEST gcc-4.8.1-glibc-2.17
|
||||
get_lib_base binutils LATEST centos6-native
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
#include "db/auto_roll_logger.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
// -- AutoRollLogger
|
||||
@@ -47,7 +45,8 @@ void AutoRollLogger::RollLogFile() {
|
||||
env_->RenameFile(log_fname_, old_fname);
|
||||
}
|
||||
|
||||
string AutoRollLogger::ValistToString(const char* format, va_list args) const {
|
||||
std::string AutoRollLogger::ValistToString(const char* format,
|
||||
va_list args) const {
|
||||
// Any log messages longer than 1024 will get truncated.
|
||||
// The user is responsible for chopping longer messages into multi line log
|
||||
static const int MAXBUFFERSIZE = 1024;
|
||||
@@ -112,7 +111,7 @@ void AutoRollLogger::LogHeader(const char* format, va_list args) {
|
||||
// strings
|
||||
va_list tmp;
|
||||
va_copy(tmp, args);
|
||||
string data = ValistToString(format, tmp);
|
||||
std::string data = ValistToString(format, tmp);
|
||||
va_end(tmp);
|
||||
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
+52
-56
@@ -19,8 +19,6 @@
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class AutoRollLoggerTest : public testing::Test {
|
||||
@@ -40,23 +38,22 @@ class AutoRollLoggerTest : public testing::Test {
|
||||
Env::Default()->CreateDir(kTestDir);
|
||||
}
|
||||
|
||||
void RollLogFileBySizeTest(AutoRollLogger* logger,
|
||||
size_t log_max_size,
|
||||
const string& log_message);
|
||||
uint64_t RollLogFileByTimeTest(AutoRollLogger* logger,
|
||||
size_t time,
|
||||
const string& log_message);
|
||||
void RollLogFileBySizeTest(AutoRollLogger* logger, size_t log_max_size,
|
||||
const std::string& log_message);
|
||||
uint64_t RollLogFileByTimeTest(AutoRollLogger* logger, size_t time,
|
||||
const std::string& log_message);
|
||||
|
||||
static const string kSampleMessage;
|
||||
static const string kTestDir;
|
||||
static const string kLogFile;
|
||||
static const std::string kSampleMessage;
|
||||
static const std::string kTestDir;
|
||||
static const std::string kLogFile;
|
||||
static Env* env;
|
||||
};
|
||||
|
||||
const string AutoRollLoggerTest::kSampleMessage(
|
||||
const std::string AutoRollLoggerTest::kSampleMessage(
|
||||
"this is the message to be written to the log file!!");
|
||||
const string AutoRollLoggerTest::kTestDir(test::TmpDir() + "/db_log_test");
|
||||
const string AutoRollLoggerTest::kLogFile(test::TmpDir() + "/db_log_test/LOG");
|
||||
const std::string AutoRollLoggerTest::kTestDir(test::TmpDir() + "/db_log_test");
|
||||
const std::string AutoRollLoggerTest::kLogFile(test::TmpDir() +
|
||||
"/db_log_test/LOG");
|
||||
Env* AutoRollLoggerTest::env = Env::Default();
|
||||
|
||||
// In this test we only want to Log some simple log message with
|
||||
@@ -86,7 +83,7 @@ void GetFileCreateTime(const std::string& fname, uint64_t* file_ctime) {
|
||||
|
||||
void AutoRollLoggerTest::RollLogFileBySizeTest(AutoRollLogger* logger,
|
||||
size_t log_max_size,
|
||||
const string& log_message) {
|
||||
const std::string& log_message) {
|
||||
logger->SetInfoLogLevel(InfoLogLevel::INFO_LEVEL);
|
||||
// measure the size of each message, which is supposed
|
||||
// to be equal or greater than log_message.size()
|
||||
@@ -111,7 +108,7 @@ void AutoRollLoggerTest::RollLogFileBySizeTest(AutoRollLogger* logger,
|
||||
}
|
||||
|
||||
uint64_t AutoRollLoggerTest::RollLogFileByTimeTest(
|
||||
AutoRollLogger* logger, size_t time, const string& log_message) {
|
||||
AutoRollLogger* logger, size_t time, const std::string& log_message) {
|
||||
uint64_t expected_create_time;
|
||||
uint64_t actual_create_time;
|
||||
uint64_t total_log_size;
|
||||
@@ -276,35 +273,24 @@ TEST_F(AutoRollLoggerTest, LogFlushWhileRolling) {
|
||||
ASSERT_TRUE(auto_roll_logger);
|
||||
std::thread flush_thread;
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
// Need to pin the old logger before beginning the roll, as rolling grabs
|
||||
// the mutex, which would prevent us from accessing the old logger.
|
||||
{"AutoRollLogger::Flush:PinnedLogger",
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit"},
|
||||
// Need to finish the flush thread init before this callback because the
|
||||
// callback accesses flush_thread.get_id() in order to apply certain sync
|
||||
// points only to the flush thread.
|
||||
{"AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit",
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallbackBegin"},
|
||||
// Need to reset logger at this point in Flush() to exercise a race
|
||||
// condition case, which is executing the flush with the pinned (old)
|
||||
// logger after the roll has cut over to a new logger.
|
||||
{"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback1",
|
||||
"AutoRollLogger::ResetLogger:BeforeNewLogger"},
|
||||
{"AutoRollLogger::ResetLogger:AfterNewLogger",
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback2"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PosixLogger::Flush:BeginCallback", [&](void* arg) {
|
||||
TEST_SYNC_POINT(
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallbackBegin");
|
||||
if (std::this_thread::get_id() == flush_thread.get_id()) {
|
||||
TEST_SYNC_POINT(
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback1");
|
||||
TEST_SYNC_POINT(
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:FlushCallback2");
|
||||
}
|
||||
});
|
||||
// Notes:
|
||||
// (1) Need to pin the old logger before beginning the roll, as rolling grabs
|
||||
// the mutex, which would prevent us from accessing the old logger. This
|
||||
// also marks flush_thread with AutoRollLogger::Flush:PinnedLogger.
|
||||
// (2) Need to reset logger during PosixLogger::Flush() to exercise a race
|
||||
// condition case, which is executing the flush with the pinned (old)
|
||||
// logger after auto-roll logger has cut over to a new logger.
|
||||
// (3) PosixLogger::Flush() happens in both threads but its SyncPoints only
|
||||
// are enabled in flush_thread (the one pinning the old logger).
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependencyAndMarkers(
|
||||
{{"AutoRollLogger::Flush:PinnedLogger",
|
||||
"AutoRollLoggerTest::LogFlushWhileRolling:PreRollAndPostThreadInit"},
|
||||
{"PosixLogger::Flush:Begin1",
|
||||
"AutoRollLogger::ResetLogger:BeforeNewLogger"},
|
||||
{"AutoRollLogger::ResetLogger:AfterNewLogger",
|
||||
"PosixLogger::Flush:Begin2"}},
|
||||
{{"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::Flush:Begin1"},
|
||||
{"AutoRollLogger::Flush:PinnedLogger", "PosixLogger::Flush:Begin2"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
flush_thread = std::thread([&]() { auto_roll_logger->Flush(); });
|
||||
@@ -361,13 +347,13 @@ TEST_F(AutoRollLoggerTest, InfoLogLevel) {
|
||||
|
||||
// Test the logger Header function for roll over logs
|
||||
// We expect the new logs creates as roll over to carry the headers specified
|
||||
static std::vector<string> GetOldFileNames(const string& path) {
|
||||
std::vector<string> ret;
|
||||
static std::vector<std::string> GetOldFileNames(const std::string& path) {
|
||||
std::vector<std::string> ret;
|
||||
|
||||
const string dirname = path.substr(/*start=*/ 0, path.find_last_of("/"));
|
||||
const string fname = path.substr(path.find_last_of("/") + 1);
|
||||
const std::string dirname = path.substr(/*start=*/0, path.find_last_of("/"));
|
||||
const std::string fname = path.substr(path.find_last_of("/") + 1);
|
||||
|
||||
std::vector<string> children;
|
||||
std::vector<std::string> children;
|
||||
Env::Default()->GetChildren(dirname, &children);
|
||||
|
||||
// We know that the old log files are named [path]<something>
|
||||
@@ -382,12 +368,13 @@ static std::vector<string> GetOldFileNames(const string& path) {
|
||||
}
|
||||
|
||||
// Return the number of lines where a given pattern was found in the file
|
||||
static size_t GetLinesCount(const string& fname, const string& pattern) {
|
||||
stringstream ssbuf;
|
||||
string line;
|
||||
static size_t GetLinesCount(const std::string& fname,
|
||||
const std::string& pattern) {
|
||||
std::stringstream ssbuf;
|
||||
std::string line;
|
||||
size_t count = 0;
|
||||
|
||||
ifstream inFile(fname.c_str());
|
||||
std::ifstream inFile(fname.c_str());
|
||||
ssbuf << inFile.rdbuf();
|
||||
|
||||
while (getline(ssbuf, line)) {
|
||||
@@ -426,7 +413,7 @@ TEST_F(AutoRollLoggerTest, LogHeaderTest) {
|
||||
}
|
||||
}
|
||||
|
||||
const string newfname = logger.TEST_log_fname();
|
||||
const std::string newfname = logger.TEST_log_fname();
|
||||
|
||||
// Log enough data to cause a roll over
|
||||
int i = 0;
|
||||
@@ -458,7 +445,16 @@ TEST_F(AutoRollLoggerTest, LogHeaderTest) {
|
||||
TEST_F(AutoRollLoggerTest, LogFileExistence) {
|
||||
rocksdb::DB* db;
|
||||
rocksdb::Options options;
|
||||
string deleteCmd = "rm -rf " + kTestDir;
|
||||
#ifdef OS_WIN
|
||||
// Replace all slashes in the path so windows CompSpec does not
|
||||
// become confused
|
||||
std::string testDir(kTestDir);
|
||||
std::replace_if(testDir.begin(), testDir.end(),
|
||||
[](char ch) { return ch == '/'; }, '\\');
|
||||
std::string deleteCmd = "if exist " + testDir + " rd /s /q " + testDir;
|
||||
#else
|
||||
std::string deleteCmd = "rm -rf " + kTestDir;
|
||||
#endif
|
||||
ASSERT_EQ(system(deleteCmd.c_str()), 0);
|
||||
options.max_log_file_size = 100 * 1024 * 1024;
|
||||
options.create_if_missing = true;
|
||||
|
||||
+44
-14
@@ -15,6 +15,7 @@
|
||||
|
||||
#include "db/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
@@ -41,29 +42,39 @@ TableBuilder* NewTableBuilder(
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, WritableFileWriter* file,
|
||||
const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts, const bool skip_filters) {
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts,
|
||||
const std::string* compression_dict, const bool skip_filters) {
|
||||
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, skip_filters),
|
||||
compression_opts, compression_dict, skip_filters,
|
||||
column_family_name),
|
||||
column_family_id, file);
|
||||
}
|
||||
|
||||
Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& ioptions,
|
||||
const EnvOptions& env_options, TableCache* table_cache,
|
||||
InternalIterator* iter, FileMetaData* meta,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, std::vector<SequenceNumber> snapshots,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties) {
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
EventLogger* event_logger, int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level) {
|
||||
assert((column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
// Reports the IOStats for flush for every following bytes.
|
||||
const size_t kReportFlushIOStatsEvery = 1048576;
|
||||
Status s;
|
||||
@@ -72,6 +83,12 @@ Status BuildTable(
|
||||
|
||||
std::string fname = TableFileName(ioptions.db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
#ifndef ROCKSDB_LITE
|
||||
EventHelpers::NotifyTableFileCreationStarted(
|
||||
ioptions.listeners, dbname, column_family_name, fname, job_id, reason);
|
||||
#endif // !ROCKSDB_LITE
|
||||
TableProperties tp;
|
||||
|
||||
if (iter->Valid()) {
|
||||
TableBuilder* builder;
|
||||
unique_ptr<WritableFileWriter> file_writer;
|
||||
@@ -79,6 +96,9 @@ Status BuildTable(
|
||||
unique_ptr<WritableFile> file;
|
||||
s = NewWritableFile(env, fname, &file, env_options);
|
||||
if (!s.ok()) {
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
return s;
|
||||
}
|
||||
file->SetIOPriority(io_priority);
|
||||
@@ -87,12 +107,13 @@ Status BuildTable(
|
||||
|
||||
builder = NewTableBuilder(
|
||||
ioptions, internal_comparator, int_tbl_prop_collector_factories,
|
||||
column_family_id, file_writer.get(), compression, compression_opts);
|
||||
column_family_id, column_family_name, file_writer.get(), compression,
|
||||
compression_opts);
|
||||
}
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator, nullptr, ioptions.info_log,
|
||||
ioptions.min_partial_merge_operands,
|
||||
mutable_cf_options.min_partial_merge_operands,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back());
|
||||
|
||||
@@ -125,11 +146,13 @@ Status BuildTable(
|
||||
}
|
||||
|
||||
if (s.ok() && !empty) {
|
||||
meta->fd.file_size = builder->FileSize();
|
||||
uint64_t file_size = builder->FileSize();
|
||||
meta->fd.file_size = file_size;
|
||||
meta->marked_for_compaction = builder->NeedCompact();
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder->GetTableProperties();
|
||||
if (table_properties) {
|
||||
*table_properties = builder->GetTableProperties();
|
||||
*table_properties = tp;
|
||||
}
|
||||
}
|
||||
delete builder;
|
||||
@@ -149,7 +172,8 @@ Status BuildTable(
|
||||
ReadOptions(), env_options, internal_comparator, meta->fd, nullptr,
|
||||
(internal_stats == nullptr) ? nullptr
|
||||
: internal_stats->GetFileReadHist(0),
|
||||
false));
|
||||
false /* for_compaction */, nullptr /* arena */,
|
||||
false /* skip_filter */, level));
|
||||
s = it->status();
|
||||
if (s.ok() && paranoid_file_checks) {
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
@@ -167,6 +191,12 @@ Status BuildTable(
|
||||
if (!s.ok() || meta->fd.GetFileSize() == 0) {
|
||||
env->DeleteFile(fname);
|
||||
}
|
||||
|
||||
// Output to event logger and fire events.
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, tp, reason, s);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+23
-10
@@ -12,11 +12,13 @@
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/immutable_options.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/table_properties.h"
|
||||
#include "rocksdb/types.h"
|
||||
#include "util/cf_options.h"
|
||||
#include "util/event_logger.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -33,14 +35,20 @@ class WritableFileWriter;
|
||||
class InternalStats;
|
||||
class InternalIterator;
|
||||
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown. It must outlive the
|
||||
// TableBuilder returned by this function.
|
||||
// @param compression_dict Data for presetting the compression library's
|
||||
// dictionary, or nullptr.
|
||||
TableBuilder* NewTableBuilder(
|
||||
const ImmutableCFOptions& options,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, WritableFileWriter* file,
|
||||
const CompressionType compression_type,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const CompressionOptions& compression_opts,
|
||||
const std::string* compression_dict = nullptr,
|
||||
const bool skip_filters = false);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
@@ -48,19 +56,24 @@ TableBuilder* NewTableBuilder(
|
||||
// *meta will be filled with metadata about the generated table.
|
||||
// If no data is present in *iter, meta->file_size will be set to
|
||||
// zero, and no Table file will be produced.
|
||||
//
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown.
|
||||
extern Status BuildTable(
|
||||
const std::string& dbname, Env* env, const ImmutableCFOptions& options,
|
||||
const EnvOptions& env_options, TableCache* table_cache,
|
||||
InternalIterator* iter, FileMetaData* meta,
|
||||
const MutableCFOptions& mutable_cf_options, const EnvOptions& env_options,
|
||||
TableCache* table_cache, InternalIterator* iter, FileMetaData* meta,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, std::vector<SequenceNumber> snapshots,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const CompressionType compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats,
|
||||
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);
|
||||
TableProperties* table_properties = nullptr, int level = -1);
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -43,6 +43,7 @@ using rocksdb::CompactionFilterContext;
|
||||
using rocksdb::CompactionOptionsFIFO;
|
||||
using rocksdb::Comparator;
|
||||
using rocksdb::CompressionType;
|
||||
using rocksdb::WALRecoveryMode;
|
||||
using rocksdb::DB;
|
||||
using rocksdb::DBOptions;
|
||||
using rocksdb::Env;
|
||||
@@ -123,6 +124,7 @@ struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
char** new_value, size_t *new_value_length,
|
||||
unsigned char* value_changed);
|
||||
const char* (*name_)(void*);
|
||||
unsigned char ignore_snapshots_;
|
||||
|
||||
virtual ~rocksdb_compactionfilter_t() {
|
||||
(*destructor_)(state_);
|
||||
@@ -148,6 +150,8 @@ struct rocksdb_compactionfilter_t : public CompactionFilter {
|
||||
}
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
virtual bool IgnoreSnapshots() const override { return ignore_snapshots_; }
|
||||
};
|
||||
|
||||
struct rocksdb_compactionfilterfactory_t : public CompactionFilterFactory {
|
||||
@@ -269,33 +273,31 @@ struct rocksdb_mergeoperator_t : public MergeOperator {
|
||||
|
||||
virtual const char* Name() const override { return (*name_)(state_); }
|
||||
|
||||
virtual bool FullMerge(const Slice& key, const Slice* existing_value,
|
||||
const std::deque<std::string>& operand_list,
|
||||
std::string* new_value,
|
||||
Logger* logger) const override {
|
||||
size_t n = operand_list.size();
|
||||
virtual bool FullMergeV2(const MergeOperationInput& merge_in,
|
||||
MergeOperationOutput* merge_out) const override {
|
||||
size_t n = merge_in.operand_list.size();
|
||||
std::vector<const char*> operand_pointers(n);
|
||||
std::vector<size_t> operand_sizes(n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
Slice operand(operand_list[i]);
|
||||
Slice operand(merge_in.operand_list[i]);
|
||||
operand_pointers[i] = operand.data();
|
||||
operand_sizes[i] = operand.size();
|
||||
}
|
||||
|
||||
const char* existing_value_data = nullptr;
|
||||
size_t existing_value_len = 0;
|
||||
if (existing_value != nullptr) {
|
||||
existing_value_data = existing_value->data();
|
||||
existing_value_len = existing_value->size();
|
||||
if (merge_in.existing_value != nullptr) {
|
||||
existing_value_data = merge_in.existing_value->data();
|
||||
existing_value_len = merge_in.existing_value->size();
|
||||
}
|
||||
|
||||
unsigned char success;
|
||||
size_t new_value_len;
|
||||
char* tmp_new_value = (*full_merge_)(
|
||||
state_, key.data(), key.size(), existing_value_data, existing_value_len,
|
||||
&operand_pointers[0], &operand_sizes[0], static_cast<int>(n), &success,
|
||||
&new_value_len);
|
||||
new_value->assign(tmp_new_value, new_value_len);
|
||||
state_, merge_in.key.data(), merge_in.key.size(), existing_value_data,
|
||||
existing_value_len, &operand_pointers[0], &operand_sizes[0],
|
||||
static_cast<int>(n), &success, &new_value_len);
|
||||
merge_out->new_value.assign(tmp_new_value, new_value_len);
|
||||
|
||||
if (delete_value_ != nullptr) {
|
||||
(*delete_value_)(state_, tmp_new_value, new_value_len);
|
||||
@@ -446,6 +448,12 @@ void rocksdb_backup_engine_create_new_backup(rocksdb_backup_engine_t* be,
|
||||
SaveError(errptr, be->rep->CreateNewBackup(db->rep));
|
||||
}
|
||||
|
||||
void rocksdb_backup_engine_purge_old_backups(rocksdb_backup_engine_t* be,
|
||||
uint32_t num_backups_to_keep,
|
||||
char** errptr) {
|
||||
SaveError(errptr, be->rep->PurgeOldBackups(num_backups_to_keep));
|
||||
}
|
||||
|
||||
rocksdb_restore_options_t* rocksdb_restore_options_create() {
|
||||
return new rocksdb_restore_options_t;
|
||||
}
|
||||
@@ -825,6 +833,31 @@ rocksdb_iterator_t* rocksdb_create_iterator_cf(
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_create_iterators(
|
||||
rocksdb_t *db,
|
||||
rocksdb_readoptions_t* opts,
|
||||
rocksdb_column_family_handle_t** column_families,
|
||||
rocksdb_iterator_t** iterators,
|
||||
size_t size,
|
||||
char** errptr) {
|
||||
std::vector<ColumnFamilyHandle*> column_families_vec;
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
column_families_vec.push_back(column_families[i]->rep);
|
||||
}
|
||||
|
||||
std::vector<Iterator*> res;
|
||||
Status status = db->rep->NewIterators(opts->rep, column_families_vec, &res);
|
||||
assert(res.size() == size);
|
||||
if (SaveError(errptr, status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
iterators[i] = new rocksdb_iterator_t;
|
||||
iterators[i]->rep = res[i];
|
||||
}
|
||||
}
|
||||
|
||||
const rocksdb_snapshot_t* rocksdb_create_snapshot(
|
||||
rocksdb_t* db) {
|
||||
rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
|
||||
@@ -1288,6 +1321,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_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;
|
||||
}
|
||||
|
||||
void rocksdb_block_based_options_set_skip_table_builder_flush(
|
||||
rocksdb_block_based_table_options_t* options, unsigned char v) {
|
||||
options->rep.skip_table_builder_flush = v;
|
||||
@@ -1388,6 +1426,11 @@ void rocksdb_options_set_compaction_filter_factory(
|
||||
std::shared_ptr<CompactionFilterFactory>(factory);
|
||||
}
|
||||
|
||||
void rocksdb_options_compaction_readahead_size(
|
||||
rocksdb_options_t* opt, size_t s) {
|
||||
opt->rep.compaction_readahead_size = s;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_comparator(
|
||||
rocksdb_options_t* opt,
|
||||
rocksdb_comparator_t* cmp) {
|
||||
@@ -1473,14 +1516,9 @@ void rocksdb_options_set_max_bytes_for_level_multiplier(
|
||||
opt->rep.max_bytes_for_level_multiplier = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_expanded_compaction_factor(
|
||||
rocksdb_options_t* opt, int n) {
|
||||
opt->rep.expanded_compaction_factor = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_grandparent_overlap_factor(
|
||||
rocksdb_options_t* opt, int n) {
|
||||
opt->rep.max_grandparent_overlap_factor = n;
|
||||
void rocksdb_options_set_max_compaction_bytes(rocksdb_options_t* opt,
|
||||
uint64_t n) {
|
||||
opt->rep.max_compaction_bytes = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_bytes_for_level_multiplier_additional(
|
||||
@@ -1517,6 +1555,10 @@ void rocksdb_options_set_level0_stop_writes_trigger(
|
||||
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);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compression(rocksdb_options_t* opt, int t) {
|
||||
opt->rep.compression = static_cast<CompressionType>(t);
|
||||
}
|
||||
@@ -1531,11 +1573,13 @@ void rocksdb_options_set_compression_per_level(rocksdb_options_t* opt,
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compression_options(
|
||||
rocksdb_options_t* opt, int w_bits, int level, int strategy) {
|
||||
void rocksdb_options_set_compression_options(rocksdb_options_t* opt, int w_bits,
|
||||
int level, int strategy,
|
||||
int max_dict_bytes) {
|
||||
opt->rep.compression_opts.window_bits = w_bits;
|
||||
opt->rep.compression_opts.level = level;
|
||||
opt->rep.compression_opts.strategy = strategy;
|
||||
opt->rep.compression_opts.max_dict_bytes = max_dict_bytes;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_prefix_extractor(
|
||||
@@ -1649,11 +1693,6 @@ void rocksdb_options_set_verify_checksums_in_compaction(
|
||||
opt->rep.verify_checksums_in_compaction = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_filter_deletes(
|
||||
rocksdb_options_t* opt, unsigned char v) {
|
||||
opt->rep.filter_deletes = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_max_sequential_skip_in_iterations(
|
||||
rocksdb_options_t* opt, uint64_t v) {
|
||||
opt->rep.max_sequential_skip_in_iterations = v;
|
||||
@@ -1739,11 +1778,6 @@ void rocksdb_options_set_delete_obsolete_files_period_micros(
|
||||
opt->rep.delete_obsolete_files_period_micros = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_source_compaction_factor(
|
||||
rocksdb_options_t* opt, int n) {
|
||||
opt->rep.expanded_compaction_factor = n;
|
||||
}
|
||||
|
||||
void rocksdb_options_prepare_for_bulk_load(rocksdb_options_t* opt) {
|
||||
opt->rep.PrepareForBulkLoad();
|
||||
}
|
||||
@@ -1756,19 +1790,14 @@ void rocksdb_options_set_memtable_vector_rep(rocksdb_options_t *opt) {
|
||||
opt->rep.memtable_factory.reset(factory);
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_prefix_bloom_bits(
|
||||
rocksdb_options_t* opt, uint32_t v) {
|
||||
opt->rep.memtable_prefix_bloom_bits = v;
|
||||
void rocksdb_options_set_memtable_prefix_bloom_size_ratio(
|
||||
rocksdb_options_t* opt, double v) {
|
||||
opt->rep.memtable_prefix_bloom_size_ratio = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_prefix_bloom_probes(
|
||||
rocksdb_options_t* opt, uint32_t v) {
|
||||
opt->rep.memtable_prefix_bloom_probes = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_memtable_prefix_bloom_huge_page_tlb_size(
|
||||
rocksdb_options_t* opt, size_t v) {
|
||||
opt->rep.memtable_prefix_bloom_huge_page_tlb_size = v;
|
||||
void rocksdb_options_set_memtable_huge_page_size(rocksdb_options_t* opt,
|
||||
size_t v) {
|
||||
opt->rep.memtable_huge_page_size = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_hash_skip_list_rep(
|
||||
@@ -1832,6 +1861,11 @@ void rocksdb_options_set_inplace_update_num_locks(
|
||||
opt->rep.inplace_update_num_locks = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_report_bg_io_stats(
|
||||
rocksdb_options_t* opt, int v) {
|
||||
opt->rep.report_bg_io_stats = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compaction_style(rocksdb_options_t *opt, int style) {
|
||||
opt->rep.compaction_style = static_cast<rocksdb::CompactionStyle>(style);
|
||||
}
|
||||
@@ -1883,10 +1917,17 @@ rocksdb_compactionfilter_t* rocksdb_compactionfilter_create(
|
||||
result->state_ = state;
|
||||
result->destructor_ = destructor;
|
||||
result->filter_ = filter;
|
||||
result->ignore_snapshots_ = false;
|
||||
result->name_ = name;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_compactionfilter_set_ignore_snapshots(
|
||||
rocksdb_compactionfilter_t* filter,
|
||||
unsigned char whether_ignore) {
|
||||
filter->ignore_snapshots_ = whether_ignore;
|
||||
}
|
||||
|
||||
void rocksdb_compactionfilter_destroy(rocksdb_compactionfilter_t* filter) {
|
||||
delete filter;
|
||||
}
|
||||
@@ -2079,6 +2120,11 @@ void rocksdb_readoptions_set_tailing(
|
||||
opt->rep.tailing = v;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_readahead_size(
|
||||
rocksdb_readoptions_t* opt, size_t v) {
|
||||
opt->rep.readahead_size = v;
|
||||
}
|
||||
|
||||
rocksdb_writeoptions_t* rocksdb_writeoptions_create() {
|
||||
return new rocksdb_writeoptions_t;
|
||||
}
|
||||
@@ -2120,6 +2166,10 @@ void rocksdb_cache_destroy(rocksdb_cache_t* cache) {
|
||||
delete cache;
|
||||
}
|
||||
|
||||
void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
|
||||
cache->rep->SetCapacity(capacity);
|
||||
}
|
||||
|
||||
rocksdb_env_t* rocksdb_create_default_env() {
|
||||
rocksdb_env_t* result = new rocksdb_env_t;
|
||||
result->rep = Env::Default();
|
||||
@@ -2127,6 +2177,13 @@ rocksdb_env_t* rocksdb_create_default_env() {
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_env_t* rocksdb_create_mem_env() {
|
||||
rocksdb_env_t* result = new rocksdb_env_t;
|
||||
result->rep = rocksdb::NewMemEnv(Env::Default());
|
||||
result->is_default = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_env_set_background_threads(rocksdb_env_t* env, int n) {
|
||||
env->rep->SetBackgroundThreads(n);
|
||||
}
|
||||
@@ -2338,6 +2395,31 @@ void rocksdb_get_options_from_string(const rocksdb_options_t* base_options,
|
||||
&new_options->rep));
|
||||
}
|
||||
|
||||
void rocksdb_delete_file_in_range(rocksdb_t* db, const char* start_key,
|
||||
size_t start_key_len, const char* limit_key,
|
||||
size_t limit_key_len, char** errptr) {
|
||||
Slice a, b;
|
||||
SaveError(
|
||||
errptr,
|
||||
DeleteFilesInRange(
|
||||
db->rep, db->rep->DefaultColumnFamily(),
|
||||
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr)));
|
||||
}
|
||||
|
||||
void rocksdb_delete_file_in_range_cf(
|
||||
rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
|
||||
const char* start_key, size_t start_key_len, const char* limit_key,
|
||||
size_t limit_key_len, char** errptr) {
|
||||
Slice a, b;
|
||||
SaveError(
|
||||
errptr,
|
||||
DeleteFilesInRange(
|
||||
db->rep, column_family->rep,
|
||||
(start_key ? (a = Slice(start_key, start_key_len), &a) : nullptr),
|
||||
(limit_key ? (b = Slice(limit_key, limit_key_len), &b) : nullptr)));
|
||||
}
|
||||
|
||||
void rocksdb_free(void* ptr) { free(ptr); }
|
||||
|
||||
} // end extern "C"
|
||||
|
||||
+44
-1
@@ -322,7 +322,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_block_based_table_factory(options, table_options);
|
||||
|
||||
rocksdb_options_set_compression(options, rocksdb_no_compression);
|
||||
rocksdb_options_set_compression_options(options, -14, -1, 0);
|
||||
rocksdb_options_set_compression_options(options, -14, -1, 0, 0);
|
||||
int compression_levels[] = {rocksdb_no_compression, rocksdb_no_compression,
|
||||
rocksdb_no_compression, rocksdb_no_compression};
|
||||
rocksdb_options_set_compression_per_level(options, compression_levels, 4);
|
||||
@@ -365,6 +365,24 @@ int main(int argc, char** argv) {
|
||||
rocksdb_backup_engine_create_new_backup(be, db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
// need a change to trigger a new backup
|
||||
rocksdb_delete(db, woptions, "does-not-exist", 14, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
rocksdb_backup_engine_create_new_backup(be, db, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
const rocksdb_backup_engine_info_t* bei = rocksdb_backup_engine_get_backup_info(be);
|
||||
CheckCondition(rocksdb_backup_engine_info_count(bei) > 1);
|
||||
rocksdb_backup_engine_info_destroy(bei);
|
||||
|
||||
rocksdb_backup_engine_purge_old_backups(be, 1, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
bei = rocksdb_backup_engine_get_backup_info(be);
|
||||
CheckCondition(rocksdb_backup_engine_info_count(bei) == 1);
|
||||
rocksdb_backup_engine_info_destroy(bei);
|
||||
|
||||
rocksdb_delete(db, woptions, "foo", 3, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
@@ -557,6 +575,7 @@ int main(int argc, char** argv) {
|
||||
rocksdb_close(db);
|
||||
rocksdb_options_set_create_if_missing(options, 0);
|
||||
rocksdb_options_set_error_if_exists(options, 0);
|
||||
rocksdb_options_set_wal_recovery_mode(options, 2);
|
||||
rocksdb_repair_db(options, dbname, &err);
|
||||
CheckNoError(err);
|
||||
db = rocksdb_open(options, dbname, &err);
|
||||
@@ -768,6 +787,30 @@ int main(int argc, char** argv) {
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
rocksdb_column_family_handle_t* iters_cf_handles[2] = { handles[0], handles[1] };
|
||||
rocksdb_iterator_t* iters_handles[2];
|
||||
rocksdb_create_iterators(db, roptions, iters_cf_handles, iters_handles, 2, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
iter = iters_handles[0];
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
iter = iters_handles[1];
|
||||
CheckCondition(!rocksdb_iter_valid(iter));
|
||||
rocksdb_iter_seek_to_first(iter);
|
||||
CheckCondition(rocksdb_iter_valid(iter));
|
||||
|
||||
for (i = 0; rocksdb_iter_valid(iter) != 0; rocksdb_iter_next(iter)) {
|
||||
i++;
|
||||
}
|
||||
CheckCondition(i == 1);
|
||||
rocksdb_iter_get_error(iter, &err);
|
||||
CheckNoError(err);
|
||||
rocksdb_iter_destroy(iter);
|
||||
|
||||
rocksdb_drop_column_family(db, handles[1], &err);
|
||||
CheckNoError(err);
|
||||
for (i = 0; i < 2; i++) {
|
||||
|
||||
+37
-30
@@ -26,7 +26,6 @@
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/compression.h"
|
||||
@@ -82,7 +81,7 @@ Status ColumnFamilyHandleImpl::GetDescriptor(ColumnFamilyDescriptor* desc) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
const Comparator* ColumnFamilyHandleImpl::user_comparator() const {
|
||||
const Comparator* ColumnFamilyHandleImpl::GetComparator() const {
|
||||
return cfd()->user_comparator();
|
||||
}
|
||||
|
||||
@@ -130,11 +129,6 @@ Status CheckConcurrentWritesSupported(const ColumnFamilyOptions& cf_options) {
|
||||
"In-place memtable updates (inplace_update_support) is not compatible "
|
||||
"with concurrent writes (allow_concurrent_memtable_write)");
|
||||
}
|
||||
if (cf_options.filter_deletes) {
|
||||
return Status::InvalidArgument(
|
||||
"Delete filtering (filter_deletes) is not compatible with concurrent "
|
||||
"memtable writes (allow_concurrent_memtable_writes)");
|
||||
}
|
||||
if (!cf_options.memtable_factory->IsInsertConcurrentlySupported()) {
|
||||
return Status::InvalidArgument(
|
||||
"Memtable doesn't concurrent writes (allow_concurrent_memtable_write)");
|
||||
@@ -147,13 +141,10 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
const ColumnFamilyOptions& src) {
|
||||
ColumnFamilyOptions result = src;
|
||||
result.comparator = icmp;
|
||||
#ifdef OS_MACOSX
|
||||
// TODO(icanadi) make write_buffer_size uint64_t instead of size_t
|
||||
ClipToRange(&result.write_buffer_size, ((size_t)64) << 10, ((size_t)1) << 30);
|
||||
#else
|
||||
ClipToRange(&result.write_buffer_size,
|
||||
((size_t)64) << 10, ((size_t)64) << 30);
|
||||
#endif
|
||||
size_t clamp_max = std::conditional<
|
||||
sizeof(size_t) == 4, std::integral_constant<size_t, 0xffffffff>,
|
||||
std::integral_constant<uint64_t, 64ull << 30>>::type::value;
|
||||
ClipToRange(&result.write_buffer_size, ((size_t)64) << 10, clamp_max);
|
||||
// if user sets arena_block_size, we trust user to use this value. Otherwise,
|
||||
// calculate a proper value from writer_buffer_size;
|
||||
if (result.arena_block_size <= 0) {
|
||||
@@ -180,6 +171,12 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
if (result.max_write_buffer_number_to_maintain < 0) {
|
||||
result.max_write_buffer_number_to_maintain = result.max_write_buffer_number;
|
||||
}
|
||||
// bloom filter size shouldn't exceed 1/4 of memtable size.
|
||||
if (result.memtable_prefix_bloom_size_ratio > 0.25) {
|
||||
result.memtable_prefix_bloom_size_ratio = 0.25;
|
||||
} else if (result.memtable_prefix_bloom_size_ratio < 0) {
|
||||
result.memtable_prefix_bloom_size_ratio = 0;
|
||||
}
|
||||
XFUNC_TEST("memtablelist_history", "transaction_xftest_SanitizeOptions",
|
||||
xf_transaction_set_memtable_history1,
|
||||
xf_transaction_set_memtable_history,
|
||||
@@ -207,6 +204,10 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
result.level0_stop_writes_trigger = std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
if (result.max_bytes_for_level_multiplier <= 0) {
|
||||
result.max_bytes_for_level_multiplier = 1;
|
||||
}
|
||||
|
||||
if (result.level0_file_num_compaction_trigger == 0) {
|
||||
Warn(db_options.info_log.get(),
|
||||
"level0_file_num_compaction_trigger cannot be 0");
|
||||
@@ -265,6 +266,10 @@ ColumnFamilyOptions SanitizeOptions(const DBOptions& db_options,
|
||||
}
|
||||
}
|
||||
|
||||
if (result.max_compaction_bytes == 0) {
|
||||
result.max_compaction_bytes = result.target_file_size_base * 25;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -332,7 +337,7 @@ void SuperVersionUnrefHandle(void* ptr) {
|
||||
|
||||
ColumnFamilyData::ColumnFamilyData(
|
||||
uint32_t id, const std::string& name, Version* _dummy_versions,
|
||||
Cache* _table_cache, WriteBuffer* write_buffer,
|
||||
Cache* _table_cache, WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& cf_options, const DBOptions* db_options,
|
||||
const EnvOptions& env_options, ColumnFamilySet* column_family_set)
|
||||
: id_(id),
|
||||
@@ -346,7 +351,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
SanitizeOptions(*db_options, &internal_comparator_, cf_options)),
|
||||
ioptions_(options_),
|
||||
mutable_cf_options_(options_, ioptions_),
|
||||
write_buffer_(write_buffer),
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
mem_(nullptr),
|
||||
imm_(options_.min_write_buffer_number_to_merge,
|
||||
options_.max_write_buffer_number_to_maintain),
|
||||
@@ -561,8 +566,9 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
"(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 (vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger) {
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
vstorage->l0_delay_trigger_count() >=
|
||||
mutable_cf_options.level0_stop_writes_trigger) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(InternalStats::LEVEL0_NUM_FILES_TOTAL, 1);
|
||||
if (compaction_picker_->IsLevel0CompactionInProgress()) {
|
||||
@@ -572,7 +578,8 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
Log(InfoLogLevel::WARN_LEVEL, 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.hard_pending_compaction_bytes_limit > 0 &&
|
||||
} 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) {
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
@@ -597,7 +604,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.level0_slowdown_writes_trigger >= 0 &&
|
||||
} 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) {
|
||||
write_controller_token_ =
|
||||
@@ -614,7 +622,8 @@ void ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
"rate %" PRIu64,
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count(),
|
||||
write_controller->delayed_write_rate());
|
||||
} else if (mutable_cf_options.soft_pending_compaction_bytes_limit > 0 &&
|
||||
} 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) {
|
||||
write_controller_token_ =
|
||||
@@ -678,7 +687,7 @@ MemTable* ColumnFamilyData::ConstructNewMemtable(
|
||||
const MutableCFOptions& mutable_cf_options, SequenceNumber earliest_seq) {
|
||||
assert(current_ != nullptr);
|
||||
return new MemTable(internal_comparator_, ioptions_, mutable_cf_options,
|
||||
write_buffer_, earliest_seq);
|
||||
write_buffer_manager_, earliest_seq);
|
||||
}
|
||||
|
||||
void ColumnFamilyData::CreateNewMemtable(
|
||||
@@ -857,7 +866,7 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
const DBOptions* db_options,
|
||||
const EnvOptions& env_options,
|
||||
Cache* table_cache,
|
||||
WriteBuffer* write_buffer,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller)
|
||||
: max_column_family_(0),
|
||||
dummy_cfd_(new ColumnFamilyData(0, "", nullptr, nullptr, nullptr,
|
||||
@@ -868,7 +877,7 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
db_options_(db_options),
|
||||
env_options_(env_options),
|
||||
table_cache_(table_cache),
|
||||
write_buffer_(write_buffer),
|
||||
write_buffer_manager_(write_buffer_manager),
|
||||
write_controller_(write_controller) {
|
||||
// initialize linked list
|
||||
dummy_cfd_->prev_ = dummy_cfd_;
|
||||
@@ -931,10 +940,9 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
const std::string& name, uint32_t id, Version* dummy_versions,
|
||||
const ColumnFamilyOptions& options) {
|
||||
assert(column_families_.find(name) == column_families_.end());
|
||||
ColumnFamilyData* new_cfd =
|
||||
new ColumnFamilyData(id, name, dummy_versions, table_cache_,
|
||||
write_buffer_, options, db_options_,
|
||||
env_options_, this);
|
||||
ColumnFamilyData* new_cfd = new ColumnFamilyData(
|
||||
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
|
||||
db_options_, env_options_, this);
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
max_column_family_ = std::max(max_column_family_, id);
|
||||
@@ -1011,8 +1019,7 @@ uint32_t GetColumnFamilyID(ColumnFamilyHandle* column_family) {
|
||||
const Comparator* GetColumnFamilyUserComparator(
|
||||
ColumnFamilyHandle* column_family) {
|
||||
if (column_family != nullptr) {
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
return cfh->user_comparator();
|
||||
return column_family->GetComparator();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
+12
-9
@@ -15,15 +15,15 @@
|
||||
#include <atomic>
|
||||
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/table_properties_collector.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "rocksdb/compaction_job_stats.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "util/mutable_cf_options.h"
|
||||
#include "util/cf_options.h"
|
||||
#include "util/thread_local.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -55,11 +55,11 @@ class ColumnFamilyHandleImpl : public ColumnFamilyHandle {
|
||||
// destroy without mutex
|
||||
virtual ~ColumnFamilyHandleImpl();
|
||||
virtual ColumnFamilyData* cfd() const { return cfd_; }
|
||||
virtual const Comparator* user_comparator() const;
|
||||
|
||||
virtual uint32_t GetID() const override;
|
||||
virtual const std::string& GetName() const override;
|
||||
virtual Status GetDescriptor(ColumnFamilyDescriptor* desc) override;
|
||||
virtual const Comparator* GetComparator() const override;
|
||||
|
||||
private:
|
||||
ColumnFamilyData* cfd_;
|
||||
@@ -209,7 +209,7 @@ class ColumnFamilyData {
|
||||
const ImmutableCFOptions* ioptions() const { return &ioptions_; }
|
||||
// REQUIRES: DB mutex held
|
||||
// This returns the MutableCFOptions used by current SuperVersion
|
||||
// You shoul use this API to reference MutableCFOptions most of the time.
|
||||
// You should use this API to reference MutableCFOptions most of the time.
|
||||
const MutableCFOptions* GetCurrentMutableCFOptions() const {
|
||||
return &(super_version_->mutable_cf_options);
|
||||
}
|
||||
@@ -323,7 +323,7 @@ class ColumnFamilyData {
|
||||
friend class ColumnFamilySet;
|
||||
ColumnFamilyData(uint32_t id, const std::string& name,
|
||||
Version* dummy_versions, Cache* table_cache,
|
||||
WriteBuffer* write_buffer,
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& options,
|
||||
const DBOptions* db_options, const EnvOptions& env_options,
|
||||
ColumnFamilySet* column_family_set);
|
||||
@@ -348,7 +348,7 @@ class ColumnFamilyData {
|
||||
|
||||
std::unique_ptr<InternalStats> internal_stats_;
|
||||
|
||||
WriteBuffer* write_buffer_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
|
||||
MemTable* mem_;
|
||||
MemTableList imm_;
|
||||
@@ -438,7 +438,8 @@ class ColumnFamilySet {
|
||||
|
||||
ColumnFamilySet(const std::string& dbname, const DBOptions* db_options,
|
||||
const EnvOptions& env_options, Cache* table_cache,
|
||||
WriteBuffer* write_buffer, WriteController* write_controller);
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
WriteController* write_controller);
|
||||
~ColumnFamilySet();
|
||||
|
||||
ColumnFamilyData* GetDefault() const;
|
||||
@@ -465,6 +466,8 @@ class ColumnFamilySet {
|
||||
// Don't call while iterating over ColumnFamilySet
|
||||
void FreeDeadColumnFamilies();
|
||||
|
||||
Cache* get_table_cache() { return table_cache_; }
|
||||
|
||||
private:
|
||||
friend class ColumnFamilyData;
|
||||
// helper function that gets called from cfd destructor
|
||||
@@ -493,7 +496,7 @@ class ColumnFamilySet {
|
||||
const DBOptions* const db_options_;
|
||||
const EnvOptions env_options_;
|
||||
Cache* table_cache_;
|
||||
WriteBuffer* write_buffer_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
WriteController* write_controller_;
|
||||
};
|
||||
|
||||
|
||||
+596
-19
@@ -1,7 +1,8 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
// This source code is licensed under the BSD-style license found in
|
||||
// the LICENSE file in the root directory of this source tree. An
|
||||
// additional grant of patent rights can be found in the PATENTS file
|
||||
// in the same 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
|
||||
@@ -13,10 +14,12 @@
|
||||
#include <thread>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/options_parser.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
@@ -26,6 +29,8 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static const int kValueSize = 1000;
|
||||
|
||||
namespace {
|
||||
std::string RandomString(Random* rnd, int len) {
|
||||
std::string r;
|
||||
@@ -70,10 +75,81 @@ class ColumnFamilyTest : public testing::Test {
|
||||
delete env_;
|
||||
}
|
||||
|
||||
// Return the value to associate with the specified key
|
||||
Slice Value(int k, std::string* storage) {
|
||||
if (k == 0) {
|
||||
// Ugh. Random seed of 0 used to produce no entropy. This code
|
||||
// preserves the implementation that was in place when all of the
|
||||
// magic values in this file were picked.
|
||||
*storage = std::string(kValueSize, ' ');
|
||||
return Slice(*storage);
|
||||
} else {
|
||||
Random r(k);
|
||||
return test::RandomString(&r, kValueSize, storage);
|
||||
}
|
||||
}
|
||||
|
||||
void Build(int base, int n, int flush_every = 0) {
|
||||
std::string key_space, value_space;
|
||||
WriteBatch batch;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (flush_every != 0 && i != 0 && i % flush_every == 0) {
|
||||
DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
|
||||
dbi->TEST_FlushMemTable();
|
||||
}
|
||||
|
||||
int keyi = base + i;
|
||||
Slice key(DBTestBase::Key(keyi));
|
||||
|
||||
batch.Clear();
|
||||
batch.Put(handles_[0], key, Value(keyi, &value_space));
|
||||
batch.Put(handles_[1], key, Value(keyi, &value_space));
|
||||
batch.Put(handles_[2], key, Value(keyi, &value_space));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
}
|
||||
}
|
||||
|
||||
void CheckMissed() {
|
||||
uint64_t next_expected = 0;
|
||||
uint64_t missed = 0;
|
||||
int bad_keys = 0;
|
||||
int bad_values = 0;
|
||||
int correct = 0;
|
||||
std::string value_space;
|
||||
for (int cf = 0; cf < 3; cf++) {
|
||||
next_expected = 0;
|
||||
Iterator* iter = db_->NewIterator(ReadOptions(false, true), handles_[cf]);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
uint64_t key;
|
||||
Slice in(iter->key());
|
||||
in.remove_prefix(3);
|
||||
if (!ConsumeDecimalNumber(&in, &key) || !in.empty() ||
|
||||
key < next_expected) {
|
||||
bad_keys++;
|
||||
continue;
|
||||
}
|
||||
missed += (key - next_expected);
|
||||
next_expected = key + 1;
|
||||
if (iter->value() != Value(static_cast<int>(key), &value_space)) {
|
||||
bad_values++;
|
||||
} else {
|
||||
correct++;
|
||||
}
|
||||
}
|
||||
delete iter;
|
||||
}
|
||||
|
||||
ASSERT_EQ(0, bad_keys);
|
||||
ASSERT_EQ(0, bad_values);
|
||||
ASSERT_EQ(0, missed);
|
||||
(void)correct;
|
||||
}
|
||||
|
||||
void Close() {
|
||||
for (auto h : handles_) {
|
||||
if (h) {
|
||||
delete h;
|
||||
db_->DestroyColumnFamilyHandle(h);
|
||||
}
|
||||
}
|
||||
handles_.clear();
|
||||
@@ -184,7 +260,7 @@ class ColumnFamilyTest : public testing::Test {
|
||||
void DropColumnFamilies(const std::vector<int>& cfs) {
|
||||
for (auto cf : cfs) {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[cf]));
|
||||
delete handles_[cf];
|
||||
db_->DestroyColumnFamilyHandle(handles_[cf]);
|
||||
handles_[cf] = nullptr;
|
||||
names_[cf] = "";
|
||||
}
|
||||
@@ -426,6 +502,135 @@ TEST_F(ColumnFamilyTest, DontReuseColumnFamilyID) {
|
||||
}
|
||||
}
|
||||
|
||||
class FlushEmptyCFTestWithParam : public ColumnFamilyTest,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
FlushEmptyCFTestWithParam() { allow_2pc_ = GetParam(); }
|
||||
|
||||
// Required if inheriting from testing::WithParamInterface<>
|
||||
static void SetUpTestCase() {}
|
||||
static void TearDownTestCase() {}
|
||||
|
||||
bool allow_2pc_;
|
||||
};
|
||||
|
||||
TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
db_options_.env = fault_env.get();
|
||||
db_options_.allow_2pc = allow_2pc_;
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
// Generate log file A.
|
||||
ASSERT_OK(Put(1, "foo", "v1")); // seqID 1
|
||||
|
||||
Reopen();
|
||||
// Log file A is not dropped after reopening because default column family's
|
||||
// min log number is 0.
|
||||
// It flushes to SST file X
|
||||
ASSERT_OK(Put(1, "foo", "v1")); // seqID 2
|
||||
ASSERT_OK(Put(1, "bar", "v2")); // seqID 3
|
||||
// Current log file is file B now. While flushing, a new log file C is created
|
||||
// and is set to current. Boths' min log number is set to file C in memory, so
|
||||
// after flushing file B is deleted. At the same time, the min log number of
|
||||
// default CF is not written to manifest. Log file A still remains.
|
||||
// Flushed to SST file Y.
|
||||
Flush(1);
|
||||
Flush(0);
|
||||
ASSERT_OK(Put(1, "bar", "v3")); // seqID 4
|
||||
ASSERT_OK(Put(1, "foo", "v4")); // seqID 5
|
||||
|
||||
// Preserve file system state up to here to simulate a crash condition.
|
||||
fault_env->SetFilesystemActive(false);
|
||||
std::vector<std::string> names;
|
||||
for (auto name : names_) {
|
||||
if (name != "") {
|
||||
names.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
fault_env->ResetState();
|
||||
|
||||
// Before opening, there are four files:
|
||||
// Log file A contains seqID 1
|
||||
// Log file C contains seqID 4, 5
|
||||
// SST file X contains seqID 1
|
||||
// SST file Y contains seqID 2, 3
|
||||
// Min log number:
|
||||
// default CF: 0
|
||||
// CF one, two: C
|
||||
// When opening the DB, all the seqID should be preserved.
|
||||
Open(names, {});
|
||||
ASSERT_EQ("v4", Get(1, "foo"));
|
||||
ASSERT_EQ("v3", Get(1, "bar"));
|
||||
Close();
|
||||
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest2) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
db_options_.env = fault_env.get();
|
||||
db_options_.allow_2pc = allow_2pc_;
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two"});
|
||||
// Generate log file A.
|
||||
ASSERT_OK(Put(1, "foo", "v1")); // seqID 1
|
||||
|
||||
Reopen();
|
||||
// Log file A is not dropped after reopening because default column family's
|
||||
// min log number is 0.
|
||||
// It flushes to SST file X
|
||||
ASSERT_OK(Put(1, "foo", "v1")); // seqID 2
|
||||
ASSERT_OK(Put(1, "bar", "v2")); // seqID 3
|
||||
// Current log file is file B now. While flushing, a new log file C is created
|
||||
// and is set to current. Both CFs' min log number is set to file C so after
|
||||
// flushing file B is deleted. Log file A still remains.
|
||||
// Flushed to SST file Y.
|
||||
Flush(1);
|
||||
ASSERT_OK(Put(0, "bar", "v2")); // seqID 4
|
||||
ASSERT_OK(Put(2, "bar", "v2")); // seqID 5
|
||||
ASSERT_OK(Put(1, "bar", "v3")); // seqID 6
|
||||
// Flushing all column families. This forces all CFs' min log to current. This
|
||||
// is written to the manifest file. Log file C is cleared.
|
||||
Flush(0);
|
||||
Flush(1);
|
||||
Flush(2);
|
||||
// Write to log file D
|
||||
ASSERT_OK(Put(1, "bar", "v4")); // seqID 7
|
||||
ASSERT_OK(Put(1, "bar", "v5")); // seqID 8
|
||||
// Preserve file system state up to here to simulate a crash condition.
|
||||
fault_env->SetFilesystemActive(false);
|
||||
std::vector<std::string> names;
|
||||
for (auto name : names_) {
|
||||
if (name != "") {
|
||||
names.push_back(name);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
fault_env->ResetState();
|
||||
// Before opening, there are two logfiles:
|
||||
// Log file A contains seqID 1
|
||||
// Log file D contains seqID 7, 8
|
||||
// Min log number:
|
||||
// default CF: D
|
||||
// CF one, two: D
|
||||
// When opening the DB, log file D should be replayed using the seqID
|
||||
// specified in the file.
|
||||
Open(names, {});
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
ASSERT_EQ("v5", Get(1, "bar"));
|
||||
Close();
|
||||
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(FlushEmptyCFTestWithParam, FlushEmptyCFTestWithParam,
|
||||
::testing::Bool());
|
||||
|
||||
TEST_F(ColumnFamilyTest, AddDrop) {
|
||||
Open();
|
||||
CreateColumnFamilies({"one", "two", "three"});
|
||||
@@ -447,7 +652,7 @@ TEST_F(ColumnFamilyTest, AddDrop) {
|
||||
|
||||
std::vector<std::string> families;
|
||||
ASSERT_OK(DB::ListColumnFamilies(db_options_, dbname_, &families));
|
||||
sort(families.begin(), families.end());
|
||||
std::sort(families.begin(), families.end());
|
||||
ASSERT_TRUE(families ==
|
||||
std::vector<std::string>({"default", "four", "three"}));
|
||||
}
|
||||
@@ -708,6 +913,38 @@ TEST_F(ColumnFamilyTest, LogDeletionTest) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, CrashAfterFlush) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
db_options_.env = fault_env.get();
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
|
||||
WriteBatch batch;
|
||||
batch.Put(handles_[0], Slice("foo"), Slice("bar"));
|
||||
batch.Put(handles_[1], Slice("foo"), Slice("bar"));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
Flush(0);
|
||||
fault_env->SetFilesystemActive(false);
|
||||
|
||||
std::vector<std::string> names;
|
||||
for (auto name : names_) {
|
||||
if (name != "") {
|
||||
names.push_back(name);
|
||||
}
|
||||
}
|
||||
Close();
|
||||
fault_env->DropUnsyncedFileData();
|
||||
fault_env->ResetState();
|
||||
Open(names, {});
|
||||
|
||||
// Write batch should be atomic.
|
||||
ASSERT_EQ(Get(0, "foo"), Get(1, "foo"));
|
||||
|
||||
Close();
|
||||
db_options_.env = env_;
|
||||
}
|
||||
|
||||
// Makes sure that obsolete log files get deleted
|
||||
TEST_F(ColumnFamilyTest, DifferentWriteBufferSizes) {
|
||||
// disable flushing stale column families
|
||||
@@ -836,6 +1073,39 @@ TEST_F(ColumnFamilyTest, MemtableNotSupportSnapshot) {
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class TestComparator : public Comparator {
|
||||
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 {}
|
||||
};
|
||||
|
||||
static TestComparator third_comparator;
|
||||
static TestComparator fourth_comparator;
|
||||
|
||||
// Test that we can retrieve the comparator from a created CF
|
||||
TEST_F(ColumnFamilyTest, GetComparator) {
|
||||
Open();
|
||||
// Add a column family with no comparator specified
|
||||
CreateColumnFamilies({"first"});
|
||||
const Comparator* comp = handles_[0]->GetComparator();
|
||||
ASSERT_EQ(comp, BytewiseComparator());
|
||||
|
||||
// Add three column families - one with no comparator and two
|
||||
// with comparators specified
|
||||
ColumnFamilyOptions second, third, fourth;
|
||||
second.comparator = &third_comparator;
|
||||
third.comparator = &fourth_comparator;
|
||||
CreateColumnFamilies({"second", "third", "fourth"}, {second, third, fourth});
|
||||
ASSERT_EQ(handles_[1]->GetComparator(), BytewiseComparator());
|
||||
ASSERT_EQ(handles_[2]->GetComparator(), &third_comparator);
|
||||
ASSERT_EQ(handles_[3]->GetComparator(), &fourth_comparator);
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, DifferentMergeOperators) {
|
||||
Open();
|
||||
CreateColumnFamilies({"first", "second"});
|
||||
@@ -877,7 +1147,8 @@ TEST_F(ColumnFamilyTest, DifferentCompactionStyles) {
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.source_compaction_factor = 100;
|
||||
default_cf.max_compaction_bytes = static_cast<uint64_t>(1) << 60;
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
default_cf.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
@@ -949,7 +1220,7 @@ TEST_F(ColumnFamilyTest, MultipleManualCompactions) {
|
||||
default_cf.num_levels = 3;
|
||||
default_cf.write_buffer_size = 64 << 10; // 64KB
|
||||
default_cf.target_file_size_base = 30 << 10;
|
||||
default_cf.source_compaction_factor = 100;
|
||||
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));
|
||||
@@ -1042,12 +1313,13 @@ TEST_F(ColumnFamilyTest, AutomaticAndManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_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.source_compaction_factor = 100;
|
||||
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));
|
||||
@@ -1134,12 +1406,13 @@ TEST_F(ColumnFamilyTest, ManualAndAutomaticCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_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.source_compaction_factor = 100;
|
||||
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));
|
||||
@@ -1229,12 +1502,13 @@ TEST_F(ColumnFamilyTest, SameCFManualManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_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.source_compaction_factor = 100;
|
||||
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));
|
||||
@@ -1327,12 +1601,13 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_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.source_compaction_factor = 100;
|
||||
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));
|
||||
@@ -1416,12 +1691,13 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticCompactionsLevel) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_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.source_compaction_factor = 100;
|
||||
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));
|
||||
@@ -1512,12 +1788,13 @@ TEST_F(ColumnFamilyTest, SameCFManualAutomaticConflict) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_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.source_compaction_factor = 100;
|
||||
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));
|
||||
@@ -1631,12 +1908,13 @@ TEST_F(ColumnFamilyTest, SameCFAutomaticManualCompactions) {
|
||||
db_options_.max_open_files = 20; // only 10 files in file cache
|
||||
db_options_.disableDataSync = true;
|
||||
db_options_.max_background_compactions = 3;
|
||||
db_options_.base_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.source_compaction_factor = 100;
|
||||
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));
|
||||
@@ -1869,6 +2147,9 @@ TEST_F(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
// 3 files for default column families, 1 file for column family [two], zero
|
||||
// files for column family [one], because it's empty
|
||||
AssertCountLiveFiles(4);
|
||||
|
||||
Flush(0);
|
||||
ASSERT_EQ(0, dbfull()->TEST_total_log_size());
|
||||
Close();
|
||||
}
|
||||
|
||||
@@ -1962,7 +2243,7 @@ TEST_F(ColumnFamilyTest, ReadDroppedColumnFamily) {
|
||||
ASSERT_OK(db_->DropColumnFamily(handles_[2]));
|
||||
} else {
|
||||
// delete CF two
|
||||
delete handles_[2];
|
||||
db_->DestroyColumnFamilyHandle(handles_[2]);
|
||||
handles_[2] = nullptr;
|
||||
}
|
||||
// Make sure iterator created can still be used.
|
||||
@@ -2167,6 +2448,7 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
mutable_cf_options.level0_stop_writes_trigger = 10000;
|
||||
mutable_cf_options.soft_pending_compaction_bytes_limit = 200;
|
||||
mutable_cf_options.hard_pending_compaction_bytes_limit = 2000;
|
||||
mutable_cf_options.disable_auto_compactions = false;
|
||||
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(50);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
@@ -2313,16 +2595,17 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
vstorage->set_l0_delay_trigger_count(50);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate, dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
vstorage->set_l0_delay_trigger_count(60);
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(300);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_TRUE(!dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate, dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
mutable_cf_options.disable_auto_compactions = false;
|
||||
vstorage->set_l0_delay_trigger_count(70);
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(500);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
@@ -2330,7 +2613,6 @@ TEST_F(ColumnFamilyTest, WriteStallSingleColumnFamily) {
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
ASSERT_EQ(kBaseRate, dbfull()->TEST_write_controler().delayed_write_rate());
|
||||
|
||||
mutable_cf_options.disable_auto_compactions = false;
|
||||
vstorage->set_l0_delay_trigger_count(71);
|
||||
vstorage->TEST_set_estimated_compaction_needed_bytes(501);
|
||||
cfd->RecalculateWriteStallConditions(mutable_cf_options);
|
||||
@@ -2557,6 +2839,226 @@ TEST_F(ColumnFamilyTest, CompactionSpeedupTwoColumnFamilies) {
|
||||
ASSERT_EQ(2, dbfull()->BGCompactionsAllowed());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(ColumnFamilyTest, FlushCloseWALFiles) {
|
||||
SpecialEnv env(Env::Default());
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(0, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBImpl::BGWorkFlush:done", "FlushCloseWALFiles:0"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Block flush jobs from running
|
||||
test::SleepingBackgroundTask sleeping_task;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task,
|
||||
Env::Priority::HIGH);
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = true;
|
||||
ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko"));
|
||||
|
||||
ASSERT_EQ(2, env.num_open_wal_file_.load());
|
||||
|
||||
sleeping_task.WakeUp();
|
||||
sleeping_task.WaitUntilDone();
|
||||
TEST_SYNC_POINT("FlushCloseWALFiles:0");
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ASSERT_EQ(1, env.num_open_wal_file_.load());
|
||||
|
||||
Reopen();
|
||||
ASSERT_EQ("mirko", Get(0, "fodor"));
|
||||
ASSERT_EQ("mirko", Get(1, "fodor"));
|
||||
db_options_.env = env_;
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
SpecialEnv env(Env::Default());
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
// Create an iterator holding the current super version.
|
||||
Iterator* it = db_->NewIterator(ReadOptions(), handles_[1]);
|
||||
// A flush will make `it` hold the last reference of its super version.
|
||||
Flush(1);
|
||||
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(0, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
|
||||
// Flush jobs will close previous WAL files after finishing. By
|
||||
// block flush jobs from running, we trigger a condition where
|
||||
// the iterator destructor should close the WAL files.
|
||||
test::SleepingBackgroundTask sleeping_task;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask, &sleeping_task,
|
||||
Env::Priority::HIGH);
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = true;
|
||||
ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko"));
|
||||
|
||||
ASSERT_EQ(2, env.num_open_wal_file_.load());
|
||||
// Deleting the iterator will clear its super version, triggering
|
||||
// closing all files
|
||||
delete it;
|
||||
ASSERT_EQ(1, env.num_open_wal_file_.load());
|
||||
|
||||
sleeping_task.WakeUp();
|
||||
sleeping_task.WaitUntilDone();
|
||||
WaitForFlush(1);
|
||||
|
||||
Reopen();
|
||||
ASSERT_EQ("mirko", Get(0, "fodor"));
|
||||
ASSERT_EQ("mirko", Get(1, "fodor"));
|
||||
db_options_.env = env_;
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
SpecialEnv env(Env::Default());
|
||||
// Allow both of flush and purge job to schedule.
|
||||
env.SetBackgroundThreads(2, Env::HIGH);
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
// Create an iterator holding the current super version.
|
||||
ReadOptions ro;
|
||||
ro.background_purge_on_iterator_cleanup = true;
|
||||
Iterator* it = db_->NewIterator(ro, handles_[1]);
|
||||
// A flush will make `it` hold the last reference of its super version.
|
||||
Flush(1);
|
||||
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(0, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"ColumnFamilyTest::IteratorCloseWALFile2:0",
|
||||
"DBImpl::BGWorkPurge:start"},
|
||||
{"ColumnFamilyTest::IteratorCloseWALFile2:2",
|
||||
"DBImpl::BackgroundCallFlush:start"},
|
||||
{"DBImpl::BGWorkPurge:end", "ColumnFamilyTest::IteratorCloseWALFile2:1"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = true;
|
||||
ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko"));
|
||||
|
||||
ASSERT_EQ(2, env.num_open_wal_file_.load());
|
||||
// Deleting the iterator will clear its super version, triggering
|
||||
// closing all files
|
||||
delete it;
|
||||
ASSERT_EQ(2, env.num_open_wal_file_.load());
|
||||
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:0");
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:1");
|
||||
ASSERT_EQ(1, env.num_open_wal_file_.load());
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:2");
|
||||
WaitForFlush(1);
|
||||
ASSERT_EQ(1, env.num_open_wal_file_.load());
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
Reopen();
|
||||
ASSERT_EQ("mirko", Get(0, "fodor"));
|
||||
ASSERT_EQ("mirko", Get(1, "fodor"));
|
||||
db_options_.env = env_;
|
||||
Close();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE // TEST functions are not supported in lite
|
||||
TEST_F(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
SpecialEnv env(Env::Default());
|
||||
// Allow both of flush and purge job to schedule.
|
||||
env.SetBackgroundThreads(2, Env::HIGH);
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(3));
|
||||
column_family_options_.level0_file_num_compaction_trigger = 2;
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodar2", "mirko"));
|
||||
Flush(1);
|
||||
|
||||
// Create an iterator holding the current super version, as well as
|
||||
// the SST file just flushed.
|
||||
ReadOptions ro;
|
||||
ro.tailing = true;
|
||||
ro.background_purge_on_iterator_cleanup = true;
|
||||
Iterator* it = db_->NewIterator(ro, handles_[1]);
|
||||
// A flush will make `it` hold the last reference of its super version.
|
||||
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodar2", "mirko"));
|
||||
Flush(1);
|
||||
|
||||
WaitForCompaction();
|
||||
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(0, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"ColumnFamilyTest::IteratorCloseWALFile2:0",
|
||||
"DBImpl::BGWorkPurge:start"},
|
||||
{"ColumnFamilyTest::IteratorCloseWALFile2:2",
|
||||
"DBImpl::BackgroundCallFlush:start"},
|
||||
{"DBImpl::BGWorkPurge:end", "ColumnFamilyTest::IteratorCloseWALFile2:1"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
WriteOptions wo;
|
||||
wo.sync = true;
|
||||
ASSERT_OK(db_->Put(wo, handles_[1], "fodor", "mirko"));
|
||||
|
||||
env.delete_count_.store(0);
|
||||
ASSERT_EQ(2, env.num_open_wal_file_.load());
|
||||
// Deleting the iterator will clear its super version, triggering
|
||||
// closing all files
|
||||
it->Seek("");
|
||||
ASSERT_EQ(2, env.num_open_wal_file_.load());
|
||||
ASSERT_EQ(0, env.delete_count_.load());
|
||||
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:0");
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:1");
|
||||
ASSERT_EQ(1, env.num_open_wal_file_.load());
|
||||
ASSERT_EQ(1, env.delete_count_.load());
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::IteratorCloseWALFile2:2");
|
||||
WaitForFlush(1);
|
||||
ASSERT_EQ(1, env.num_open_wal_file_.load());
|
||||
ASSERT_EQ(1, env.delete_count_.load());
|
||||
|
||||
delete it;
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
Reopen();
|
||||
ASSERT_EQ("mirko", Get(0, "fodor"));
|
||||
ASSERT_EQ("mirko", Get(1, "fodor"));
|
||||
db_options_.env = env_;
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Disable on windows because SyncWAL requires env->IsSyncThreadSafe()
|
||||
// to return true which is not so in unbuffered mode.
|
||||
#ifndef OS_WIN
|
||||
TEST_F(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
@@ -2586,6 +3088,81 @@ TEST_F(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
Close();
|
||||
}
|
||||
#endif
|
||||
|
||||
// this test is placed here, because the infrastructure for Column Family
|
||||
// test is being used to ensure a roll of wal files.
|
||||
// Basic idea is to test that WAL truncation is being detected and not
|
||||
// ignored
|
||||
TEST_F(ColumnFamilyTest, DISABLED_LogTruncationTest) {
|
||||
Open();
|
||||
CreateColumnFamiliesAndReopen({"one", "two"});
|
||||
|
||||
Build(0, 100);
|
||||
|
||||
// Flush the 0th column family to force a roll of the wal log
|
||||
Flush(0);
|
||||
|
||||
// Add some more entries
|
||||
Build(100, 100);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
|
||||
// collect wal files
|
||||
std::vector<std::string> logfs;
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
if (!(ParseFileName(filenames[i], &number, &type))) continue;
|
||||
|
||||
if (type != kLogFile) continue;
|
||||
|
||||
logfs.push_back(filenames[i]);
|
||||
}
|
||||
|
||||
std::sort(logfs.begin(), logfs.end());
|
||||
ASSERT_GE(logfs.size(), 2);
|
||||
|
||||
// Take the last but one file, and truncate it
|
||||
std::string fpath = dbname_ + "/" + logfs[logfs.size() - 2];
|
||||
std::vector<std::string> names_save = names_;
|
||||
|
||||
uint64_t fsize;
|
||||
ASSERT_OK(env_->GetFileSize(fpath, &fsize));
|
||||
ASSERT_GT(fsize, 0);
|
||||
|
||||
Close();
|
||||
|
||||
std::string backup_logs = dbname_ + "/backup_logs";
|
||||
std::string t_fpath = backup_logs + "/" + logfs[logfs.size() - 2];
|
||||
|
||||
ASSERT_OK(env_->CreateDirIfMissing(backup_logs));
|
||||
// Not sure how easy it is to make this data driven.
|
||||
// need to read back the WAL file and truncate last 10
|
||||
// entries
|
||||
CopyFile(fpath, t_fpath, fsize - 9180);
|
||||
|
||||
ASSERT_OK(env_->DeleteFile(fpath));
|
||||
ASSERT_OK(env_->RenameFile(t_fpath, fpath));
|
||||
|
||||
db_options_.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
|
||||
OpenReadOnly(names_save);
|
||||
|
||||
CheckMissed();
|
||||
|
||||
Close();
|
||||
|
||||
Open(names_save);
|
||||
|
||||
CheckMissed();
|
||||
|
||||
Close();
|
||||
|
||||
// cleanup
|
||||
env_->DeleteDir(backup_logs);
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "db/db_impl.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -49,6 +50,7 @@ class FlushedFileCollector : public EventListener {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void ClearFlushedFiles() { flushed_files_.clear(); }
|
||||
|
||||
private:
|
||||
std::vector<std::string> flushed_files_;
|
||||
@@ -115,13 +117,12 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
Options options;
|
||||
// to trigger compaction more easily
|
||||
const int kWriteBufferSize = 10000;
|
||||
const int kWriteBufferSize = 65536;
|
||||
options.create_if_missing = true;
|
||||
// Disable RocksDB background compaction.
|
||||
options.compaction_style = kCompactionStyleNone;
|
||||
// Small slowdown and stop trigger for experimental purpose.
|
||||
options.level0_slowdown_writes_trigger = 20;
|
||||
options.level0_stop_writes_trigger = 20;
|
||||
options.level0_slowdown_writes_trigger = (1 << 30);
|
||||
options.level0_stop_writes_trigger = (1 << 30);
|
||||
options.write_buffer_size = kWriteBufferSize;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.compression = kNoCompression;
|
||||
@@ -144,6 +145,7 @@ TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, 1));
|
||||
reinterpret_cast<DBImpl*>(db)->TEST_WaitForCompact();
|
||||
|
||||
// verify all compaction input files are deleted
|
||||
for (auto fname : l0_files) {
|
||||
@@ -152,6 +154,46 @@ TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, NotCutOutputOnLevel0) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
// Disable RocksDB background compaction.
|
||||
options.compaction_style = kCompactionStyleNone;
|
||||
options.level0_slowdown_writes_trigger = 1000;
|
||||
options.level0_stop_writes_trigger = 1000;
|
||||
options.write_buffer_size = 65536;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.compression = kNoCompression;
|
||||
options.max_compaction_bytes = 5000;
|
||||
|
||||
// Add listener
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
assert(s.ok());
|
||||
assert(db);
|
||||
|
||||
// create couple files
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
|
||||
}
|
||||
reinterpret_cast<DBImpl*>(db)->TEST_WaitForFlushMemTable();
|
||||
auto l0_files_1 = collector->GetFlushedFiles();
|
||||
collector->ClearFlushedFiles();
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
|
||||
}
|
||||
reinterpret_cast<DBImpl*>(db)->TEST_WaitForFlushMemTable();
|
||||
auto l0_files_2 = collector->GetFlushedFiles();
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files_1, 0));
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files_2, 0));
|
||||
// no assertion failure
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, CapturingPendingFiles) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
|
||||
@@ -93,7 +93,7 @@ Status CompactedDBImpl::Init(const Options& options) {
|
||||
mutex_.Lock();
|
||||
ColumnFamilyDescriptor cf(kDefaultColumnFamilyName,
|
||||
ColumnFamilyOptions(options));
|
||||
Status s = Recover({ cf }, true /* read only */, false);
|
||||
Status s = Recover({cf}, true /* read only */, false, true);
|
||||
if (s.ok()) {
|
||||
cfd_ = reinterpret_cast<ColumnFamilyHandleImpl*>(
|
||||
DefaultColumnFamily())->cfd();
|
||||
|
||||
+16
-46
@@ -16,8 +16,8 @@
|
||||
#include <inttypes.h>
|
||||
#include <vector>
|
||||
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "db/column_family.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
@@ -143,8 +143,8 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& _mutable_cf_options,
|
||||
std::vector<CompactionInputFiles> _inputs,
|
||||
int _output_level, uint64_t _target_file_size,
|
||||
uint64_t _max_grandparent_overlap_bytes,
|
||||
uint32_t _output_path_id, CompressionType _compression,
|
||||
uint64_t _max_compaction_bytes, uint32_t _output_path_id,
|
||||
CompressionType _compression,
|
||||
std::vector<FileMetaData*> _grandparents,
|
||||
bool _manual_compaction, double _score,
|
||||
bool _deletion_compaction,
|
||||
@@ -152,7 +152,7 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
: start_level_(_inputs[0].level),
|
||||
output_level_(_output_level),
|
||||
max_output_file_size_(_target_file_size),
|
||||
max_grandparent_overlap_bytes_(_max_grandparent_overlap_bytes),
|
||||
max_compaction_bytes_(_max_compaction_bytes),
|
||||
mutable_cf_options_(_mutable_cf_options),
|
||||
input_version_(nullptr),
|
||||
number_levels_(vstorage->num_levels()),
|
||||
@@ -162,9 +162,6 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
deletion_compaction_(_deletion_compaction),
|
||||
inputs_(std::move(_inputs)),
|
||||
grandparents_(std::move(_grandparents)),
|
||||
grandparent_index_(0),
|
||||
seen_key_(false),
|
||||
overlapped_bytes_(0),
|
||||
score_(_score),
|
||||
bottommost_level_(IsBottommostLevel(output_level_, vstorage, inputs_)),
|
||||
is_full_compaction_(IsFullCompaction(vstorage, inputs_)),
|
||||
@@ -190,8 +187,7 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
}
|
||||
}
|
||||
|
||||
Slice smallest_user_key;
|
||||
GetBoundaryKeys(vstorage, inputs_, &smallest_user_key, &largest_user_key_);
|
||||
GetBoundaryKeys(vstorage, inputs_, &smallest_user_key_, &largest_user_key_);
|
||||
}
|
||||
|
||||
Compaction::~Compaction() {
|
||||
@@ -206,9 +202,11 @@ Compaction::~Compaction() {
|
||||
}
|
||||
|
||||
bool Compaction::InputCompressionMatchesOutput() const {
|
||||
int base_level = input_version_->storage_info()->base_level();
|
||||
bool matches = (GetCompressionType(*cfd_->ioptions(), start_level_,
|
||||
base_level) == output_compression_);
|
||||
VersionStorageInfo* vstorage = input_version_->storage_info();
|
||||
int base_level = vstorage->base_level();
|
||||
bool matches =
|
||||
(GetCompressionType(*cfd_->ioptions(), vstorage, mutable_cf_options_,
|
||||
start_level_, base_level) == output_compression_);
|
||||
if (matches) {
|
||||
TEST_SYNC_POINT("Compaction::InputCompressionMatchesOutput:Matches");
|
||||
return true;
|
||||
@@ -249,7 +247,7 @@ bool Compaction::IsTrivialMove() const {
|
||||
return (start_level_ != output_level_ && num_input_levels() == 1 &&
|
||||
input(0, 0)->fd.GetPathId() == output_path_id() &&
|
||||
InputCompressionMatchesOutput() &&
|
||||
TotalFileSize(grandparents_) <= max_grandparent_overlap_bytes_);
|
||||
TotalFileSize(grandparents_) <= max_compaction_bytes_);
|
||||
}
|
||||
|
||||
void Compaction::AddInputDeletions(VersionEdit* out_edit) {
|
||||
@@ -290,38 +288,12 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Compaction::ShouldStopBefore(const Slice& internal_key) {
|
||||
// Scan to find earliest grandparent file that contains key.
|
||||
const InternalKeyComparator* icmp = &cfd_->internal_comparator();
|
||||
while (grandparent_index_ < grandparents_.size() &&
|
||||
icmp->Compare(internal_key,
|
||||
grandparents_[grandparent_index_]->largest.Encode()) > 0) {
|
||||
if (seen_key_) {
|
||||
overlapped_bytes_ += grandparents_[grandparent_index_]->fd.GetFileSize();
|
||||
}
|
||||
assert(grandparent_index_ + 1 >= grandparents_.size() ||
|
||||
icmp->Compare(grandparents_[grandparent_index_]->largest.Encode(),
|
||||
grandparents_[grandparent_index_+1]->smallest.Encode())
|
||||
< 0);
|
||||
grandparent_index_++;
|
||||
}
|
||||
seen_key_ = true;
|
||||
|
||||
if (overlapped_bytes_ > max_grandparent_overlap_bytes_) {
|
||||
// Too much overlap for current output; start new output
|
||||
overlapped_bytes_ = 0;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark (or clear) each file that is being compacted
|
||||
void Compaction::MarkFilesBeingCompacted(bool mark_as_compacted) {
|
||||
for (size_t i = 0; i < num_input_levels(); i++) {
|
||||
for (size_t j = 0; j < inputs_[i].size(); j++) {
|
||||
assert(mark_as_compacted ? !inputs_[i][j]->being_compacted :
|
||||
inputs_[i][j]->being_compacted);
|
||||
assert(mark_as_compacted ? !inputs_[i][j]->being_compacted
|
||||
: inputs_[i][j]->being_compacted);
|
||||
inputs_[i][j]->being_compacted = mark_as_compacted;
|
||||
}
|
||||
}
|
||||
@@ -396,10 +368,8 @@ int InputSummary(const std::vector<FileMetaData*>& files, char* output,
|
||||
|
||||
void Compaction::Summary(char* output, int len) {
|
||||
int write =
|
||||
snprintf(output, len, "Base version %" PRIu64
|
||||
" Base level %d, inputs: [",
|
||||
input_version_->GetVersionNumber(),
|
||||
start_level_);
|
||||
snprintf(output, len, "Base version %" PRIu64 " Base level %d, inputs: [",
|
||||
input_version_->GetVersionNumber(), start_level_);
|
||||
if (write < 0 || write >= len) {
|
||||
return;
|
||||
}
|
||||
@@ -421,7 +391,7 @@ void Compaction::Summary(char* output, int len) {
|
||||
snprintf(output + write, len - write, "]");
|
||||
}
|
||||
|
||||
uint64_t Compaction::OutputFilePreallocationSize() {
|
||||
uint64_t Compaction::OutputFilePreallocationSize() const {
|
||||
uint64_t preallocation_size = 0;
|
||||
|
||||
if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel ||
|
||||
|
||||
+25
-20
@@ -8,10 +8,10 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
#include "db/version_set.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/mutable_cf_options.h"
|
||||
#include "db/version_set.h"
|
||||
#include "util/cf_options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -37,7 +37,7 @@ class Compaction {
|
||||
Compaction(VersionStorageInfo* input_version,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
std::vector<CompactionInputFiles> inputs, int output_level,
|
||||
uint64_t target_file_size, uint64_t max_grandparent_overlap_bytes,
|
||||
uint64_t target_file_size, uint64_t max_compaction_bytes,
|
||||
uint32_t output_path_id, CompressionType compression,
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
bool manual_compaction = false, double score = -1,
|
||||
@@ -104,7 +104,7 @@ class Compaction {
|
||||
}
|
||||
|
||||
// Returns the LevelFilesBrief of the specified compaction input level.
|
||||
LevelFilesBrief* input_levels(size_t compaction_input_level) {
|
||||
const LevelFilesBrief* input_levels(size_t compaction_input_level) const {
|
||||
return &input_levels_[compaction_input_level];
|
||||
}
|
||||
|
||||
@@ -132,10 +132,6 @@ class Compaction {
|
||||
bool KeyNotExistsBeyondOutputLevel(const Slice& user_key,
|
||||
std::vector<size_t>* level_ptrs) const;
|
||||
|
||||
// Returns true iff we should stop building the current output
|
||||
// before processing "internal_key".
|
||||
bool ShouldStopBefore(const Slice& internal_key);
|
||||
|
||||
// Clear all files to indicate that they are not being compacted
|
||||
// Delete this compaction from the list of running compactions.
|
||||
//
|
||||
@@ -151,13 +147,13 @@ class Compaction {
|
||||
double score() const { return score_; }
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level() { return bottommost_level_; }
|
||||
bool bottommost_level() const { return bottommost_level_; }
|
||||
|
||||
// Does this compaction include all sst files?
|
||||
bool is_full_compaction() { return is_full_compaction_; }
|
||||
bool is_full_compaction() const { return is_full_compaction_; }
|
||||
|
||||
// Was this compaction triggered manually by the client?
|
||||
bool is_manual_compaction() { return is_manual_compaction_; }
|
||||
bool is_manual_compaction() const { return is_manual_compaction_; }
|
||||
|
||||
// Used when allow_trivial_move option is set in
|
||||
// Universal compaction. If all the input files are
|
||||
@@ -170,19 +166,21 @@ class Compaction {
|
||||
// Used when allow_trivial_move option is set in
|
||||
// Universal compaction. Returns true, if the input files
|
||||
// are non-overlapping and can be trivially moved.
|
||||
bool is_trivial_move() { return is_trivial_move_; }
|
||||
bool is_trivial_move() const { return is_trivial_move_; }
|
||||
|
||||
// How many total levels are there?
|
||||
int number_levels() const { return number_levels_; }
|
||||
|
||||
// Return the MutableCFOptions that should be used throughout the compaction
|
||||
// procedure
|
||||
const MutableCFOptions* mutable_cf_options() { return &mutable_cf_options_; }
|
||||
const MutableCFOptions* mutable_cf_options() const {
|
||||
return &mutable_cf_options_;
|
||||
}
|
||||
|
||||
// Returns the size in bytes that the output file should be preallocated to.
|
||||
// In level compaction, that is max_file_size_. In universal compaction, that
|
||||
// is the sum of all input file sizes.
|
||||
uint64_t OutputFilePreallocationSize();
|
||||
uint64_t OutputFilePreallocationSize() const;
|
||||
|
||||
void SetInputVersion(Version* input_version);
|
||||
|
||||
@@ -221,10 +219,18 @@ class Compaction {
|
||||
output_table_properties_ = std::move(tp);
|
||||
}
|
||||
|
||||
Slice GetSmallestUserKey() const { return smallest_user_key_; }
|
||||
|
||||
Slice GetLargestUserKey() const { return largest_user_key_; }
|
||||
|
||||
CompactionReason compaction_reason() { return compaction_reason_; }
|
||||
|
||||
const std::vector<FileMetaData*>& grandparents() const {
|
||||
return grandparents_;
|
||||
}
|
||||
|
||||
uint64_t max_compaction_bytes() const { return max_compaction_bytes_; }
|
||||
|
||||
private:
|
||||
// mark (or clear) all files that are being compacted
|
||||
void MarkFilesBeingCompacted(bool mark_as_compacted);
|
||||
@@ -246,7 +252,7 @@ class Compaction {
|
||||
const int start_level_; // the lowest level to be compacted
|
||||
const int output_level_; // levels to which output files are stored
|
||||
uint64_t max_output_file_size_;
|
||||
uint64_t max_grandparent_overlap_bytes_;
|
||||
uint64_t max_compaction_bytes_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
Version* input_version_;
|
||||
VersionEdit edit_;
|
||||
@@ -265,13 +271,9 @@ class Compaction {
|
||||
// A copy of inputs_, organized more closely in memory
|
||||
autovector<LevelFilesBrief, 2> input_levels_;
|
||||
|
||||
// State used to check for number of of overlapping grandparent files
|
||||
// State used to check for number of overlapping grandparent files
|
||||
// (grandparent == "output_level_ + 1")
|
||||
std::vector<FileMetaData*> grandparents_;
|
||||
size_t grandparent_index_; // Index in grandparent_starts_
|
||||
bool seen_key_; // Some output key has been seen
|
||||
uint64_t overlapped_bytes_; // Bytes of overlap between current output
|
||||
// and grandparent files
|
||||
const double score_; // score that was used to pick this compaction.
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
@@ -292,6 +294,9 @@ class Compaction {
|
||||
// table properties of output files
|
||||
TablePropertiesCollection output_table_properties_;
|
||||
|
||||
// smallest user keys in compaction
|
||||
Slice smallest_user_key_;
|
||||
|
||||
// largest user keys in compaction
|
||||
Slice largest_user_key_;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
bool expect_valid_internal_key, Compaction* compaction,
|
||||
bool expect_valid_internal_key, const Compaction* compaction,
|
||||
const CompactionFilter* compaction_filter, LogBuffer* log_buffer)
|
||||
: input_(input),
|
||||
cmp_(cmp),
|
||||
@@ -49,6 +49,12 @@ CompactionIterator::CompactionIterator(
|
||||
} else {
|
||||
ignore_snapshots_ = false;
|
||||
}
|
||||
input_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
CompactionIterator::~CompactionIterator() {
|
||||
// input_ Iteartor lifetime is longer than pinned_iters_mgr_ lifetime
|
||||
input_->SetPinnedItersMgr(nullptr);
|
||||
}
|
||||
|
||||
void CompactionIterator::ResetRecordCounts() {
|
||||
@@ -83,6 +89,8 @@ void CompactionIterator::Next() {
|
||||
ikey_.user_key = current_key_.GetUserKey();
|
||||
valid_ = true;
|
||||
} else {
|
||||
// We consumed all pinned merge operands, release pinned iterators
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
// MergeHelper moves the iterator to the first record after the merged
|
||||
// records, so even though we reached the end of the merge output, we do
|
||||
// not want to advance the iterator.
|
||||
@@ -246,6 +254,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// those operations for a given key is documented as being undefined. So
|
||||
// we can choose how to handle such a combinations of operations. We will
|
||||
// try to compact out as much as we can in these cases.
|
||||
// We will report counts on these anomalous cases.
|
||||
|
||||
// The easiest way to process a SingleDelete during iteration is to peek
|
||||
// ahead at the next key.
|
||||
@@ -268,6 +277,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// First SingleDelete has been skipped since we already called
|
||||
// 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_) {
|
||||
// Found a matching value, we can drop the single delete and the
|
||||
@@ -277,7 +287,12 @@ void CompactionIterator::NextFromInput() {
|
||||
|
||||
// Note: it doesn't matter whether the second key is a Put or if it
|
||||
// is an unexpected Merge or Delete. We will compact it out
|
||||
// either way.
|
||||
// either way. We will maintain counts of how many mismatches
|
||||
// happened
|
||||
if (next_ikey.type != kTypeValue) {
|
||||
++iter_stats_.num_single_del_mismatch;
|
||||
}
|
||||
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
// Already called input_->Next() once. Call it a second time to
|
||||
@@ -318,6 +333,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// Key doesn't exist outside of this range.
|
||||
// Can compact out this SingleDelete.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
++iter_stats_.num_single_del_fallthru;
|
||||
} else {
|
||||
// Output SingleDelete
|
||||
valid_ = true;
|
||||
@@ -368,6 +384,7 @@ void CompactionIterator::NextFromInput() {
|
||||
return;
|
||||
}
|
||||
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
// We know the merge type entry is not hidden, otherwise we would
|
||||
// have hit (A)
|
||||
// We encapsulate the merge related state machine in a different
|
||||
@@ -395,6 +412,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// batch consumed by the merge operator should not shadow any keys
|
||||
// coming after the merges
|
||||
has_current_user_key_ = false;
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
}
|
||||
} else {
|
||||
valid_ = true;
|
||||
@@ -423,15 +441,15 @@ void CompactionIterator::PrepareOutput() {
|
||||
inline SequenceNumber CompactionIterator::findEarliestVisibleSnapshot(
|
||||
SequenceNumber in, SequenceNumber* prev_snapshot) {
|
||||
assert(snapshots_->size());
|
||||
SequenceNumber prev __attribute__((unused)) = 0;
|
||||
SequenceNumber prev __attribute__((__unused__)) = kMaxSequenceNumber;
|
||||
for (const auto cur : *snapshots_) {
|
||||
assert(prev <= cur);
|
||||
assert(prev == kMaxSequenceNumber || prev <= cur);
|
||||
if (cur >= in) {
|
||||
*prev_snapshot = prev;
|
||||
*prev_snapshot = prev == kMaxSequenceNumber ? 0 : prev;
|
||||
return cur;
|
||||
}
|
||||
prev = cur;
|
||||
assert(prev);
|
||||
assert(prev < kMaxSequenceNumber);
|
||||
}
|
||||
*prev_snapshot = prev;
|
||||
return kMaxSequenceNumber;
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "db/compaction.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
#include "util/log_buffer.h"
|
||||
|
||||
@@ -33,6 +34,10 @@ struct CompactionIteratorStats {
|
||||
uint64_t num_input_corrupt_records = 0;
|
||||
uint64_t total_input_raw_key_bytes = 0;
|
||||
uint64_t total_input_raw_value_bytes = 0;
|
||||
|
||||
// Single-Delete diagnostics for exceptional situations
|
||||
uint64_t num_single_del_fallthru = 0;
|
||||
uint64_t num_single_del_mismatch = 0;
|
||||
};
|
||||
|
||||
class CompactionIterator {
|
||||
@@ -42,10 +47,12 @@ class CompactionIterator {
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot, Env* env,
|
||||
bool expect_valid_internal_key,
|
||||
Compaction* compaction = nullptr,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
LogBuffer* log_buffer = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
void ResetRecordCounts();
|
||||
|
||||
// Seek to the beginning of the compaction iterator output.
|
||||
@@ -92,7 +99,7 @@ class CompactionIterator {
|
||||
const SequenceNumber earliest_write_conflict_snapshot_;
|
||||
Env* env_;
|
||||
bool expect_valid_internal_key_;
|
||||
Compaction* compaction_;
|
||||
const Compaction* compaction_;
|
||||
const CompactionFilter* compaction_filter_;
|
||||
LogBuffer* log_buffer_;
|
||||
bool bottommost_level_;
|
||||
@@ -136,6 +143,9 @@ class CompactionIterator {
|
||||
bool clear_and_output_next_key_ = false;
|
||||
|
||||
MergeOutputIterator merge_out_iter_;
|
||||
// PinnedIteratorsManager used to pin input_ Iterator blocks while reading
|
||||
// merge operands and then releasing them after consuming them.
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
std::string compaction_filter_value_;
|
||||
// "level_ptrs" holds indices that remember which file of an associated
|
||||
// level we were last checking during the last call to compaction->
|
||||
|
||||
+192
-29
@@ -16,12 +16,13 @@
|
||||
#include <inttypes.h>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "db/db_iter.h"
|
||||
@@ -63,7 +64,7 @@ namespace rocksdb {
|
||||
|
||||
// Maintains state for each sub-compaction
|
||||
struct CompactionJob::SubcompactionState {
|
||||
Compaction* compaction;
|
||||
const Compaction* compaction;
|
||||
std::unique_ptr<CompactionIterator> c_iter;
|
||||
|
||||
// The boundaries of the key-range this compaction is interested in. No two
|
||||
@@ -98,12 +99,22 @@ struct CompactionJob::SubcompactionState {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t current_output_file_size;
|
||||
|
||||
// State during the subcompaction
|
||||
uint64_t total_bytes;
|
||||
uint64_t num_input_records;
|
||||
uint64_t num_output_records;
|
||||
CompactionJobStats compaction_job_stats;
|
||||
uint64_t approx_size;
|
||||
// An index that used to speed up ShouldStopBefore().
|
||||
size_t grandparent_index = 0;
|
||||
// The number of bytes overlapping between the current output and
|
||||
// grandparent files used in ShouldStopBefore().
|
||||
uint64_t overlapped_bytes = 0;
|
||||
// A flag determine whether the key has been seen in ShouldStopBefore()
|
||||
bool seen_key = false;
|
||||
std::string compression_dict;
|
||||
|
||||
SubcompactionState(Compaction* c, Slice* _start, Slice* _end,
|
||||
uint64_t size = 0)
|
||||
@@ -112,10 +123,15 @@ struct CompactionJob::SubcompactionState {
|
||||
end(_end),
|
||||
outfile(nullptr),
|
||||
builder(nullptr),
|
||||
current_output_file_size(0),
|
||||
total_bytes(0),
|
||||
num_input_records(0),
|
||||
num_output_records(0),
|
||||
approx_size(size) {
|
||||
approx_size(size),
|
||||
grandparent_index(0),
|
||||
overlapped_bytes(0),
|
||||
seen_key(false),
|
||||
compression_dict() {
|
||||
assert(compaction != nullptr);
|
||||
}
|
||||
|
||||
@@ -129,11 +145,16 @@ struct CompactionJob::SubcompactionState {
|
||||
outputs = std::move(o.outputs);
|
||||
outfile = std::move(o.outfile);
|
||||
builder = std::move(o.builder);
|
||||
current_output_file_size = std::move(o.current_output_file_size);
|
||||
total_bytes = std::move(o.total_bytes);
|
||||
num_input_records = std::move(o.num_input_records);
|
||||
num_output_records = std::move(o.num_output_records);
|
||||
compaction_job_stats = std::move(o.compaction_job_stats);
|
||||
approx_size = std::move(o.approx_size);
|
||||
grandparent_index = std::move(o.grandparent_index);
|
||||
overlapped_bytes = std::move(o.overlapped_bytes);
|
||||
seen_key = std::move(o.seen_key);
|
||||
compression_dict = std::move(o.compression_dict);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -141,6 +162,39 @@ struct CompactionJob::SubcompactionState {
|
||||
SubcompactionState(const SubcompactionState&) = delete;
|
||||
|
||||
SubcompactionState& operator=(const SubcompactionState&) = delete;
|
||||
|
||||
// Returns true iff we should stop building the current output
|
||||
// before processing "internal_key".
|
||||
bool ShouldStopBefore(const Slice& internal_key, uint64_t curr_file_size) {
|
||||
const InternalKeyComparator* icmp =
|
||||
&compaction->column_family_data()->internal_comparator();
|
||||
const std::vector<FileMetaData*>& grandparents = compaction->grandparents();
|
||||
|
||||
// Scan to find earliest grandparent file that contains key.
|
||||
while (grandparent_index < grandparents.size() &&
|
||||
icmp->Compare(internal_key,
|
||||
grandparents[grandparent_index]->largest.Encode()) >
|
||||
0) {
|
||||
if (seen_key) {
|
||||
overlapped_bytes += grandparents[grandparent_index]->fd.GetFileSize();
|
||||
}
|
||||
assert(grandparent_index + 1 >= grandparents.size() ||
|
||||
icmp->Compare(
|
||||
grandparents[grandparent_index]->largest.Encode(),
|
||||
grandparents[grandparent_index + 1]->smallest.Encode()) <= 0);
|
||||
grandparent_index++;
|
||||
}
|
||||
seen_key = true;
|
||||
|
||||
if (overlapped_bytes + curr_file_size >
|
||||
compaction->max_compaction_bytes()) {
|
||||
// Too much overlap for current output; start new output
|
||||
overlapped_bytes = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Maintains state for the entire compaction
|
||||
@@ -422,9 +476,9 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
|
||||
// Group the ranges into subcompactions
|
||||
const double min_file_fill_percent = 4.0 / 5;
|
||||
uint64_t max_output_files = static_cast<uint64_t>(std::ceil(
|
||||
sum / min_file_fill_percent /
|
||||
cfd->GetCurrentMutableCFOptions()->MaxFileSizeForLevel(out_lvl)));
|
||||
uint64_t max_output_files = static_cast<uint64_t>(
|
||||
std::ceil(sum / min_file_fill_percent /
|
||||
c->mutable_cf_options()->MaxFileSizeForLevel(out_lvl)));
|
||||
uint64_t subcompactions =
|
||||
std::min({static_cast<uint64_t>(ranges.size()),
|
||||
static_cast<uint64_t>(db_options_.max_subcompactions),
|
||||
@@ -575,6 +629,13 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
<< "num_output_records" << compact_->num_output_records
|
||||
<< "num_subcompactions" << compact_->sub_compact_states.size();
|
||||
|
||||
if (compaction_job_stats_ != nullptr) {
|
||||
stream << "num_single_delete_mismatches"
|
||||
<< compaction_job_stats_->num_single_del_mismatch;
|
||||
stream << "num_single_delete_fallthrough"
|
||||
<< compaction_job_stats_->num_single_del_fallthru;
|
||||
}
|
||||
|
||||
if (measure_io_stats_ && compaction_job_stats_ != nullptr) {
|
||||
stream << "file_write_nanos" << compaction_job_stats_->file_write_nanos;
|
||||
stream << "file_range_sync_nanos"
|
||||
@@ -620,6 +681,31 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
}
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
const MutableCFOptions* mutable_cf_options =
|
||||
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.
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto compaction_filter = cfd->ioptions()->compaction_filter;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_from_factory = nullptr;
|
||||
if (compaction_filter == nullptr) {
|
||||
@@ -630,7 +716,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
MergeHelper merge(
|
||||
env_, cfd->user_comparator(), cfd->ioptions()->merge_operator,
|
||||
compaction_filter, db_options_.info_log.get(),
|
||||
cfd->ioptions()->min_partial_merge_operands,
|
||||
mutable_cf_options->min_partial_merge_operands,
|
||||
false /* internal key corruption is expected */,
|
||||
existing_snapshots_.empty() ? 0 : existing_snapshots_.back(),
|
||||
compact_->compaction->level(), db_options_.statistics.get());
|
||||
@@ -655,6 +741,13 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
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
|
||||
// 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);
|
||||
|
||||
// TODO(noetzli): check whether we could check !shutting_down_->... only
|
||||
// only occasionally (see diff D42687)
|
||||
while (status.ok() && !shutting_down_->load(std::memory_order_acquire) &&
|
||||
@@ -669,7 +762,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (end != nullptr &&
|
||||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) >= 0) {
|
||||
break;
|
||||
} else if (sub_compact->compaction->ShouldStopBefore(key) &&
|
||||
} else if (sub_compact->compaction->output_level() != 0 &&
|
||||
sub_compact->ShouldStopBefore(
|
||||
key, sub_compact->current_output_file_size) &&
|
||||
sub_compact->builder != nullptr) {
|
||||
status = FinishCompactionOutputFile(input->status(), sub_compact);
|
||||
if (!status.ok()) {
|
||||
@@ -694,20 +789,74 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
assert(sub_compact->builder != nullptr);
|
||||
assert(sub_compact->current_output() != nullptr);
|
||||
sub_compact->builder->Add(key, value);
|
||||
sub_compact->current_output_file_size = sub_compact->builder->FileSize();
|
||||
sub_compact->current_output()->meta.UpdateBoundaries(
|
||||
key, c_iter->ikey().sequence);
|
||||
sub_compact->num_output_records++;
|
||||
|
||||
if (sub_compact->outputs.size() == 1) { // first output file
|
||||
// Check if this key/value overlaps any sample intervals; if so, appends
|
||||
// overlapping portions to the dictionary.
|
||||
for (const auto& data_elmt : {key, value}) {
|
||||
size_t data_end_offset = data_begin_offset + data_elmt.size();
|
||||
while (sample_begin_offset_iter != sample_begin_offsets.cend() &&
|
||||
*sample_begin_offset_iter < data_end_offset) {
|
||||
size_t sample_end_offset =
|
||||
*sample_begin_offset_iter + (1 << kSampleLenShift);
|
||||
// Invariant: Because we advance sample iterator while processing the
|
||||
// data_elmt containing the sample's last byte, the current sample
|
||||
// cannot end before the current data_elmt.
|
||||
assert(data_begin_offset < sample_end_offset);
|
||||
|
||||
size_t data_elmt_copy_offset, data_elmt_copy_len;
|
||||
if (*sample_begin_offset_iter <= data_begin_offset) {
|
||||
// The sample starts before data_elmt starts, so take bytes starting
|
||||
// at the beginning of data_elmt.
|
||||
data_elmt_copy_offset = 0;
|
||||
} else {
|
||||
// data_elmt starts before the sample starts, so take bytes starting
|
||||
// at the below offset into data_elmt.
|
||||
data_elmt_copy_offset =
|
||||
*sample_begin_offset_iter - data_begin_offset;
|
||||
}
|
||||
if (sample_end_offset <= data_end_offset) {
|
||||
// The sample ends before data_elmt ends, so take as many bytes as
|
||||
// needed.
|
||||
data_elmt_copy_len =
|
||||
sample_end_offset - (data_begin_offset + data_elmt_copy_offset);
|
||||
} else {
|
||||
// data_elmt ends before the sample ends, so take all remaining
|
||||
// bytes in data_elmt.
|
||||
data_elmt_copy_len =
|
||||
data_end_offset - (data_begin_offset + data_elmt_copy_offset);
|
||||
}
|
||||
compression_dict.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.
|
||||
break;
|
||||
}
|
||||
// Next sample may require bytes from same data_elmt.
|
||||
sample_begin_offset_iter++;
|
||||
}
|
||||
data_begin_offset = data_end_offset;
|
||||
}
|
||||
}
|
||||
|
||||
// Close output file if it is big enough
|
||||
// TODO(aekmekji): determine if file should be closed earlier than this
|
||||
// during subcompactions (i.e. if output size, estimated by input size, is
|
||||
// going to be 1.2MB and max_output_file_size = 1MB, prefer to have 0.6MB
|
||||
// and 0.6MB instead of 1MB and 0.2MB)
|
||||
if (sub_compact->builder->FileSize() >=
|
||||
if (sub_compact->current_output_file_size >=
|
||||
sub_compact->compaction->max_output_file_size()) {
|
||||
status = FinishCompactionOutputFile(input->status(), sub_compact);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
c_iter->Next();
|
||||
}
|
||||
|
||||
@@ -716,6 +865,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
c_iter_stats.num_input_deletion_records;
|
||||
sub_compact->compaction_job_stats.num_corrupt_keys =
|
||||
c_iter_stats.num_input_corrupt_records;
|
||||
sub_compact->compaction_job_stats.num_single_del_fallthru =
|
||||
c_iter_stats.num_single_del_fallthru;
|
||||
sub_compact->compaction_job_stats.num_single_del_mismatch =
|
||||
c_iter_stats.num_single_del_mismatch;
|
||||
sub_compact->compaction_job_stats.total_input_raw_key_bytes +=
|
||||
c_iter_stats.total_input_raw_key_bytes;
|
||||
sub_compact->compaction_job_stats.total_input_raw_value_bytes +=
|
||||
@@ -820,9 +973,10 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
sub_compact->outfile.reset();
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
TableProperties tp;
|
||||
if (s.ok() && current_entries > 0) {
|
||||
// Verify that the table is usable
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
InternalIterator* iter = cfd->table_cache()->NewIterator(
|
||||
ReadOptions(), env_options_, cfd->internal_comparator(), meta->fd,
|
||||
nullptr, cfd->internal_stats()->GetFileReadHist(
|
||||
@@ -836,34 +990,30 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
|
||||
delete iter;
|
||||
|
||||
// Output to event logger and fire events.
|
||||
if (s.ok()) {
|
||||
auto tp = sub_compact->builder->GetTableProperties();
|
||||
tp = sub_compact->builder->GetTableProperties();
|
||||
sub_compact->current_output()->table_properties =
|
||||
std::make_shared<TableProperties>(tp);
|
||||
TableFileCreationInfo info(std::move(tp));
|
||||
info.db_name = dbname_;
|
||||
info.cf_name = cfd->GetName();
|
||||
info.file_path =
|
||||
TableFileName(cfd->ioptions()->db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
info.file_size = meta->fd.GetFileSize();
|
||||
info.job_id = job_id_;
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Generated table #%" PRIu64 ": %" PRIu64
|
||||
" keys, %" PRIu64 " bytes%s",
|
||||
cfd->GetName().c_str(), job_id_, output_number, current_entries,
|
||||
current_bytes,
|
||||
meta->marked_for_compaction ? " (need compaction)" : "");
|
||||
EventHelpers::LogAndNotifyTableFileCreation(
|
||||
event_logger_, cfd->ioptions()->listeners, meta->fd, info);
|
||||
}
|
||||
}
|
||||
std::string fname = TableFileName(db_options_.db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname,
|
||||
job_id_, meta->fd, tp, TableFileCreationReason::kCompaction, s);
|
||||
|
||||
// Report new file to SstFileManagerImpl
|
||||
auto sfm =
|
||||
static_cast<SstFileManagerImpl*>(db_options_.sst_file_manager.get());
|
||||
if (sfm && meta->fd.GetPathId() == 0) {
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
auto fn = TableFileName(cfd->ioptions()->db_paths, meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
sfm->OnAddFile(fn);
|
||||
@@ -879,6 +1029,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
}
|
||||
|
||||
sub_compact->builder.reset();
|
||||
sub_compact->current_output_file_size = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -939,10 +1090,17 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
assert(sub_compact->builder == nullptr);
|
||||
// no need to lock because VersionSet::next_file_number_ is atomic
|
||||
uint64_t file_number = versions_->NewFileNumber();
|
||||
// Make the output file
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
std::string fname = TableFileName(db_options_.db_paths, file_number,
|
||||
sub_compact->compaction->output_path_id());
|
||||
// Fire events.
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
#ifndef ROCKSDB_LITE
|
||||
EventHelpers::NotifyTableFileCreationStarted(
|
||||
cfd->ioptions()->listeners, dbname_, cfd->GetName(), fname, job_id_,
|
||||
TableFileCreationReason::kCompaction);
|
||||
#endif // !ROCKSDB_LITE
|
||||
// Make the output file
|
||||
unique_ptr<WritableFile> writable_file;
|
||||
Status s = NewWritableFile(env_, fname, &writable_file, env_options_);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::ERROR_LEVEL, db_options_.info_log,
|
||||
@@ -951,8 +1109,13 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_, file_number, s.ToString().c_str());
|
||||
LogFlush(db_options_.info_log);
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger_, cfd->ioptions()->listeners, dbname_, cfd->GetName(),
|
||||
fname, job_id_, FileDescriptor(), TableProperties(),
|
||||
TableFileCreationReason::kCompaction, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
SubcompactionState::Output out;
|
||||
out.meta.fd =
|
||||
FileDescriptor(file_number, sub_compact->compaction->output_path_id(), 0);
|
||||
@@ -965,7 +1128,6 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
sub_compact->outfile.reset(
|
||||
new WritableFileWriter(std::move(writable_file), env_options_));
|
||||
|
||||
ColumnFamilyData* cfd = sub_compact->compaction->column_family_data();
|
||||
// If the Column family flag is to only optimize filters for hits,
|
||||
// we can skip creating filters if this is the bottommost_level where
|
||||
// data is going to be found
|
||||
@@ -973,9 +1135,10 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
cfd->ioptions()->optimize_filters_for_hits && bottommost_level_;
|
||||
sub_compact->builder.reset(NewTableBuilder(
|
||||
*cfd->ioptions(), cfd->internal_comparator(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(),
|
||||
cfd->int_tbl_prop_collector_factories(), cfd->GetID(), cfd->GetName(),
|
||||
sub_compact->outfile.get(), sub_compact->compaction->output_compression(),
|
||||
cfd->ioptions()->compression_opts, skip_filters));
|
||||
cfd->ioptions()->compression_opts, &sub_compact->compression_dict,
|
||||
skip_filters));
|
||||
LogFlush(db_options_.info_log);
|
||||
return s;
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ class CompactionJob {
|
||||
const std::string& dbname_;
|
||||
const DBOptions& db_options_;
|
||||
const EnvOptions& env_options_;
|
||||
|
||||
Env* env_;
|
||||
VersionSet* versions_;
|
||||
std::atomic<bool>* shutting_down_;
|
||||
|
||||
@@ -624,7 +624,10 @@ CompressionType GetAnyCompression() {
|
||||
return kBZip2Compression;
|
||||
} else if (LZ4_Supported()) {
|
||||
return kLZ4Compression;
|
||||
} else if (XPRESS_Supported()) {
|
||||
return kXpressCompression;
|
||||
}
|
||||
|
||||
return kNoCompression;
|
||||
}
|
||||
|
||||
@@ -660,7 +663,7 @@ TEST_P(CompactionJobStatsTest, CompactionJobStatsTest) {
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.bytes_per_sync = 512 * 1024;
|
||||
|
||||
options.compaction_measure_io_stats = true;
|
||||
options.report_bg_io_stats = true;
|
||||
for (int test = 0; test < 2; ++test) {
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
@@ -954,7 +957,7 @@ TEST_P(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
uint64_t key_base = 100000000l;
|
||||
// Note: key_base must be multiple of num_keys_per_L0_file
|
||||
int num_keys_per_table = 100;
|
||||
const uint32_t kTestScale = 8;
|
||||
const uint32_t kTestScale = 6;
|
||||
const int kKeySize = 10;
|
||||
const int kValueSize = 900;
|
||||
double compression_ratio = 1.0;
|
||||
@@ -1005,8 +1008,9 @@ TEST_P(CompactionJobStatsTest, UniversalCompactionTest) {
|
||||
num_input_units,
|
||||
num_keys_per_table * num_input_units,
|
||||
1.0, 0, false));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 4U);
|
||||
ASSERT_EQ(stats_checker->NumberOfUnverifiedStats(), 3U);
|
||||
|
||||
for (uint64_t start_key = key_base;
|
||||
start_key <= key_base * kTestScale;
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction_job.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -70,9 +70,9 @@ class CompactionJobTest : public testing::Test {
|
||||
dbname_(test::TmpDir() + "/compaction_job_test"),
|
||||
mutable_cf_options_(Options(), ImmutableCFOptions(Options())),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_(db_options_.db_write_buffer_size),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
versions_(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()) {
|
||||
@@ -241,8 +241,8 @@ class CompactionJobTest : public testing::Test {
|
||||
|
||||
Compaction compaction(cfd->current()->storage_info(),
|
||||
*cfd->GetLatestMutableCFOptions(),
|
||||
compaction_input_files, 1, 1024 * 1024, 10, 0,
|
||||
kNoCompression, {}, true);
|
||||
compaction_input_files, 1, 1024 * 1024,
|
||||
10 * 1024 * 1024, 0, kNoCompression, {}, true);
|
||||
compaction.SetInputVersion(cfd->current());
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
@@ -285,7 +285,7 @@ class CompactionJobTest : public testing::Test {
|
||||
WriteController write_controller_;
|
||||
DBOptions db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
WriteBuffer write_buffer_;
|
||||
WriteBufferManager write_buffer_manager_;
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic<bool> shutting_down_;
|
||||
|
||||
+124
-115
@@ -70,7 +70,8 @@ struct UserKeyComparator {
|
||||
};
|
||||
|
||||
typedef std::priority_queue<InputFileInfo, std::vector<InputFileInfo>,
|
||||
UserKeyComparator> SmallestKeyHeap;
|
||||
UserKeyComparator>
|
||||
SmallestKeyHeap;
|
||||
|
||||
// This function creates the heap that is used to find if the files are
|
||||
// overlapping during universal compaction when the allow_trivial_move
|
||||
@@ -109,13 +110,22 @@ SmallestKeyHeap create_level_heap(Compaction* c, const Comparator* ucmp) {
|
||||
// matter what the values of the other two parameters are.
|
||||
// Otherwise, the compression type is determined based on options and level.
|
||||
CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int level, int base_level,
|
||||
const bool enable_compression) {
|
||||
if (!enable_compression) {
|
||||
// disable compression
|
||||
return kNoCompression;
|
||||
}
|
||||
// If the use has specified a different compression level for each level,
|
||||
|
||||
// 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)) {
|
||||
return ioptions.bottommost_compression;
|
||||
}
|
||||
// If the user has specified a different compression level for each level,
|
||||
// then pick the compression for that level.
|
||||
if (!ioptions.compression_per_level.empty()) {
|
||||
assert(level == 0 || level >= base_level);
|
||||
@@ -129,7 +139,7 @@ CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
// specified compression levels, use the last value.
|
||||
return ioptions.compression_per_level[std::max(0, std::min(idx, n))];
|
||||
} else {
|
||||
return ioptions.compression;
|
||||
return mutable_cf_options.compression;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,10 +200,9 @@ void CompactionPicker::GetRange(const CompactionInputFiles& inputs1,
|
||||
InternalKey smallest1, smallest2, largest1, largest2;
|
||||
GetRange(inputs1, &smallest1, &largest1);
|
||||
GetRange(inputs2, &smallest2, &largest2);
|
||||
*smallest = icmp_->Compare(smallest1, smallest2) < 0 ?
|
||||
smallest1 : smallest2;
|
||||
*largest = icmp_->Compare(largest1, largest2) < 0 ?
|
||||
largest2 : largest1;
|
||||
*smallest =
|
||||
icmp_->Compare(smallest1, smallest2) < 0 ? smallest1 : smallest2;
|
||||
*largest = icmp_->Compare(largest1, largest2) < 0 ? largest2 : largest1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,10 +266,6 @@ Compaction* CompactionPicker::FormCompaction(
|
||||
const std::vector<CompactionInputFiles>& input_files, int output_level,
|
||||
VersionStorageInfo* vstorage, const MutableCFOptions& mutable_cf_options,
|
||||
uint32_t output_path_id) {
|
||||
uint64_t max_grandparent_overlap_bytes =
|
||||
output_level + 1 < vstorage->num_levels() ?
|
||||
mutable_cf_options.MaxGrandParentOverlapBytes(output_level + 1) :
|
||||
std::numeric_limits<uint64_t>::max();
|
||||
assert(input_files.size());
|
||||
|
||||
// TODO(rven ): we might be able to run concurrent level 0 compaction
|
||||
@@ -269,10 +274,11 @@ Compaction* CompactionPicker::FormCompaction(
|
||||
if ((input_files[0].level == 0) && !level0_compactions_in_progress_.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto c = new Compaction(
|
||||
vstorage, mutable_cf_options, input_files, output_level,
|
||||
compact_options.output_file_size_limit, max_grandparent_overlap_bytes,
|
||||
output_path_id, compact_options.compression, /* grandparents */ {}, true);
|
||||
auto c =
|
||||
new Compaction(vstorage, 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
|
||||
@@ -284,8 +290,7 @@ Compaction* CompactionPicker::FormCompaction(
|
||||
|
||||
Status CompactionPicker::GetCompactionInputsFromFileNumbers(
|
||||
std::vector<CompactionInputFiles>* input_files,
|
||||
std::unordered_set<uint64_t>* input_set,
|
||||
const VersionStorageInfo* vstorage,
|
||||
std::unordered_set<uint64_t>* input_set, const VersionStorageInfo* vstorage,
|
||||
const CompactionOptions& compact_options) const {
|
||||
if (input_set->size() == 0U) {
|
||||
return Status::InvalidArgument(
|
||||
@@ -323,8 +328,8 @@ Status CompactionPicker::GetCompactionInputsFromFileNumbers(
|
||||
return Status::InvalidArgument(message);
|
||||
}
|
||||
|
||||
for (int level = first_non_empty_level;
|
||||
level <= last_non_empty_level; ++level) {
|
||||
for (int level = first_non_empty_level; level <= last_non_empty_level;
|
||||
++level) {
|
||||
matched_input_files[level].level = level;
|
||||
input_files->emplace_back(std::move(matched_input_files[level]));
|
||||
}
|
||||
@@ -332,8 +337,6 @@ Status CompactionPicker::GetCompactionInputsFromFileNumbers(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Returns true if any one of the parent files are being compacted
|
||||
bool CompactionPicker::RangeInCompaction(VersionStorageInfo* vstorage,
|
||||
const InternalKey* smallest,
|
||||
@@ -384,6 +387,9 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
vstorage->GetOverlappingInputs(output_level, &smallest, &largest,
|
||||
&output_level_inputs->files, *parent_index,
|
||||
parent_index);
|
||||
if (!output_level_inputs->empty()) {
|
||||
ExpandWhileOverlapping(cf_name, vstorage, output_level_inputs);
|
||||
}
|
||||
|
||||
if (FilesInCompaction(output_level_inputs->files)) {
|
||||
return false;
|
||||
@@ -407,8 +413,7 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
const uint64_t inputs1_size =
|
||||
TotalCompensatedFileSize(output_level_inputs->files);
|
||||
const uint64_t expanded0_size = TotalCompensatedFileSize(expanded0.files);
|
||||
uint64_t limit =
|
||||
mutable_cf_options.ExpandedCompactionByteSizeLimit(input_level);
|
||||
uint64_t limit = mutable_cf_options.max_compaction_bytes;
|
||||
if (expanded0.size() > inputs->size() &&
|
||||
inputs1_size + expanded0_size < limit &&
|
||||
!FilesInCompaction(expanded0.files) &&
|
||||
@@ -504,8 +509,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, mutable_cf_options, std::move(inputs), output_level,
|
||||
mutable_cf_options.MaxFileSizeForLevel(output_level),
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, output_path_id,
|
||||
GetCompressionType(ioptions_, output_level, 1),
|
||||
/* max_compaction_bytes */ LLONG_MAX, output_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
/* grandparents */ {}, /* is manual */ true);
|
||||
if (start_level == 0) {
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
@@ -541,8 +547,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// and we must not pick one file and drop another older file if the
|
||||
// two files overlap.
|
||||
if (input_level > 0) {
|
||||
const uint64_t limit = mutable_cf_options.MaxFileSizeForLevel(input_level) *
|
||||
mutable_cf_options.source_compaction_factor;
|
||||
const uint64_t limit = mutable_cf_options.max_compaction_bytes;
|
||||
uint64_t total = 0;
|
||||
for (size_t i = 0; i + 1 < inputs.size(); ++i) {
|
||||
uint64_t s = inputs[i]->compensated_file_size;
|
||||
@@ -604,9 +609,9 @@ Compaction* CompactionPicker::CompactRange(
|
||||
Compaction* compaction = new Compaction(
|
||||
vstorage, mutable_cf_options, std::move(compaction_inputs), output_level,
|
||||
mutable_cf_options.MaxFileSizeForLevel(output_level),
|
||||
mutable_cf_options.MaxGrandParentOverlapBytes(input_level),
|
||||
output_path_id,
|
||||
GetCompressionType(ioptions_, output_level, vstorage->base_level()),
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id,
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
std::move(grandparents), /* is manual compaction */ true);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
@@ -618,11 +623,7 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
// being compacted). Since we just changed compaction score, we recalculate it
|
||||
// here
|
||||
{ // this piece of code recomputes compaction score
|
||||
CompactionOptionsFIFO dummy_compaction_options_fifo;
|
||||
vstorage->ComputeCompactionScore(mutable_cf_options,
|
||||
dummy_compaction_options_fifo);
|
||||
}
|
||||
vstorage->ComputeCompactionScore(mutable_cf_options);
|
||||
|
||||
return compaction;
|
||||
}
|
||||
@@ -630,9 +631,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
// Test whether two files have overlapping key-ranges.
|
||||
bool HaveOverlappingKeyRanges(
|
||||
const Comparator* c,
|
||||
const SstFileMetaData& a, const SstFileMetaData& b) {
|
||||
bool HaveOverlappingKeyRanges(const Comparator* c, const SstFileMetaData& a,
|
||||
const SstFileMetaData& b) {
|
||||
if (c->Compare(a.smallestkey, b.smallestkey) >= 0) {
|
||||
if (c->Compare(a.smallestkey, b.largestkey) <= 0) {
|
||||
// b.smallestkey <= a.smallestkey <= b.largestkey
|
||||
@@ -656,9 +656,8 @@ bool HaveOverlappingKeyRanges(
|
||||
} // namespace
|
||||
|
||||
Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const {
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level) const {
|
||||
auto& levels = cf_meta.levels;
|
||||
auto comparator = icmp_->user_comparator();
|
||||
|
||||
@@ -711,18 +710,17 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
// has overlapping key-range with other non-compaction input
|
||||
// files in the same level.
|
||||
while (first_included > 0) {
|
||||
if (comparator->Compare(
|
||||
current_files[first_included - 1].largestkey,
|
||||
current_files[first_included].smallestkey) < 0) {
|
||||
if (comparator->Compare(current_files[first_included - 1].largestkey,
|
||||
current_files[first_included].smallestkey) <
|
||||
0) {
|
||||
break;
|
||||
}
|
||||
first_included--;
|
||||
}
|
||||
|
||||
while (last_included < static_cast<int>(current_files.size()) - 1) {
|
||||
if (comparator->Compare(
|
||||
current_files[last_included + 1].smallestkey,
|
||||
current_files[last_included].largestkey) > 0) {
|
||||
if (comparator->Compare(current_files[last_included + 1].smallestkey,
|
||||
current_files[last_included].largestkey) > 0) {
|
||||
break;
|
||||
}
|
||||
last_included++;
|
||||
@@ -732,33 +730,31 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
// include all files between the first and the last compaction input files.
|
||||
for (int f = first_included; f <= last_included; ++f) {
|
||||
if (current_files[f].being_compacted) {
|
||||
return Status::Aborted(
|
||||
"Necessary compaction input file " + current_files[f].name +
|
||||
" is currently being compacted.");
|
||||
return Status::Aborted("Necessary compaction input file " +
|
||||
current_files[f].name +
|
||||
" is currently being compacted.");
|
||||
}
|
||||
input_files->insert(
|
||||
TableFileNameToNumber(current_files[f].name));
|
||||
input_files->insert(TableFileNameToNumber(current_files[f].name));
|
||||
}
|
||||
|
||||
// update smallest and largest key
|
||||
if (l == 0) {
|
||||
for (int f = first_included; f <= last_included; ++f) {
|
||||
if (comparator->Compare(
|
||||
smallestkey, current_files[f].smallestkey) > 0) {
|
||||
if (comparator->Compare(smallestkey, current_files[f].smallestkey) >
|
||||
0) {
|
||||
smallestkey = current_files[f].smallestkey;
|
||||
}
|
||||
if (comparator->Compare(
|
||||
largestkey, current_files[f].largestkey) < 0) {
|
||||
if (comparator->Compare(largestkey, current_files[f].largestkey) < 0) {
|
||||
largestkey = current_files[f].largestkey;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (comparator->Compare(
|
||||
smallestkey, current_files[first_included].smallestkey) > 0) {
|
||||
if (comparator->Compare(smallestkey,
|
||||
current_files[first_included].smallestkey) > 0) {
|
||||
smallestkey = current_files[first_included].smallestkey;
|
||||
}
|
||||
if (comparator->Compare(
|
||||
largestkey, current_files[last_included].largestkey) < 0) {
|
||||
if (comparator->Compare(largestkey,
|
||||
current_files[last_included].largestkey) < 0) {
|
||||
largestkey = current_files[last_included].largestkey;
|
||||
}
|
||||
}
|
||||
@@ -775,16 +771,15 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
// time and not by key
|
||||
for (int m = std::max(l, 1); m <= output_level; ++m) {
|
||||
for (auto& next_lv_file : levels[m].files) {
|
||||
if (HaveOverlappingKeyRanges(
|
||||
comparator, aggregated_file_meta, next_lv_file)) {
|
||||
if (HaveOverlappingKeyRanges(comparator, aggregated_file_meta,
|
||||
next_lv_file)) {
|
||||
if (next_lv_file.being_compacted) {
|
||||
return Status::Aborted(
|
||||
"File " + next_lv_file.name +
|
||||
" that has overlapping key range with one of the compaction "
|
||||
" input file is currently being compacted.");
|
||||
}
|
||||
input_files->insert(
|
||||
TableFileNameToNumber(next_lv_file.name));
|
||||
input_files->insert(TableFileNameToNumber(next_lv_file.name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -794,28 +789,25 @@ Status CompactionPicker::SanitizeCompactionInputFilesForAllLevels(
|
||||
|
||||
Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const {
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level) const {
|
||||
assert(static_cast<int>(cf_meta.levels.size()) - 1 ==
|
||||
cf_meta.levels[cf_meta.levels.size() - 1].level);
|
||||
if (output_level >= static_cast<int>(cf_meta.levels.size())) {
|
||||
return Status::InvalidArgument(
|
||||
"Output level for column family " + cf_meta.name +
|
||||
" must between [0, " +
|
||||
ToString(cf_meta.levels[cf_meta.levels.size() - 1].level) +
|
||||
"].");
|
||||
ToString(cf_meta.levels[cf_meta.levels.size() - 1].level) + "].");
|
||||
}
|
||||
|
||||
if (output_level > MaxOutputLevel()) {
|
||||
return Status::InvalidArgument(
|
||||
"Exceed the maximum output level defined by "
|
||||
"the current compaction algorithm --- " +
|
||||
ToString(MaxOutputLevel()));
|
||||
ToString(MaxOutputLevel()));
|
||||
}
|
||||
|
||||
if (output_level < 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Output level cannot be negative.");
|
||||
return Status::InvalidArgument("Output level cannot be negative.");
|
||||
}
|
||||
|
||||
if (input_files->size() == 0) {
|
||||
@@ -823,8 +815,8 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
"A compaction must contain at least one file.");
|
||||
}
|
||||
|
||||
Status s = SanitizeCompactionInputFilesForAllLevels(
|
||||
input_files, cf_meta, output_level);
|
||||
Status s = SanitizeCompactionInputFilesForAllLevels(input_files, cf_meta,
|
||||
output_level);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -838,10 +830,9 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
for (auto file_meta : level_meta.files) {
|
||||
if (file_num == TableFileNameToNumber(file_meta.name)) {
|
||||
if (file_meta.being_compacted) {
|
||||
return Status::Aborted(
|
||||
"Specified compaction input file " +
|
||||
MakeTableFileName("", file_num) +
|
||||
" is already being compacted.");
|
||||
return Status::Aborted("Specified compaction input file " +
|
||||
MakeTableFileName("", file_num) +
|
||||
" is already being compacted.");
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
@@ -853,8 +844,7 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
}
|
||||
if (!found) {
|
||||
return Status::InvalidArgument(
|
||||
"Specified compaction input file " +
|
||||
MakeTableFileName("", file_num) +
|
||||
"Specified compaction input file " + MakeTableFileName("", file_num) +
|
||||
" does not exist in column family " + cf_meta.name + ".");
|
||||
}
|
||||
}
|
||||
@@ -863,8 +853,8 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
bool LevelCompactionPicker::NeedsCompaction(const VersionStorageInfo* vstorage)
|
||||
const {
|
||||
bool LevelCompactionPicker::NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const {
|
||||
if (!vstorage->FilesMarkedForCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
@@ -1010,7 +1000,7 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
CompactionInputFiles output_level_inputs;
|
||||
output_level_inputs.level = output_level;
|
||||
if (!SetupOtherInputs(cf_name, mutable_cf_options, vstorage, &inputs,
|
||||
&output_level_inputs, &parent_index, base_index)) {
|
||||
&output_level_inputs, &parent_index, base_index)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1024,9 +1014,10 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
auto c = new Compaction(
|
||||
vstorage, mutable_cf_options, std::move(compaction_inputs), output_level,
|
||||
mutable_cf_options.MaxFileSizeForLevel(output_level),
|
||||
mutable_cf_options.MaxGrandParentOverlapBytes(level),
|
||||
mutable_cf_options.max_compaction_bytes,
|
||||
GetPathId(ioptions_, mutable_cf_options, output_level),
|
||||
GetCompressionType(ioptions_, output_level, vstorage->base_level()),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
std::move(grandparents), is_manual, score,
|
||||
false /* deletion_compaction */, compaction_reason);
|
||||
|
||||
@@ -1040,11 +1031,7 @@ Compaction* LevelCompactionPicker::PickCompaction(
|
||||
// takes running compactions into account (by skipping files that are already
|
||||
// being compacted). Since we just changed compaction score, we recalculate it
|
||||
// here
|
||||
{ // this piece of code recomputes compaction score
|
||||
CompactionOptionsFIFO dummy_compaction_options_fifo;
|
||||
vstorage->ComputeCompactionScore(mutable_cf_options,
|
||||
dummy_compaction_options_fifo);
|
||||
}
|
||||
vstorage->ComputeCompactionScore(mutable_cf_options);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("LevelCompactionPicker::PickCompaction:Return", c);
|
||||
|
||||
@@ -1320,6 +1307,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
sorted_runs.size() <
|
||||
(unsigned int)mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
LogToBuffer(log_buffer, "[%s] Universal: nothing to do\n", cf_name.c_str());
|
||||
TEST_SYNC_POINT_CALLBACK("UniversalCompactionPicker::PickCompaction:Return",
|
||||
nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
VersionStorageInfo::LevelSummaryStorage tmp;
|
||||
@@ -1354,19 +1343,34 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
assert(sorted_runs.size() >=
|
||||
static_cast<size_t>(
|
||||
mutable_cf_options.level0_file_num_compaction_trigger));
|
||||
unsigned int num_files =
|
||||
static_cast<unsigned int>(sorted_runs.size()) -
|
||||
mutable_cf_options.level0_file_num_compaction_trigger;
|
||||
if ((c = PickCompactionUniversalReadAmp(
|
||||
cf_name, mutable_cf_options, vstorage, score, UINT_MAX,
|
||||
num_files, sorted_runs, log_buffer)) != nullptr) {
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: compacting for file num -- %u\n",
|
||||
cf_name.c_str(), num_files);
|
||||
// Get the total number of sorted runs that are not being compacted
|
||||
int num_sr_not_compacted = 0;
|
||||
for (size_t i = 0; i < sorted_runs.size(); i++) {
|
||||
if (sorted_runs[i].being_compacted == false) {
|
||||
num_sr_not_compacted++;
|
||||
}
|
||||
}
|
||||
|
||||
// The number of sorted runs that are not being compacted is greater than
|
||||
// the maximum allowed number of sorted runs
|
||||
if (num_sr_not_compacted >
|
||||
mutable_cf_options.level0_file_num_compaction_trigger) {
|
||||
unsigned int num_files =
|
||||
num_sr_not_compacted -
|
||||
mutable_cf_options.level0_file_num_compaction_trigger + 1;
|
||||
if ((c = PickCompactionUniversalReadAmp(
|
||||
cf_name, mutable_cf_options, vstorage, score, UINT_MAX,
|
||||
num_files, sorted_runs, log_buffer)) != nullptr) {
|
||||
LogToBuffer(log_buffer,
|
||||
"[%s] Universal: compacting for file num -- %u\n",
|
||||
cf_name.c_str(), num_files);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (c == nullptr) {
|
||||
TEST_SYNC_POINT_CALLBACK("UniversalCompactionPicker::PickCompaction:Return",
|
||||
nullptr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1419,6 +1423,8 @@ Compaction* UniversalCompactionPicker::PickCompaction(
|
||||
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("UniversalCompactionPicker::PickCompaction:Return",
|
||||
c);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -1439,8 +1445,9 @@ uint32_t UniversalCompactionPicker::GetPathId(
|
||||
// considered in this algorithm. So the target size can be violated in
|
||||
// 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;
|
||||
uint64_t future_size =
|
||||
file_size * (100 - ioptions.compaction_options_universal.size_ratio) /
|
||||
100;
|
||||
uint32_t p = 0;
|
||||
assert(!ioptions.db_paths.empty());
|
||||
for (; p < ioptions.db_paths.size() - 1; p++) {
|
||||
@@ -1464,17 +1471,17 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
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;
|
||||
ioptions_.compaction_options_universal.min_merge_width;
|
||||
unsigned int max_merge_width =
|
||||
ioptions_.compaction_options_universal.max_merge_width;
|
||||
ioptions_.compaction_options_universal.max_merge_width;
|
||||
|
||||
const SortedRun* sr = nullptr;
|
||||
bool done = false;
|
||||
size_t start_index = 0;
|
||||
unsigned int candidate_count = 0;
|
||||
|
||||
unsigned int max_files_to_compact = std::min(max_merge_width,
|
||||
max_number_of_files_to_compact);
|
||||
unsigned int max_files_to_compact =
|
||||
std::min(max_merge_width, max_number_of_files_to_compact);
|
||||
min_merge_width = std::max(min_merge_width, 2U);
|
||||
|
||||
// Caller checks the size before executing this function. This invariant is
|
||||
@@ -1586,7 +1593,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
uint64_t older_file_size = 0;
|
||||
for (size_t i = sorted_runs.size() - 1; i >= first_index_after; i--) {
|
||||
older_file_size += sorted_runs[i].size;
|
||||
if (older_file_size * 100L >= total_size * (long) ratio_to_compress) {
|
||||
if (older_file_size * 100L >= total_size * (long)ratio_to_compress) {
|
||||
enable_compression = false;
|
||||
break;
|
||||
}
|
||||
@@ -1638,7 +1645,8 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalReadAmp(
|
||||
return new Compaction(
|
||||
vstorage, mutable_cf_options, std::move(inputs), output_level,
|
||||
mutable_cf_options.MaxFileSizeForLevel(output_level), LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, start_level, 1, enable_compression),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, start_level,
|
||||
1, enable_compression),
|
||||
/* grandparents */ {}, /* is manual */ false, score,
|
||||
false /* deletion_compaction */, compaction_reason);
|
||||
}
|
||||
@@ -1654,8 +1662,8 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
VersionStorageInfo* vstorage, double score,
|
||||
const std::vector<SortedRun>& sorted_runs, LogBuffer* log_buffer) {
|
||||
// percentage flexibilty while reducing size amplification
|
||||
uint64_t ratio = ioptions_.compaction_options_universal.
|
||||
max_size_amplification_percent;
|
||||
uint64_t ratio =
|
||||
ioptions_.compaction_options_universal.max_size_amplification_percent;
|
||||
|
||||
unsigned int candidate_count = 0;
|
||||
uint64_t candidate_size = 0;
|
||||
@@ -1666,7 +1674,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
for (size_t loop = 0; loop < sorted_runs.size() - 1; loop++) {
|
||||
sr = &sorted_runs[loop];
|
||||
if (!sr->being_compacted) {
|
||||
start_index = loop; // Consider this as the first candidate.
|
||||
start_index = loop; // Consider this as the first candidate.
|
||||
break;
|
||||
}
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
@@ -1678,7 +1686,7 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
}
|
||||
|
||||
if (sr == nullptr) {
|
||||
return nullptr; // no candidate files
|
||||
return nullptr; // no candidate files
|
||||
}
|
||||
{
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
@@ -1716,14 +1724,14 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
LogToBuffer(
|
||||
log_buffer,
|
||||
"[%s] Universal: size amp not needed. newer-files-total-size %" PRIu64
|
||||
"earliest-file-size %" PRIu64,
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name.c_str(), candidate_size, earliest_file_size);
|
||||
return nullptr;
|
||||
} else {
|
||||
LogToBuffer(
|
||||
log_buffer,
|
||||
"[%s] Universal: size amp needed. newer-files-total-size %" PRIu64
|
||||
"earliest-file-size %" PRIu64,
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name.c_str(), candidate_size, earliest_file_size);
|
||||
}
|
||||
assert(start_index < sorted_runs.size() - 1);
|
||||
@@ -1763,14 +1771,15 @@ Compaction* UniversalCompactionPicker::PickCompactionUniversalSizeAmp(
|
||||
vstorage->num_levels() - 1,
|
||||
mutable_cf_options.MaxFileSizeForLevel(vstorage->num_levels() - 1),
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
|
||||
GetCompressionType(ioptions_, vstorage->num_levels() - 1, 1),
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
vstorage->num_levels() - 1, 1),
|
||||
/* grandparents */ {}, /* is manual */ false, score,
|
||||
false /* deletion_compaction */,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
}
|
||||
|
||||
bool FIFOCompactionPicker::NeedsCompaction(const VersionStorageInfo* vstorage)
|
||||
const {
|
||||
bool FIFOCompactionPicker::NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const {
|
||||
const int kLevel0 = 0;
|
||||
return vstorage->CompactionScore(kLevel0) >= 1;
|
||||
}
|
||||
|
||||
+31
-36
@@ -20,8 +20,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "util/mutable_cf_options.h"
|
||||
|
||||
#include "util/cf_options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -63,23 +62,20 @@ class CompactionPicker {
|
||||
InternalKey** compaction_end, bool* manual_conflict);
|
||||
|
||||
// The maximum allowed output level. Default value is NumberLevels() - 1.
|
||||
virtual int MaxOutputLevel() const {
|
||||
return NumberLevels() - 1;
|
||||
}
|
||||
virtual int MaxOutputLevel() const { return NumberLevels() - 1; }
|
||||
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const = 0;
|
||||
|
||||
// Sanitize the input set of compaction input files.
|
||||
// When the input parameters do not describe a valid compaction, the
|
||||
// function will try to fix the input_files by adding necessary
|
||||
// files. If it's not possible to conver an invalid input_files
|
||||
// into a valid one by adding more files, the function will return a
|
||||
// non-ok status with specific reason.
|
||||
// Sanitize the input set of compaction input files.
|
||||
// When the input parameters do not describe a valid compaction, the
|
||||
// function will try to fix the input_files by adding necessary
|
||||
// files. If it's not possible to conver an invalid input_files
|
||||
// into a valid one by adding more files, the function will return a
|
||||
// non-ok status with specific reason.
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status SanitizeCompactionInputFiles(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const;
|
||||
Status SanitizeCompactionInputFiles(std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Free up the files that participated in a compaction
|
||||
@@ -123,15 +119,15 @@ class CompactionPicker {
|
||||
// Stores the minimal range that covers all entries in inputs in
|
||||
// *smallest, *largest.
|
||||
// REQUIRES: inputs is not empty
|
||||
void GetRange(const CompactionInputFiles& inputs,
|
||||
InternalKey* smallest, InternalKey* largest);
|
||||
void GetRange(const CompactionInputFiles& inputs, InternalKey* smallest,
|
||||
InternalKey* largest);
|
||||
|
||||
// Stores the minimal range that covers all entries in inputs1 and inputs2
|
||||
// in *smallest, *largest.
|
||||
// REQUIRES: inputs is not empty
|
||||
void GetRange(const CompactionInputFiles& inputs1,
|
||||
const CompactionInputFiles& inputs2,
|
||||
InternalKey* smallest, InternalKey* largest);
|
||||
const CompactionInputFiles& inputs2, InternalKey* smallest,
|
||||
InternalKey* largest);
|
||||
|
||||
// Add more files to the inputs on "level" to make sure that
|
||||
// no newer version of a key is compacted to "level+1" while leaving an older
|
||||
@@ -166,13 +162,12 @@ class CompactionPicker {
|
||||
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
|
||||
// A helper function to SanitizeCompactionInputFiles() that
|
||||
// sanitizes "input_files" by adding necessary files.
|
||||
// A helper function to SanitizeCompactionInputFiles() that
|
||||
// sanitizes "input_files" by adding necessary files.
|
||||
#ifndef ROCKSDB_LITE
|
||||
virtual Status SanitizeCompactionInputFilesForAllLevels(
|
||||
std::unordered_set<uint64_t>* input_files,
|
||||
const ColumnFamilyMetaData& cf_meta,
|
||||
const int output_level) const;
|
||||
const ColumnFamilyMetaData& cf_meta, const int output_level) const;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Keeps track of all compactions that are running on Level0.
|
||||
@@ -192,8 +187,8 @@ class LevelCompactionPicker : public CompactionPicker {
|
||||
VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) override;
|
||||
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const
|
||||
override;
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
// Pick a path ID to place a newly generated file, with its level
|
||||
static uint32_t GetPathId(const ImmutableCFOptions& ioptions,
|
||||
@@ -232,8 +227,8 @@ class UniversalCompactionPicker : public CompactionPicker {
|
||||
|
||||
virtual int MaxOutputLevel() const override { return NumberLevels() - 1; }
|
||||
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const
|
||||
override;
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override;
|
||||
|
||||
private:
|
||||
struct SortedRun {
|
||||
@@ -307,19 +302,17 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
InternalKey** compaction_end, bool* manual_conflict) override;
|
||||
|
||||
// The maximum allowed output level. Always returns 0.
|
||||
virtual int MaxOutputLevel() const override {
|
||||
return 0;
|
||||
}
|
||||
virtual int MaxOutputLevel() const override { return 0; }
|
||||
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const
|
||||
override;
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override;
|
||||
};
|
||||
|
||||
class NullCompactionPicker : public CompactionPicker {
|
||||
public:
|
||||
NullCompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
const InternalKeyComparator* icmp) :
|
||||
CompactionPicker(ioptions, icmp) {}
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
virtual ~NullCompactionPicker() {}
|
||||
|
||||
// Always return "nullptr"
|
||||
@@ -342,14 +335,16 @@ class NullCompactionPicker : public CompactionPicker {
|
||||
}
|
||||
|
||||
// Always returns false.
|
||||
virtual bool NeedsCompaction(const VersionStorageInfo* vstorage) const
|
||||
override {
|
||||
virtual bool NeedsCompaction(
|
||||
const VersionStorageInfo* vstorage) const override {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
CompressionType GetCompressionType(const ImmutableCFOptions& ioptions,
|
||||
const VersionStorageInfo* vstorage,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
int level, int base_level,
|
||||
const bool enable_compression = true);
|
||||
|
||||
|
||||
+145
-32
@@ -79,7 +79,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
}
|
||||
|
||||
void Add(int level, uint32_t file_number, const char* smallest,
|
||||
const char* largest, uint64_t file_size = 0, uint32_t path_id = 0,
|
||||
const char* largest, uint64_t file_size = 1, uint32_t path_id = 0,
|
||||
SequenceNumber smallest_seq = 100,
|
||||
SequenceNumber largest_seq = 100) {
|
||||
assert(level < vstorage_->num_levels());
|
||||
@@ -119,7 +119,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
vstorage_->UpdateNumNonEmptyLevels();
|
||||
vstorage_->GenerateFileIndexer();
|
||||
vstorage_->GenerateLevelFilesBrief();
|
||||
vstorage_->ComputeCompactionScore(mutable_cf_options_, fifo_options_);
|
||||
vstorage_->ComputeCompactionScore(mutable_cf_options_);
|
||||
vstorage_->GenerateLevel0NonOverlapping();
|
||||
vstorage_->SetFinalized();
|
||||
}
|
||||
@@ -195,6 +195,7 @@ 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;
|
||||
Add(0, 1U, "150", "200", 1000000000U);
|
||||
// Level 1 score 1.2
|
||||
Add(1, 66U, "150", "200", 6000000U);
|
||||
@@ -319,6 +320,7 @@ TEST_F(CompactionPickerTest, Level0TriggerDynamic4) {
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 200;
|
||||
mutable_cf_options_.max_bytes_for_level_multiplier = 10;
|
||||
|
||||
NewVersionStorage(num_levels, kCompactionStyleLevel);
|
||||
Add(0, 1U, "150", "200");
|
||||
Add(0, 2U, "200", "250");
|
||||
@@ -351,6 +353,7 @@ TEST_F(CompactionPickerTest, LevelTriggerDynamic4) {
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 200;
|
||||
mutable_cf_options_.max_bytes_for_level_multiplier = 10;
|
||||
mutable_cf_options_.compaction_pri = kMinOverlappingRatio;
|
||||
NewVersionStorage(num_levels, kCompactionStyleLevel);
|
||||
Add(0, 1U, "150", "200");
|
||||
Add(num_levels - 1, 3U, "200", "250", 300U);
|
||||
@@ -366,11 +369,9 @@ TEST_F(CompactionPickerTest, LevelTriggerDynamic4) {
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(6U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(3U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(4U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(5U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(0, compaction->num_input_files(1));
|
||||
ASSERT_EQ(1U, compaction->num_input_levels());
|
||||
ASSERT_EQ(num_levels - 1, compaction->output_level());
|
||||
}
|
||||
|
||||
@@ -466,7 +467,6 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
ioptions_.compaction_options_fifo = fifo_options_;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
// must return false when there's no files.
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), false);
|
||||
@@ -491,6 +491,7 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
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_.compaction_pri = kMinOverlappingRatio;
|
||||
|
||||
Add(2, 6U, "150", "179", 50000000U);
|
||||
@@ -517,6 +518,7 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping2) {
|
||||
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_.compaction_pri = kMinOverlappingRatio;
|
||||
|
||||
Add(2, 6U, "150", "175",
|
||||
@@ -544,20 +546,21 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping2) {
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping3) {
|
||||
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 = 10000000;
|
||||
mutable_cf_options_.max_bytes_for_level_multiplier = 10;
|
||||
mutable_cf_options_.compaction_pri = kMinOverlappingRatio;
|
||||
|
||||
// file 7 and 8 over lap with the same file, but file 8 is smaller so
|
||||
// it will be picked.
|
||||
Add(2, 6U, "150", "175", 60000000U); // Overlaps with file 26, 27
|
||||
Add(2, 7U, "176", "200", 60000000U); // Overlaps with file 27
|
||||
Add(2, 8U, "201", "300", 61000000U); // Overlaps with file 27
|
||||
Add(2, 6U, "150", "167", 60000000U); // Overlaps with file 26, 27
|
||||
Add(2, 7U, "168", "169", 60000000U); // Overlaps with file 27
|
||||
Add(2, 8U, "201", "300", 61000000U); // Overlaps with file 28, but the file
|
||||
// itself is larger. Should be picked.
|
||||
|
||||
Add(3, 26U, "160", "165", 260000000U);
|
||||
Add(3, 26U, "166", "170", 260000000U);
|
||||
Add(3, 27U, "180", "400", 260000000U);
|
||||
Add(3, 28U, "401", "500", 260000000U);
|
||||
Add(3, 27U, "166", "170", 260000000U);
|
||||
Add(3, 28U, "180", "400", 260000000U);
|
||||
Add(3, 29U, "401", "500", 260000000U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
@@ -595,6 +598,8 @@ TEST_F(CompactionPickerTest, ParentIndexResetBug) {
|
||||
// ranges (with different sequence numbers) in the input files.
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.compaction_pri = kByCompensatedSize;
|
||||
|
||||
Add(1, 1U, "100", "150", 1U);
|
||||
// Overlapping user keys
|
||||
Add(1, 2U, "200", "400", 1U);
|
||||
@@ -616,9 +621,9 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys2) {
|
||||
// Overlapping user keys on same level and output level
|
||||
Add(1, 1U, "200", "400", 1000000000U);
|
||||
Add(1, 2U, "400", "500", 1U, 0, 0);
|
||||
Add(2, 3U, "400", "600", 1U);
|
||||
// The following file is not in the compaction despite overlapping user keys
|
||||
Add(2, 4U, "600", "700", 1U, 0, 0);
|
||||
Add(2, 3U, "000", "100", 1U);
|
||||
Add(2, 4U, "100", "600", 1U, 0, 0);
|
||||
Add(2, 5U, "600", "700", 1U, 0, 0);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
@@ -626,10 +631,12 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys2) {
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(3U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(4U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(5U, compaction->input(1, 2)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys3) {
|
||||
@@ -661,6 +668,31 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys3) {
|
||||
ASSERT_EQ(7U, compaction->input(1, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, OverlappingUserKeys4) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.max_bytes_for_level_base = 1000000;
|
||||
|
||||
Add(1, 1U, "100", "150", 1U);
|
||||
Add(1, 2U, "150", "199", 1U, 0, 0);
|
||||
Add(1, 3U, "200", "250", 1100000U, 0, 0);
|
||||
Add(1, 4U, "251", "300", 1U, 0, 0);
|
||||
Add(1, 5U, "300", "350", 1U, 0, 0);
|
||||
|
||||
Add(2, 6U, "100", "115", 1U);
|
||||
Add(2, 7U, "125", "325", 1U);
|
||||
Add(2, 8U, "350", "400", 1U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
@@ -770,19 +802,22 @@ TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded1) {
|
||||
Add(1, 4U, "400", "500", 600);
|
||||
Add(1, 5U, "600", "700", 600);
|
||||
// Level 2 is less than target 10000 even added size of level 1
|
||||
// Size ratio of L2/L1 is 9600 / 1200 = 8
|
||||
Add(2, 6U, "150", "200", 2500);
|
||||
Add(2, 7U, "201", "210", 2000);
|
||||
Add(2, 8U, "300", "310", 2500);
|
||||
Add(2, 8U, "300", "310", 2600);
|
||||
Add(2, 9U, "400", "500", 2500);
|
||||
// Level 3 exceeds target 100,000 of 1000
|
||||
Add(3, 10U, "400", "500", 101000);
|
||||
// Level 4 exceeds target 1,000,000 of 500 after adding size from level 3
|
||||
Add(4, 11U, "400", "500", 999500);
|
||||
Add(5, 11U, "400", "500", 8000000);
|
||||
// Level 4 exceeds target 1,000,000 by 900 after adding size from level 3
|
||||
// Size ratio L4/L3 is 9.9
|
||||
// After merge from L3, L4 size is 1000900
|
||||
Add(4, 11U, "400", "500", 999900);
|
||||
Add(5, 11U, "400", "500", 8007200);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(2200u + 11000u + 5500u,
|
||||
ASSERT_EQ(200u * 9u + 10900u + 900u * 9,
|
||||
vstorage_->estimated_compaction_needed_bytes());
|
||||
}
|
||||
|
||||
@@ -798,17 +833,42 @@ TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded2) {
|
||||
Add(0, 4U, "150", "200", 200);
|
||||
Add(0, 5U, "150", "200", 200);
|
||||
Add(0, 6U, "150", "200", 200);
|
||||
// Level 1 is over target by
|
||||
// Level 1 size will be 1400 after merging with L0
|
||||
Add(1, 7U, "400", "500", 200);
|
||||
Add(1, 8U, "600", "700", 200);
|
||||
// Level 2 is less than target 10000 even added size of level 1
|
||||
Add(2, 9U, "150", "200", 9500);
|
||||
Add(2, 9U, "150", "200", 9100);
|
||||
// Level 3 over the target, but since level 4 is empty, we assume it will be
|
||||
// a trivial move.
|
||||
Add(3, 10U, "400", "500", 101000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(1400u + 4400u + 11000u,
|
||||
vstorage_->estimated_compaction_needed_bytes());
|
||||
// estimated L1->L2 merge: 400 * (9100.0 / 1400.0 + 1.0)
|
||||
ASSERT_EQ(1400u + 3000u, vstorage_->estimated_compaction_needed_bytes());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, EstimateCompactionBytesNeeded3) {
|
||||
int num_levels = ioptions_.num_levels;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 3;
|
||||
mutable_cf_options_.max_bytes_for_level_base = 1000;
|
||||
mutable_cf_options_.max_bytes_for_level_multiplier = 10;
|
||||
NewVersionStorage(num_levels, kCompactionStyleLevel);
|
||||
Add(0, 1U, "150", "200", 2000);
|
||||
Add(0, 2U, "150", "200", 2000);
|
||||
Add(0, 4U, "150", "200", 2000);
|
||||
Add(0, 5U, "150", "200", 2000);
|
||||
Add(0, 6U, "150", "200", 1000);
|
||||
// Level 1 size will be 10000 after merging with L0
|
||||
Add(1, 7U, "400", "500", 500);
|
||||
Add(1, 8U, "600", "700", 500);
|
||||
|
||||
Add(2, 9U, "150", "200", 10000);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(10000u + 18000u, vstorage_->estimated_compaction_needed_bytes());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, EstimateCompactionBytesNeededDynamicLevel) {
|
||||
@@ -832,12 +892,14 @@ TEST_F(CompactionPickerTest, EstimateCompactionBytesNeededDynamicLevel) {
|
||||
// num_levels - 3 is over target by 100 + 1000
|
||||
Add(num_levels - 3, 7U, "400", "500", 300);
|
||||
Add(num_levels - 3, 8U, "600", "700", 300);
|
||||
// Level 2 is over target by 1100 + 100
|
||||
Add(num_levels - 2, 9U, "150", "200", 5100);
|
||||
// num_levels - 2 is over target by 1100 + 200
|
||||
Add(num_levels - 2, 9U, "150", "200", 5200);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(1600u + 12100u + 13200u,
|
||||
// Merging to the second last level: (5200 / 1600 + 1) * 1100
|
||||
// Merging to the last level: (50000 / 6300 + 1) * 1300
|
||||
ASSERT_EQ(1600u + 4675u + 11617u,
|
||||
vstorage_->estimated_compaction_needed_bytes());
|
||||
}
|
||||
|
||||
@@ -999,6 +1061,57 @@ TEST_F(CompactionPickerTest, IsBottommostLevelTest) {
|
||||
DeleteVersionStorage();
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, MaxCompactionBytesHit) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 1000000u;
|
||||
mutable_cf_options_.max_compaction_bytes = 800000u;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// A compaction should be triggered and pick file 2 and 5.
|
||||
// It cannot expand because adding file 1 and 3, the compaction size will
|
||||
// exceed mutable_cf_options_.max_bytes_for_level_base.
|
||||
Add(1, 1U, "100", "150", 300000U);
|
||||
Add(1, 2U, "151", "200", 300001U, 0, 0);
|
||||
Add(1, 3U, "201", "250", 300000U, 0, 0);
|
||||
Add(1, 4U, "251", "300", 300000U, 0, 0);
|
||||
Add(2, 5U, "160", "256", 1U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, MaxCompactionBytesNotHit) {
|
||||
mutable_cf_options_.max_bytes_for_level_base = 1000000u;
|
||||
mutable_cf_options_.max_compaction_bytes = 1000000u;
|
||||
ioptions_.level_compaction_dynamic_level_bytes = false;
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
// A compaction should be triggered and pick file 2 and 5.
|
||||
// and it expands to file 1 and 3 too.
|
||||
Add(1, 1U, "100", "150", 300000U);
|
||||
Add(1, 2U, "151", "200", 300001U, 0, 0);
|
||||
Add(1, 3U, "201", "250", 300000U, 0, 0);
|
||||
Add(1, 4U, "251", "300", 300000U, 0, 0);
|
||||
Add(2, 5U, "000", "233", 1U);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(level_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, vstorage_.get(), &log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_levels());
|
||||
ASSERT_EQ(3U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(0, 2)->fd.GetNumber());
|
||||
ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -42,6 +42,7 @@ class CorruptionTest : public testing::Test {
|
||||
|
||||
CorruptionTest() {
|
||||
tiny_cache_ = NewLRUCache(100);
|
||||
options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options_.env = &env_;
|
||||
dbname_ = test::TmpDir() + "/corruption_test";
|
||||
DestroyDB(dbname_, options_);
|
||||
|
||||
+351
-9
@@ -9,14 +9,10 @@
|
||||
#include <cstdlib>
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/lru_cache.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static uint64_t TestGetTickerCount(const Options& options,
|
||||
Tickers ticker_type) {
|
||||
return options.statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
|
||||
class DBBlockCacheTest : public DBTestBase {
|
||||
private:
|
||||
size_t miss_count_ = 0;
|
||||
@@ -44,6 +40,7 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
Options GetOptions(const BlockBasedTableOptions& table_options) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
// options.compression = kNoCompression;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
@@ -166,14 +163,16 @@ TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SNAPPY
|
||||
TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
ReadOptions read_options;
|
||||
auto table_options = GetTableOptions();
|
||||
auto options = GetOptions(table_options);
|
||||
options.compression = CompressionType::kSnappyCompression;
|
||||
InitTable(options);
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
std::shared_ptr<Cache> compressed_cache = NewLRUCache(0, 0, false);
|
||||
std::shared_ptr<Cache> compressed_cache = NewLRUCache(1 << 25, 0, false);
|
||||
table_options.block_cache = cache;
|
||||
table_options.block_cache_compressed = compressed_cache;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
@@ -205,9 +204,6 @@ TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
cache->SetCapacity(usage);
|
||||
cache->SetStrictCapacityLimit(true);
|
||||
ASSERT_EQ(usage, cache->GetPinnedUsage());
|
||||
// compressed_cache->SetCapacity(compressed_usage);
|
||||
compressed_cache->SetCapacity(0);
|
||||
// compressed_cache->SetStrictCapacityLimit(true);
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(kNumBlocks - 1));
|
||||
ASSERT_TRUE(iter->status().IsIncomplete());
|
||||
@@ -227,6 +223,352 @@ TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
}
|
||||
#endif // SNAPPY
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
// Make sure that when options.block_cache is set, after a new table is
|
||||
// created its index/filter blocks are added to block cache.
|
||||
TEST_F(DBBlockCacheTest, IndexAndFilterBlocksOfNewTableAddedToCache) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
// Create a new table.
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// index/filter blocks added to block cache right after table creation.
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(2, /* only index/filter were added */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
|
||||
uint64_t int_num;
|
||||
ASSERT_TRUE(
|
||||
dbfull()->GetIntProperty("rocksdb.estimate-table-readers-mem", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
|
||||
// Make sure filter block is in cache.
|
||||
std::string value;
|
||||
ReadOptions ropt;
|
||||
db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
|
||||
|
||||
// Miss count should remain the same.
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
|
||||
db_->KeyMayExist(ReadOptions(), handles_[1], "key", &value);
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(2, TestGetTickerCount(options, BLOCK_CACHE_FILTER_HIT));
|
||||
|
||||
// Make sure index block is in cache.
|
||||
auto index_block_hit = TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT);
|
||||
value = Get(1, "key");
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(index_block_hit + 1,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
|
||||
value = Get(1, "key");
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(index_block_hit + 2,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_INDEX_HIT));
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
// 200 bytes are enough to hold the first two blocks
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(200, 0, false);
|
||||
table_options.block_cache = cache;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "key", "val"));
|
||||
// Create a new table
|
||||
ASSERT_OK(Flush(1));
|
||||
size_t index_bytes_insert =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_INSERT);
|
||||
size_t filter_bytes_insert =
|
||||
TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT);
|
||||
ASSERT_GT(index_bytes_insert, 0);
|
||||
ASSERT_GT(filter_bytes_insert, 0);
|
||||
ASSERT_EQ(cache->GetUsage(), index_bytes_insert + filter_bytes_insert);
|
||||
// set the cache capacity to the current usage
|
||||
cache->SetCapacity(index_bytes_insert + filter_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT), 0);
|
||||
ASSERT_OK(Put(1, "key2", "val"));
|
||||
// Create a new table
|
||||
ASSERT_OK(Flush(1));
|
||||
// cache evicted old index and block entries
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_INSERT),
|
||||
index_bytes_insert);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_INSERT),
|
||||
filter_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_INDEX_BYTES_EVICT),
|
||||
index_bytes_insert);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_FILTER_BYTES_EVICT),
|
||||
filter_bytes_insert);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// A mock cache wraps LRUCache, and record how many entries have been
|
||||
// inserted for each priority.
|
||||
class MockCache : public LRUCache {
|
||||
public:
|
||||
static uint32_t high_pri_insert_count;
|
||||
static uint32_t low_pri_insert_count;
|
||||
|
||||
MockCache() : LRUCache(1 << 25, 0, false, 0.0) {}
|
||||
|
||||
virtual Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle, Priority priority) override {
|
||||
if (priority == Priority::LOW) {
|
||||
low_pri_insert_count++;
|
||||
} else {
|
||||
high_pri_insert_count++;
|
||||
}
|
||||
return LRUCache::Insert(key, value, charge, deleter, handle, priority);
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t MockCache::high_pri_insert_count = 0;
|
||||
uint32_t MockCache::low_pri_insert_count = 0;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_F(DBBlockCacheTest, IndexAndFilterBlocksCachePriority) {
|
||||
for (auto priority : {Cache::Priority::LOW, Cache::Priority::HIGH}) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.block_cache.reset(new MockCache());
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
table_options.cache_index_and_filter_blocks_with_high_priority =
|
||||
priority == Cache::Priority::HIGH ? true : false;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
MockCache::high_pri_insert_count = 0;
|
||||
MockCache::low_pri_insert_count = 0;
|
||||
|
||||
// Create a new table.
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Put("bar", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(0));
|
||||
|
||||
// index/filter blocks added to block cache right after table creation.
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(2, /* only index/filter were added */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
ASSERT_EQ(0, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
|
||||
if (priority == Cache::Priority::LOW) {
|
||||
ASSERT_EQ(0, MockCache::high_pri_insert_count);
|
||||
ASSERT_EQ(2, MockCache::low_pri_insert_count);
|
||||
} else {
|
||||
ASSERT_EQ(2, MockCache::high_pri_insert_count);
|
||||
ASSERT_EQ(0, MockCache::low_pri_insert_count);
|
||||
}
|
||||
|
||||
// Access data block.
|
||||
ASSERT_EQ("value", Get("foo"));
|
||||
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(3, /*adding data block*/
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
ASSERT_EQ(1, TestGetTickerCount(options, BLOCK_CACHE_DATA_MISS));
|
||||
|
||||
// Data block should be inserted with low priority.
|
||||
if (priority == Cache::Priority::LOW) {
|
||||
ASSERT_EQ(0, MockCache::high_pri_insert_count);
|
||||
ASSERT_EQ(3, MockCache::low_pri_insert_count);
|
||||
} else {
|
||||
ASSERT_EQ(2, MockCache::high_pri_insert_count);
|
||||
ASSERT_EQ(1, MockCache::low_pri_insert_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, ParanoidFileChecks) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.paranoid_file_checks = true;
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "1_key", "val"));
|
||||
ASSERT_OK(Put(1, "9_key", "val"));
|
||||
// Create a new table.
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_EQ(1, /* read and cache data block */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
ASSERT_OK(Put(1, "1_key2", "val2"));
|
||||
ASSERT_OK(Put(1, "9_key2", "val2"));
|
||||
// Create a new SST file. This will further trigger a compaction
|
||||
// and generate another file.
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(3, /* Totally 3 files created up to now */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
// After disabling options.paranoid_file_checks. NO further block
|
||||
// is added after generating a new file.
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[1], {{"paranoid_file_checks", "false"}}));
|
||||
|
||||
ASSERT_OK(Put(1, "1_key3", "val3"));
|
||||
ASSERT_OK(Put(1, "9_key3", "val3"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "1_key4", "val4"));
|
||||
ASSERT_OK(Put(1, "9_key4", "val4"));
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(3, /* Totally 3 files created up to now */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, CompressedCache) {
|
||||
if (!Snappy_Supported()) {
|
||||
return;
|
||||
}
|
||||
int num_iter = 80;
|
||||
|
||||
// Run this test three iterations.
|
||||
// Iteration 1: only a uncompressed block cache
|
||||
// Iteration 2: only a compressed block cache
|
||||
// Iteration 3: both block cache and compressed cache
|
||||
// Iteration 4: both block cache and compressed cache, but DB is not
|
||||
// compressed
|
||||
for (int iter = 0; iter < 4; iter++) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 64 * 1024; // small write buffer
|
||||
options.statistics = rocksdb::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
switch (iter) {
|
||||
case 0:
|
||||
// only uncompressed block cache
|
||||
table_options.block_cache = NewLRUCache(8 * 1024);
|
||||
table_options.block_cache_compressed = nullptr;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 1:
|
||||
// no block cache, only compressed cache
|
||||
table_options.no_block_cache = true;
|
||||
table_options.block_cache = nullptr;
|
||||
table_options.block_cache_compressed = NewLRUCache(8 * 1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 2:
|
||||
// both compressed and uncompressed block cache
|
||||
table_options.block_cache = NewLRUCache(1024);
|
||||
table_options.block_cache_compressed = NewLRUCache(8 * 1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
break;
|
||||
case 3:
|
||||
// both block cache and compressed cache, but DB is not compressed
|
||||
// also, make block cache sizes bigger, to trigger block cache hits
|
||||
table_options.block_cache = NewLRUCache(1024 * 1024);
|
||||
table_options.block_cache_compressed = NewLRUCache(8 * 1024 * 1024);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.compression = kNoCompression;
|
||||
break;
|
||||
default:
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
// default column family doesn't have block cache
|
||||
Options no_block_cache_opts;
|
||||
no_block_cache_opts.statistics = options.statistics;
|
||||
no_block_cache_opts = CurrentOptions(no_block_cache_opts);
|
||||
BlockBasedTableOptions table_options_no_bc;
|
||||
table_options_no_bc.no_block_cache = true;
|
||||
no_block_cache_opts.table_factory.reset(
|
||||
NewBlockBasedTableFactory(table_options_no_bc));
|
||||
ReopenWithColumnFamilies(
|
||||
{"default", "pikachu"},
|
||||
std::vector<Options>({no_block_cache_opts, options}));
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
// Write 8MB (80 values, each 100K)
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
std::vector<std::string> values;
|
||||
std::string str;
|
||||
for (int i = 0; i < num_iter; i++) {
|
||||
if (i % 4 == 0) { // high compression ratio
|
||||
str = RandomString(&rnd, 1000);
|
||||
}
|
||||
values.push_back(str);
|
||||
ASSERT_OK(Put(1, Key(i), values[i]));
|
||||
}
|
||||
|
||||
// flush all data from memtable so that reads are from block cache
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
for (int i = 0; i < num_iter; i++) {
|
||||
ASSERT_EQ(Get(1, Key(i)), values[i]);
|
||||
}
|
||||
|
||||
// check that we triggered the appropriate code paths in the cache
|
||||
switch (iter) {
|
||||
case 0:
|
||||
// only uncompressed block cache
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
case 1:
|
||||
// no block cache, only compressed cache
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
case 2:
|
||||
// both compressed and uncompressed block cache
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
break;
|
||||
case 3:
|
||||
// both compressed and uncompressed block cache
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_MISS), 0);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_HIT), 0);
|
||||
ASSERT_GT(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_MISS), 0);
|
||||
// compressed doesn't have any hits since blocks are not compressed on
|
||||
// storage
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOCK_CACHE_COMPRESSED_HIT), 0);
|
||||
break;
|
||||
default:
|
||||
ASSERT_TRUE(false);
|
||||
}
|
||||
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -368,11 +368,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
// entries are deleted. The compaction should create bunch of 'DeleteFile'
|
||||
// entries in VersionEdit, but none of the 'AddFile's.
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// put some data
|
||||
@@ -400,11 +399,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) {
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterWithValueChange) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.num_levels = 3;
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<ChangeFilterFactory>();
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Write 100K+1 keys, these are written to a few files
|
||||
@@ -467,8 +465,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
|
||||
PutFixed64(&three, 3);
|
||||
PutFixed64(&four, 4);
|
||||
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
options.num_levels = 3;
|
||||
@@ -621,11 +618,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextCfId) {
|
||||
// Compaction filters should only be applied to records that are newer than the
|
||||
// latest snapshot. This test inserts records and applies a delete filter.
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterSnapshot) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Put some data.
|
||||
@@ -659,11 +655,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilterSnapshot) {
|
||||
// records newer than the snapshot will also be processed
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
std::string five = ToString(5);
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory = std::make_shared<DeleteISFilterFactory>();
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Put some data.
|
||||
|
||||
+101
-72
@@ -40,34 +40,6 @@ class DBCompactionTestWithParam
|
||||
};
|
||||
|
||||
namespace {
|
||||
class OnFileDeletionListener : public EventListener {
|
||||
public:
|
||||
OnFileDeletionListener() :
|
||||
matched_count_(0),
|
||||
expected_file_name_("") {}
|
||||
|
||||
void SetExpectedFileName(
|
||||
const std::string file_name) {
|
||||
expected_file_name_ = file_name;
|
||||
}
|
||||
|
||||
void VerifyMatchedCount(size_t expected_value) {
|
||||
ASSERT_EQ(matched_count_, expected_value);
|
||||
}
|
||||
|
||||
void OnTableFileDeleted(
|
||||
const TableFileDeletionInfo& info) override {
|
||||
if (expected_file_name_ != "") {
|
||||
ASSERT_EQ(expected_file_name_, info.file_path);
|
||||
expected_file_name_ = "";
|
||||
matched_count_++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t matched_count_;
|
||||
std::string expected_file_name_;
|
||||
};
|
||||
|
||||
class FlushedFileCollector : public EventListener {
|
||||
public:
|
||||
@@ -98,8 +70,7 @@ class FlushedFileCollector : public EventListener {
|
||||
static const int kCDTValueSize = 1000;
|
||||
static const int kCDTKeysPerBuffer = 4;
|
||||
static const int kCDTNumLevels = 8;
|
||||
Options DeletionTriggerOptions() {
|
||||
Options options;
|
||||
Options DeletionTriggerOptions(Options options) {
|
||||
options.compression = kNoCompression;
|
||||
options.write_buffer_size = kCDTKeysPerBuffer * (kCDTValueSize + 24);
|
||||
options.min_write_buffer_number_to_merge = 1;
|
||||
@@ -202,7 +173,7 @@ const SstFileMetaData* PickFileRandomly(
|
||||
TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
for (int tid = 0; tid < 3; ++tid) {
|
||||
uint64_t db_size[2];
|
||||
Options options = CurrentOptions(DeletionTriggerOptions());
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
|
||||
if (tid == 1) {
|
||||
@@ -241,12 +212,11 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTrigger) {
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
// This test verify UpdateAccumulatedStats is not on by observing
|
||||
// the compaction behavior when there are many of deletion entries.
|
||||
// This test verify UpdateAccumulatedStats is not on
|
||||
// if options.skip_stats_update_on_db_open = true
|
||||
// The test will need to be updated if the internal behavior changes.
|
||||
|
||||
Options options = DeletionTriggerOptions();
|
||||
options = CurrentOptions(options);
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
@@ -260,10 +230,6 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
for (int k = 0; k < kTestSize; ++k) {
|
||||
ASSERT_OK(Delete(Key(k)));
|
||||
}
|
||||
|
||||
// Reopen the DB with stats-update disabled
|
||||
options.skip_stats_update_on_db_open = true;
|
||||
env_->random_file_open_counter_.store(0);
|
||||
@@ -288,8 +254,7 @@ TEST_F(DBCompactionTest, SkipStatsUpdateTest) {
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.new_table_reader_for_compaction_inputs = true;
|
||||
options.max_open_files = 100;
|
||||
@@ -378,7 +343,7 @@ TEST_F(DBCompactionTest, TestTableReaderForCompaction) {
|
||||
TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) {
|
||||
for (int tid = 0; tid < 2; ++tid) {
|
||||
uint64_t db_size[3];
|
||||
Options options = CurrentOptions(DeletionTriggerOptions());
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
|
||||
if (tid == 1) {
|
||||
@@ -435,7 +400,7 @@ TEST_P(DBCompactionTestWithParam, CompactionDeletionTriggerReopen) {
|
||||
TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
uint64_t db_size[3];
|
||||
for (int test = 0; test < 2; ++test) {
|
||||
Options options = CurrentOptions(DeletionTriggerOptions());
|
||||
Options options = DeletionTriggerOptions(CurrentOptions());
|
||||
options.skip_stats_update_on_db_open = (test == 0);
|
||||
|
||||
env_->random_read_counter_.Reset();
|
||||
@@ -493,14 +458,13 @@ TEST_F(DBCompactionTest, DisableStatsUpdateReopen) {
|
||||
TEST_P(DBCompactionTestWithParam, CompactionTrigger) {
|
||||
const int kNumKeysPerFile = 100;
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.num_levels = 3;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -538,7 +502,7 @@ TEST_F(DBCompactionTest, BGCompactionsAllowed) {
|
||||
// and see number of compactions scheduled to be less than allowed.
|
||||
const int kNumKeysPerFile = 100;
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.num_levels = 3;
|
||||
@@ -549,7 +513,6 @@ TEST_F(DBCompactionTest, BGCompactionsAllowed) {
|
||||
options.base_background_compactions = 1;
|
||||
options.max_background_compactions = 3;
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeysPerFile));
|
||||
options = CurrentOptions(options);
|
||||
|
||||
// Block all threads in thread pool.
|
||||
const size_t kTotalTasks = 4;
|
||||
@@ -632,10 +595,9 @@ TEST_F(DBCompactionTest, BGCompactionsAllowed) {
|
||||
}
|
||||
|
||||
TEST_P(DBCompactionTestWithParam, CompactionsGenerateMultipleFiles) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100000000; // Large write buffer
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -662,9 +624,8 @@ TEST_P(DBCompactionTestWithParam, CompactionsGenerateMultipleFiles) {
|
||||
|
||||
TEST_F(DBCompactionTest, MinorCompactionsHappen) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 10000;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
const int N = 500;
|
||||
@@ -688,8 +649,74 @@ TEST_F(DBCompactionTest, MinorCompactionsHappen) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, UserKeyCrossFile1) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// create first file and flush to l0
|
||||
Put("4", "A");
|
||||
Put("3", "A");
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
Put("2", "A");
|
||||
Delete("3");
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ("NOT_FOUND", Get("3"));
|
||||
|
||||
// move both files down to l1
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ("NOT_FOUND", Get("3"));
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Put("2", "B");
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("3"));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, UserKeyCrossFile2) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// create first file and flush to l0
|
||||
Put("4", "A");
|
||||
Put("3", "A");
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
|
||||
Put("2", "A");
|
||||
SingleDelete("3");
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ("NOT_FOUND", Get("3"));
|
||||
|
||||
// move both files down to l1
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_EQ("NOT_FOUND", Get("3"));
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Put("2", "B");
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
ASSERT_EQ("NOT_FOUND", Get("3"));
|
||||
}
|
||||
|
||||
TEST_F(DBCompactionTest, ZeroSeqIdCompaction) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
|
||||
@@ -703,7 +730,6 @@ TEST_F(DBCompactionTest, ZeroSeqIdCompaction) {
|
||||
const size_t key_len =
|
||||
static_cast<size_t>(compact_opt.output_file_size_limit) / 5;
|
||||
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::vector<const Snapshot*> snaps;
|
||||
@@ -747,10 +773,8 @@ TEST_F(DBCompactionTest, ZeroSeqIdCompaction) {
|
||||
// if the database is shutdown during the memtable compaction.
|
||||
TEST_F(DBCompactionTest, RecoverDuringMemtableCompaction) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 1000000;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Trigger a long memtable compaction and reopen the database during it
|
||||
@@ -774,10 +798,9 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveOneFile) {
|
||||
[&](void* arg) { trivial_move++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100000000;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int32_t num_keys = 80;
|
||||
@@ -992,17 +1015,21 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
"DBImpl::BackgroundCompaction:NonTrivial",
|
||||
[&](void* arg) { non_trivial_move++; });
|
||||
bool first = true;
|
||||
bool second = true;
|
||||
// Purpose of dependencies:
|
||||
// 4 -> 1: ensure the order of two non-trivial compactions
|
||||
// 5 -> 2 and 5 -> 3: ensure we do a check before two non-trivial compactions
|
||||
// are installed
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBCompaction::ManualPartial:4", "DBCompaction::ManualPartial:1"},
|
||||
{"DBCompaction::ManualPartial:2", "DBCompaction::ManualPartial:3"}});
|
||||
{"DBCompaction::ManualPartial:5", "DBCompaction::ManualPartial:2"},
|
||||
{"DBCompaction::ManualPartial:5", "DBCompaction::ManualPartial:3"}});
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::BackgroundCompaction:NonTrivial:AfterRun", [&](void* arg) {
|
||||
if (first) {
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:4");
|
||||
first = false;
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:4");
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:3");
|
||||
} else if (second) {
|
||||
} else { // second non-trivial compaction
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:2");
|
||||
}
|
||||
});
|
||||
@@ -1079,6 +1106,7 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
std::string end_string = Key(199);
|
||||
Slice begin(begin_string);
|
||||
Slice end(end_string);
|
||||
// First non-trivial compaction is triggered
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &begin, &end));
|
||||
});
|
||||
|
||||
@@ -1102,13 +1130,17 @@ TEST_P(DBCompactionTestWithParam, ManualCompactionPartial) {
|
||||
values[i] = RandomString(&rnd, value_size);
|
||||
ASSERT_OK(Put(Key(i), values[i]));
|
||||
}
|
||||
// Second non-trivial compaction is triggered
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// 3 files in L0
|
||||
// Before two non-trivial compactions are installed, there are 3 files in L0
|
||||
ASSERT_EQ("3,0,0,0,0,1,2", FilesPerLevel(0));
|
||||
// 1 file in L6, 1 file in L1
|
||||
TEST_SYNC_POINT("DBCompaction::ManualPartial:5");
|
||||
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// After two non-trivial compactions are installed, there is 1 file in L6, and
|
||||
// 1 file in L1
|
||||
ASSERT_EQ("0,1,0,0,0,0,1", FilesPerLevel(0));
|
||||
threads.join();
|
||||
|
||||
@@ -1369,10 +1401,9 @@ TEST_P(DBCompactionTestWithParam, TrivialMoveToLastLevelWithFiles) {
|
||||
[&](void* arg) { non_trivial_move++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100000000;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int32_t value_size = 10 * 1024; // 10 KB
|
||||
@@ -1661,7 +1692,7 @@ TEST_P(DBCompactionTestWithParam, ConvertCompactionStyle) {
|
||||
int max_key_universal_insert = 600;
|
||||
|
||||
// Stage 1: generate a db with level compaction
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.num_levels = 4;
|
||||
@@ -1671,7 +1702,6 @@ TEST_P(DBCompactionTestWithParam, ConvertCompactionStyle) {
|
||||
options.target_file_size_base = 200 << 10; // 200KB
|
||||
options.target_file_size_multiplier = 1;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
for (int i = 0; i <= max_key_level_insert; i++) {
|
||||
@@ -2386,10 +2416,9 @@ TEST_P(DBCompactionTestWithParam, ForceBottommostLevelCompaction) {
|
||||
[&](void* arg) { non_trivial_move++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100000000;
|
||||
options.max_subcompactions = max_subcompactions_;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
int32_t value_size = 10 * 1024; // 10 KB
|
||||
@@ -2479,14 +2508,14 @@ class CompactionPriTest : public DBTestBase,
|
||||
};
|
||||
|
||||
TEST_P(CompactionPriTest, Test) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 16 * 1024;
|
||||
options.compaction_pri = static_cast<CompactionPri>(compaction_pri_);
|
||||
options.hard_pending_compaction_bytes_limit = 256 * 1024;
|
||||
options.max_bytes_for_level_base = 64 * 1024;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.compression = kNoCompression;
|
||||
options = CurrentOptions(options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
+45
-34
@@ -125,19 +125,19 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.db_write_buffer_size = 2048;
|
||||
options.write_buffer_size = 2048;
|
||||
options.db_write_buffer_size = 204800;
|
||||
options.write_buffer_size = 20480;
|
||||
options.max_write_buffer_number = 2;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 9999;
|
||||
options.level0_stop_writes_trigger = 9999;
|
||||
options.target_file_size_base = 2048;
|
||||
options.target_file_size_base = 9102;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.max_bytes_for_level_base = 10240;
|
||||
options.max_bytes_for_level_base = 40960;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.max_background_compactions = 2;
|
||||
options.num_levels = 5;
|
||||
options.expanded_compaction_factor = 0; // Force not expanding in compactions
|
||||
options.max_compaction_bytes = 0; // Force not expanding in compactions
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
@@ -154,10 +154,10 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(4U, int_prop);
|
||||
|
||||
// Put about 7K to L0
|
||||
// Put about 28K to L0
|
||||
for (int i = 0; i < 70; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
@@ -167,14 +167,14 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(4U, int_prop);
|
||||
|
||||
// Insert extra about 3.5K to L0. After they are compacted to L4, base level
|
||||
// Insert extra about 28K to L0. After they are compacted to L4, base level
|
||||
// should be changed to L3.
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
for (int i = 0; i < 70; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
@@ -199,10 +199,10 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
// Write about 10K more
|
||||
// Write about 40K more
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
@@ -218,15 +218,16 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
|
||||
// Trigger a condition that the compaction changes base level and L0->Lbase
|
||||
// happens at the same time.
|
||||
// We try to make last levels' targets to be 10K, 40K, 160K, add triggers
|
||||
// We try to make last levels' targets to be 40K, 160K, 640K, add triggers
|
||||
// another compaction from 40K->160K.
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "true"},
|
||||
}));
|
||||
// Write about 150K more
|
||||
for (int i = 0; i < 1350; i++) {
|
||||
// Write about 650K more.
|
||||
// Each file is about 11KB, with 9KB of data.
|
||||
for (int i = 0; i < 1300; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
@@ -236,29 +237,39 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(2U, int_prop);
|
||||
|
||||
// A manual compaction will trigger the base level to become L2
|
||||
// Keep Writing data until base level changed 2->1. There will be L0->L2
|
||||
// compaction going on at the same time.
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
for (int attempt = 0; attempt <= 20; attempt++) {
|
||||
// Write about 5K more data with two flushes. It should be flush to level 2
|
||||
// but when it is applied, base level is already 1.
|
||||
for (int i = 0; i < 50; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 80)));
|
||||
}
|
||||
Flush();
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
if (int_prop == 2U) {
|
||||
env_->SleepForMicroseconds(50000);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
env_->SleepForMicroseconds(200000);
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"CompactionJob::Run():Start", "DynamicLevelMaxBytesBase2:0"},
|
||||
{"DynamicLevelMaxBytesBase2:1", "CompactionJob::Run():End"},
|
||||
{"DynamicLevelMaxBytesBase2:compact_range_finish",
|
||||
"FlushJob::WriteLevel0Table"},
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread thread([this] {
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:compact_range_start");
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:compact_range_finish");
|
||||
});
|
||||
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:0");
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ASSERT_OK(Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))),
|
||||
RandomString(&rnd, 380)));
|
||||
}
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:1");
|
||||
|
||||
Flush();
|
||||
|
||||
thread.join();
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
rocksdb::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(1U, int_prop);
|
||||
@@ -284,7 +295,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesCompactRange) {
|
||||
options.max_background_compactions = 1;
|
||||
const int kNumLevels = 5;
|
||||
options.num_levels = kNumLevels;
|
||||
options.expanded_compaction_factor = 0; // Force not expanding in compactions
|
||||
options.max_compaction_bytes = 1; // Force not expanding in compactions
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
@@ -14,19 +14,20 @@
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdint.h>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <stdint.h>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/version_set.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "port/port.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/testharness.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -83,7 +84,6 @@ int DBImpl::IsFileDeletionsEnabled() const {
|
||||
Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
uint64_t* manifest_file_size,
|
||||
bool flush_memtable) {
|
||||
|
||||
*manifest_file_size = 0;
|
||||
|
||||
mutex_.Lock();
|
||||
@@ -126,7 +126,7 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
}
|
||||
|
||||
ret.clear();
|
||||
ret.reserve(live.size() + 2); //*.sst + CURRENT + MANIFEST
|
||||
ret.reserve(live.size() + 3); // *.sst + CURRENT + MANIFEST + OPTIONS
|
||||
|
||||
// create names of the live files. The names are not absolute
|
||||
// paths, instead they are relative to dbname_;
|
||||
@@ -136,6 +136,7 @@ Status DBImpl::GetLiveFiles(std::vector<std::string>& ret,
|
||||
|
||||
ret.push_back(CurrentFileName(""));
|
||||
ret.push_back(DescriptorFileName("", versions_->manifest_file_number()));
|
||||
ret.push_back(OptionsFileName("", versions_->options_file_number()));
|
||||
|
||||
// find length of manifest file while holding the mutex lock
|
||||
*manifest_file_size = versions_->manifest_file_size();
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same 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 "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBFlushTest : public DBTestBase {
|
||||
public:
|
||||
DBFlushTest() : DBTestBase("/db_flush_test") {}
|
||||
};
|
||||
|
||||
// We had issue when two background threads trying to flush at the same time,
|
||||
// only one of them get committed. The test verifies the issue is fixed.
|
||||
TEST_F(DBFlushTest, FlushWhileWritingManifest) {
|
||||
Options options;
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_background_flushes = 2;
|
||||
Reopen(options);
|
||||
FlushOptions no_wait;
|
||||
no_wait.wait = false;
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"VersionSet::LogAndApply:WriteManifest",
|
||||
"DBFlushTest::FlushWhileWritingManifest:1"},
|
||||
{"MemTableList::InstallMemtableFlushResults:InProgress",
|
||||
"VersionSet::LogAndApply:WriteManifestDone"}});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(Put("foo", "v"));
|
||||
ASSERT_OK(dbfull()->Flush(no_wait));
|
||||
TEST_SYNC_POINT("DBFlushTest::FlushWhileWritingManifest:1");
|
||||
ASSERT_OK(Put("bar", "v"));
|
||||
ASSERT_OK(dbfull()->Flush(no_wait));
|
||||
// If the issue is hit we will wait here forever.
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
#ifndef ROCKSDB_LITE
|
||||
ASSERT_EQ(2, TotalTableFiles());
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+760
-429
File diff suppressed because it is too large.
Load diff
+197
-25
@@ -10,8 +10,10 @@
|
||||
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <list>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -29,13 +31,13 @@
|
||||
#include "db/wal_manager.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "memtable_list.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/scoped_arena_iterator.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/event_logger.h"
|
||||
@@ -55,6 +57,7 @@ class Arena;
|
||||
class WriteCallback;
|
||||
struct JobContext;
|
||||
struct ExternalSstFileInfo;
|
||||
struct MemTableInfo;
|
||||
|
||||
class DBImpl : public DB {
|
||||
public:
|
||||
@@ -232,7 +235,7 @@ class DBImpl : public DB {
|
||||
// SST files will also be checked.
|
||||
//
|
||||
// If a key is found, *found_record_for_key will be set to true and
|
||||
// *seq will will be set to the stored sequence number for the latest
|
||||
// *seq will be set to the stored sequence number for the latest
|
||||
// operation on this key or kMaxSequenceNumber if unknown.
|
||||
// If no key is found, *found_record_for_key will be set to false.
|
||||
//
|
||||
@@ -249,16 +252,18 @@ class DBImpl : public DB {
|
||||
//
|
||||
// Returns OK or NotFound on success,
|
||||
// other status on unexpected error.
|
||||
// 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);
|
||||
|
||||
using DB::AddFile;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const ExternalSstFileInfo* file_info,
|
||||
bool move_file) override;
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file, bool skip_snapshot_check) override;
|
||||
virtual Status AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path, bool move_file) override;
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file, bool skip_snapshot_check) override;
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
@@ -296,7 +301,8 @@ class DBImpl : public DB {
|
||||
bool disallow_trivial_move = false);
|
||||
|
||||
// Force current memtable contents to be flushed.
|
||||
Status TEST_FlushMemTable(bool wait = true);
|
||||
Status TEST_FlushMemTable(bool wait = true,
|
||||
ColumnFamilyHandle* cfh = nullptr);
|
||||
|
||||
// Wait for memtable compaction
|
||||
Status TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family = nullptr);
|
||||
@@ -337,20 +343,33 @@ class DBImpl : public DB {
|
||||
|
||||
uint64_t TEST_LogfileNumber();
|
||||
|
||||
uint64_t TEST_total_log_size() const { return total_log_size_; }
|
||||
|
||||
// Returns column family name to ImmutableCFOptions map.
|
||||
Status TEST_GetAllImmutableCFOptions(
|
||||
std::unordered_map<std::string, const ImmutableCFOptions*>* iopts_map);
|
||||
|
||||
// Return the lastest MutableCFOptions of of a column family
|
||||
Status TEST_GetLatestMutableCFOptions(ColumnFamilyHandle* column_family,
|
||||
MutableCFOptions* mutable_cf_opitons);
|
||||
|
||||
Cache* TEST_table_cache() { return table_cache_.get(); }
|
||||
|
||||
WriteController& TEST_write_controler() { return write_controller_; }
|
||||
|
||||
uint64_t TEST_FindMinLogContainingOutstandingPrep();
|
||||
uint64_t TEST_FindMinPrepLogReferencedByMemTable();
|
||||
|
||||
#endif // NDEBUG
|
||||
|
||||
// Return maximum background compaction alowed to be scheduled based on
|
||||
// Return maximum background compaction allowed to be scheduled based on
|
||||
// compaction status.
|
||||
int BGCompactionsAllowed() const;
|
||||
|
||||
// move logs pending closing from job_context to the DB queue and
|
||||
// schedule a purge
|
||||
void ScheduleBgLogWriterClose(JobContext* job_context);
|
||||
|
||||
// Returns the list of live files in 'live' and the list
|
||||
// of all files in the filesystem in 'candidate_files'.
|
||||
// If force == false and the last call was less than
|
||||
@@ -363,7 +382,10 @@ class DBImpl : public DB {
|
||||
// belong to live files are posibly 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 PurgeObsoleteFiles(const JobContext& background_contet);
|
||||
void PurgeObsoleteFiles(const JobContext& background_contet,
|
||||
bool schedule_only = false);
|
||||
|
||||
void SchedulePurge();
|
||||
|
||||
ColumnFamilyHandle* DefaultColumnFamily() const override;
|
||||
|
||||
@@ -421,12 +443,75 @@ class DBImpl : public DB {
|
||||
return num_running_compactions_;
|
||||
}
|
||||
|
||||
// hollow transactions shell used for recovery.
|
||||
// these will then be passed to TransactionDB so that
|
||||
// locks can be reacquired before writing can resume.
|
||||
struct RecoveredTransaction {
|
||||
uint64_t log_number_;
|
||||
std::string name_;
|
||||
WriteBatch* batch_;
|
||||
explicit RecoveredTransaction(const uint64_t log, const std::string& name,
|
||||
WriteBatch* batch)
|
||||
: log_number_(log), name_(name), batch_(batch) {}
|
||||
|
||||
~RecoveredTransaction() { delete batch_; }
|
||||
};
|
||||
|
||||
bool allow_2pc() const { return db_options_.allow_2pc; }
|
||||
|
||||
std::unordered_map<std::string, RecoveredTransaction*>
|
||||
recovered_transactions() {
|
||||
return recovered_transactions_;
|
||||
}
|
||||
|
||||
RecoveredTransaction* GetRecoveredTransaction(const std::string& name) {
|
||||
auto it = recovered_transactions_.find(name);
|
||||
if (it == recovered_transactions_.end()) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void InsertRecoveredTransaction(const uint64_t log, const std::string& name,
|
||||
WriteBatch* batch) {
|
||||
recovered_transactions_[name] = new RecoveredTransaction(log, name, batch);
|
||||
MarkLogAsContainingPrepSection(log);
|
||||
}
|
||||
|
||||
void DeleteRecoveredTransaction(const std::string& name) {
|
||||
auto it = recovered_transactions_.find(name);
|
||||
assert(it != recovered_transactions_.end());
|
||||
auto* trx = it->second;
|
||||
recovered_transactions_.erase(it);
|
||||
MarkLogAsHavingPrepSectionFlushed(trx->log_number_);
|
||||
delete trx;
|
||||
}
|
||||
|
||||
void DeleteAllRecoveredTransactions() {
|
||||
for (auto it = recovered_transactions_.begin();
|
||||
it != recovered_transactions_.end(); it++) {
|
||||
delete it->second;
|
||||
}
|
||||
recovered_transactions_.clear();
|
||||
}
|
||||
|
||||
void MarkLogAsHavingPrepSectionFlushed(uint64_t log);
|
||||
void MarkLogAsContainingPrepSection(uint64_t log);
|
||||
void AddToLogsToFreeQueue(log::Writer* log_writer) {
|
||||
logs_to_free_queue_.push_back(log_writer);
|
||||
}
|
||||
|
||||
Status NewDB();
|
||||
|
||||
protected:
|
||||
Env* const env_;
|
||||
const std::string dbname_;
|
||||
unique_ptr<VersionSet> versions_;
|
||||
const DBOptions db_options_;
|
||||
Statistics* stats_;
|
||||
std::unordered_map<std::string, RecoveredTransaction*>
|
||||
recovered_transactions_;
|
||||
|
||||
InternalIterator* NewInternalIterator(const ReadOptions&,
|
||||
ColumnFamilyData* cfd,
|
||||
@@ -452,6 +537,8 @@ class DBImpl : public DB {
|
||||
Compaction *c, const Status &st,
|
||||
const CompactionJobStats& job_stats,
|
||||
int job_id);
|
||||
void NotifyOnMemTableSealed(ColumnFamilyData* cfd,
|
||||
const MemTableInfo& mem_table_info);
|
||||
|
||||
void NewThreadStatusCfInfo(ColumnFamilyData* cfd) const;
|
||||
|
||||
@@ -460,11 +547,17 @@ class DBImpl : public DB {
|
||||
void EraseThreadStatusDbInfo() const;
|
||||
|
||||
Status WriteImpl(const WriteOptions& options, WriteBatch* updates,
|
||||
WriteCallback* callback);
|
||||
WriteCallback* callback = nullptr,
|
||||
uint64_t* log_used = nullptr, uint64_t log_ref = 0,
|
||||
bool disable_memtable = false);
|
||||
|
||||
uint64_t FindMinLogContainingOutstandingPrep();
|
||||
uint64_t FindMinPrepLogReferencedByMemTable();
|
||||
|
||||
private:
|
||||
friend class DB;
|
||||
friend class InternalStats;
|
||||
friend class TransactionImpl;
|
||||
#ifndef ROCKSDB_LITE
|
||||
friend class ForwardIterator;
|
||||
#endif
|
||||
@@ -477,13 +570,14 @@ class DBImpl : public DB {
|
||||
|
||||
struct WriteContext;
|
||||
|
||||
Status NewDB();
|
||||
struct PurgeFileInfo;
|
||||
|
||||
// Recover the descriptor from persistent storage. May do a significant
|
||||
// amount of work to recover recently logged updates. Any changes to
|
||||
// be made to the descriptor are added to *edit.
|
||||
Status Recover(const std::vector<ColumnFamilyDescriptor>& column_families,
|
||||
bool read_only = false, bool error_if_log_file_exist = false);
|
||||
bool read_only = false, bool error_if_log_file_exist = false,
|
||||
bool error_if_data_exists_in_logs = false);
|
||||
|
||||
void MaybeIgnoreError(Status* s) const;
|
||||
|
||||
@@ -491,6 +585,10 @@ 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);
|
||||
|
||||
// Background process needs to call
|
||||
// auto x = CaptureCurrentFileNumberInPendingOutputs()
|
||||
@@ -511,6 +609,8 @@ class DBImpl : public DB {
|
||||
// and blocked by any other pending_outputs_ calls)
|
||||
void ReleaseFileNumberFromPendingOutputs(std::list<uint64_t>::iterator v);
|
||||
|
||||
Status SyncClosedLogs(JobContext* job_context);
|
||||
|
||||
// Flush the in-memory write buffer to storage. Switches to a new
|
||||
// log-file/memtable and writes a new descriptor iff successful.
|
||||
Status FlushMemTableToOutputFile(ColumnFamilyData* cfd,
|
||||
@@ -520,10 +620,10 @@ class DBImpl : public DB {
|
||||
|
||||
// REQUIRES: log_numbers are sorted in ascending order
|
||||
Status RecoverLogFiles(const std::vector<uint64_t>& log_numbers,
|
||||
SequenceNumber* max_sequence, bool read_only);
|
||||
SequenceNumber* next_sequence, bool read_only);
|
||||
|
||||
// The following two methods are used to flush a memtable to
|
||||
// storage. The first one is used atdatabase RecoveryTime (when the
|
||||
// storage. The first one is used at database RecoveryTime (when the
|
||||
// database is opened) and is heavyweight because it holds the mutex
|
||||
// for the entire period. The second method WriteLevel0Table supports
|
||||
// concurrent flush memtables to storage.
|
||||
@@ -544,15 +644,30 @@ class DBImpl : public DB {
|
||||
// Wait for memtable flushed
|
||||
Status WaitForFlushMemTable(ColumnFamilyData* cfd);
|
||||
|
||||
void RecordFlushIOStats();
|
||||
void RecordCompactionIOStats();
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status CompactFilesImpl(
|
||||
const CompactionOptions& compact_options, ColumnFamilyData* cfd,
|
||||
Version* version, const std::vector<std::string>& input_file_names,
|
||||
const int output_level, int output_path_id, JobContext* job_context,
|
||||
LogBuffer* log_buffer);
|
||||
// Finds the lowest level in the DB that the ingested file can be added to
|
||||
// REQUIRES: mutex_ held
|
||||
int PickLevelForIngestedFile(ColumnFamilyData* cfd,
|
||||
const ExternalSstFileInfo& file_info);
|
||||
|
||||
// Wait for current AddFile() calls to finish.
|
||||
// REQUIRES: mutex_ held
|
||||
void WaitForAddFile();
|
||||
|
||||
Status CompactFilesImpl(const CompactionOptions& compact_options,
|
||||
ColumnFamilyData* cfd, Version* version,
|
||||
const std::vector<std::string>& input_file_names,
|
||||
const int output_level, int output_path_id,
|
||||
JobContext* job_context, LogBuffer* log_buffer);
|
||||
|
||||
Status ReadExternalSstFileInfo(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
ExternalSstFileInfo* file_info);
|
||||
|
||||
#else
|
||||
// AddFile is not supported in ROCKSDB_LITE so this function
|
||||
// will be no-op
|
||||
void WaitForAddFile() {}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
ColumnFamilyData* GetColumnFamilyDataByName(const std::string& cf_name);
|
||||
@@ -560,11 +675,15 @@ class DBImpl : public DB {
|
||||
void MaybeScheduleFlushOrCompaction();
|
||||
void SchedulePendingFlush(ColumnFamilyData* cfd);
|
||||
void SchedulePendingCompaction(ColumnFamilyData* cfd);
|
||||
void SchedulePendingPurge(std::string fname, FileType type, uint64_t number,
|
||||
uint32_t path_id, int job_id);
|
||||
static void BGWorkCompaction(void* arg);
|
||||
static void BGWorkFlush(void* db);
|
||||
static void BGWorkPurge(void* arg);
|
||||
static void UnscheduleCallback(void* arg);
|
||||
void BackgroundCallCompaction(void* arg);
|
||||
void BackgroundCallFlush();
|
||||
void BackgroundCallPurge();
|
||||
Status BackgroundCompaction(bool* madeProgress, JobContext* job_context,
|
||||
LogBuffer* log_buffer, void* m = 0);
|
||||
Status BackgroundFlush(bool* madeProgress, JobContext* job_context,
|
||||
@@ -615,9 +734,10 @@ class DBImpl : public DB {
|
||||
// * if AnyManualCompaction, whenever a compaction finishes, even if it hasn't
|
||||
// made any progress
|
||||
// * whenever a compaction made any progress
|
||||
// * whenever bg_flush_scheduled_ value decreases (i.e. whenever a flush is
|
||||
// done, even if it didn't make any progress)
|
||||
// * whenever there is an error in background flush or compaction
|
||||
// * whenever bg_flush_scheduled_ or bg_purge_scheduled_ value decreases
|
||||
// (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_addfile_ goes to 0.
|
||||
InstrumentedCondVar bg_cv_;
|
||||
uint64_t logfile_number_;
|
||||
std::deque<uint64_t>
|
||||
@@ -710,7 +830,7 @@ class DBImpl : public DB {
|
||||
|
||||
Directories directories_;
|
||||
|
||||
WriteBuffer write_buffer_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
|
||||
WriteThread write_thread_;
|
||||
|
||||
@@ -737,6 +857,19 @@ class DBImpl : public DB {
|
||||
// State is protected with db mutex.
|
||||
std::list<uint64_t> pending_outputs_;
|
||||
|
||||
// PurgeFileInfo is a structure to hold information of files to be deleted in
|
||||
// purge_queue_
|
||||
struct PurgeFileInfo {
|
||||
std::string fname;
|
||||
FileType type;
|
||||
uint64_t number;
|
||||
uint32_t path_id;
|
||||
int job_id;
|
||||
PurgeFileInfo(std::string fn, FileType t, uint64_t num, uint32_t pid,
|
||||
int jid)
|
||||
: fname(fn), type(t), number(num), path_id(pid), job_id(jid) {}
|
||||
};
|
||||
|
||||
// flush_queue_ and compaction_queue_ hold column families that we need to
|
||||
// flush and compact, respectively.
|
||||
// A column family is inserted into flush_queue_ when it satisfies condition
|
||||
@@ -761,6 +894,12 @@ class DBImpl : public DB {
|
||||
// invariant(column family present in compaction_queue_ <==>
|
||||
// ColumnFamilyData::pending_compaction_ == true)
|
||||
std::deque<ColumnFamilyData*> compaction_queue_;
|
||||
|
||||
// A queue to store filenames of the files to be purged
|
||||
std::deque<PurgeFileInfo> purge_queue_;
|
||||
|
||||
// A queue to store log writers to close
|
||||
std::deque<log::Writer*> logs_to_free_queue_;
|
||||
int unscheduled_flushes_;
|
||||
int unscheduled_compactions_;
|
||||
|
||||
@@ -776,6 +915,9 @@ class DBImpl : public DB {
|
||||
// stores the number of flushes are currently running
|
||||
int num_running_flushes_;
|
||||
|
||||
// number of background obsolete file purge jobs, submitted to the HIGH pool
|
||||
int bg_purge_scheduled_;
|
||||
|
||||
// Information for a manual compaction
|
||||
struct ManualCompaction {
|
||||
ColumnFamilyData* cfd;
|
||||
@@ -837,6 +979,14 @@ class DBImpl : public DB {
|
||||
// The options to access storage files
|
||||
const EnvOptions env_options_;
|
||||
|
||||
// A set of compactions that are running right now
|
||||
// REQUIRES: mutex held
|
||||
std::unordered_set<Compaction*> running_compactions_;
|
||||
|
||||
// Number of running AddFile() calls.
|
||||
// REQUIRES: mutex held
|
||||
int num_running_addfile_;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
WalManager wal_manager_;
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -856,6 +1006,28 @@ class DBImpl : public DB {
|
||||
// Indicate DB was opened successfully
|
||||
bool opened_successfully_;
|
||||
|
||||
// minmum log number 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_;
|
||||
|
||||
// to be used in conjunction with min_log_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
|
||||
//
|
||||
// when trying to determine the minmum 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.
|
||||
std::unordered_map<uint64_t, uint64_t> prepared_section_completed_;
|
||||
std::mutex prep_heap_mutex_;
|
||||
|
||||
// No copying allowed
|
||||
DBImpl(const DBImpl&);
|
||||
void operator=(const DBImpl&);
|
||||
|
||||
@@ -0,0 +1,417 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same directory.
|
||||
|
||||
#include "db/db_impl.h"
|
||||
|
||||
#ifndef __STDC_FORMAT_MACROS
|
||||
#define __STDC_FORMAT_MACROS
|
||||
#endif
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "db/builder.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "table/table_builder.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/file_util.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
Status DBImpl::ReadExternalSstFileInfo(ColumnFamilyHandle* column_family,
|
||||
const std::string& file_path,
|
||||
ExternalSstFileInfo* file_info) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
auto cfd = cfh->cfd();
|
||||
|
||||
file_info->file_path = file_path;
|
||||
status = env_->GetFileSize(file_path, &file_info->file_size);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Access the file using TableReader to extract
|
||||
// version, number of entries, smallest user key, largest user key
|
||||
std::unique_ptr<RandomAccessFile> sst_file;
|
||||
status = env_->NewRandomAccessFile(file_path, &sst_file, env_options_);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
std::unique_ptr<RandomAccessFileReader> sst_file_reader;
|
||||
sst_file_reader.reset(new RandomAccessFileReader(std::move(sst_file)));
|
||||
|
||||
std::unique_ptr<TableReader> table_reader;
|
||||
status = cfd->ioptions()->table_factory->NewTableReader(
|
||||
TableReaderOptions(*cfd->ioptions(), env_options_,
|
||||
cfd->internal_comparator()),
|
||||
std::move(sst_file_reader), file_info->file_size, &table_reader);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Get the external sst file version from table properties
|
||||
const UserCollectedProperties& user_collected_properties =
|
||||
table_reader->GetTableProperties()->user_collected_properties;
|
||||
UserCollectedProperties::const_iterator external_sst_file_version_iter =
|
||||
user_collected_properties.find(ExternalSstFilePropertyNames::kVersion);
|
||||
if (external_sst_file_version_iter == user_collected_properties.end()) {
|
||||
return Status::InvalidArgument("Generated table version not found");
|
||||
}
|
||||
|
||||
file_info->version =
|
||||
DecodeFixed32(external_sst_file_version_iter->second.c_str());
|
||||
if (file_info->version == 1) {
|
||||
// version 1 imply that all sequence numbers in table equal 0
|
||||
file_info->sequence_number = 0;
|
||||
} else {
|
||||
return Status::InvalidArgument("Generated table version is not supported");
|
||||
}
|
||||
// Get number of entries in table
|
||||
file_info->num_entries = table_reader->GetTableProperties()->num_entries;
|
||||
|
||||
ParsedInternalKey key;
|
||||
std::unique_ptr<InternalIterator> iter(
|
||||
table_reader->NewIterator(ReadOptions()));
|
||||
|
||||
// Get first (smallest) key from file
|
||||
iter->SeekToFirst();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("Generated table have non zero sequence number");
|
||||
}
|
||||
file_info->smallest_key = key.user_key.ToString();
|
||||
|
||||
// Get last (largest) key from file
|
||||
iter->SeekToLast();
|
||||
if (!ParseInternalKey(iter->key(), &key)) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
if (key.sequence != 0) {
|
||||
return Status::Corruption("Generated table have non zero sequence number");
|
||||
}
|
||||
file_info->largest_key = key.user_key.ToString();
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status DBImpl::AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<std::string>& file_path_list,
|
||||
bool move_file, bool skip_snapshot_check) {
|
||||
Status status;
|
||||
auto num_files = file_path_list.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
}
|
||||
|
||||
std::vector<ExternalSstFileInfo> file_info_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
status = ReadExternalSstFileInfo(column_family, file_path_list[i],
|
||||
&file_info_list[i]);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
return AddFile(column_family, file_info_list, move_file, skip_snapshot_check);
|
||||
}
|
||||
|
||||
Status DBImpl::AddFile(ColumnFamilyHandle* column_family,
|
||||
const std::vector<ExternalSstFileInfo>& file_info_list,
|
||||
bool move_file, bool skip_snapshot_check) {
|
||||
Status status;
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
ColumnFamilyData* cfd = cfh->cfd();
|
||||
const Comparator* user_cmp = cfd->internal_comparator().user_comparator();
|
||||
|
||||
auto num_files = file_info_list.size();
|
||||
if (num_files == 0) {
|
||||
return Status::InvalidArgument("The list of files is empty");
|
||||
}
|
||||
|
||||
// Verify that passed files dont have overlapping ranges
|
||||
if (num_files > 1) {
|
||||
std::vector<const ExternalSstFileInfo*> sorted_file_info_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
sorted_file_info_list[i] = &file_info_list[i];
|
||||
}
|
||||
|
||||
std::sort(sorted_file_info_list.begin(), sorted_file_info_list.end(),
|
||||
[&user_cmp, &file_info_list](const ExternalSstFileInfo* info1,
|
||||
const ExternalSstFileInfo* info2) {
|
||||
return user_cmp->Compare(info1->smallest_key,
|
||||
info2->smallest_key) < 0;
|
||||
});
|
||||
|
||||
for (size_t i = 0; i < num_files - 1; i++) {
|
||||
if (user_cmp->Compare(sorted_file_info_list[i]->largest_key,
|
||||
sorted_file_info_list[i + 1]->smallest_key) >= 0) {
|
||||
return Status::NotSupported("Files have overlapping ranges");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint64_t> micro_list(num_files, 0);
|
||||
std::vector<FileMetaData> meta_list(num_files);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[i], false);
|
||||
if (file_info_list[i].num_entries == 0) {
|
||||
return Status::InvalidArgument("File contain no entries");
|
||||
}
|
||||
if (file_info_list[i].version != 1) {
|
||||
return Status::InvalidArgument(
|
||||
"Generated table version is not supported");
|
||||
}
|
||||
// version 1 imply that file have only Put Operations with Sequence Number =
|
||||
// 0
|
||||
|
||||
meta_list[i].smallest =
|
||||
InternalKey(file_info_list[i].smallest_key,
|
||||
file_info_list[i].sequence_number, ValueType::kTypeValue);
|
||||
meta_list[i].largest =
|
||||
InternalKey(file_info_list[i].largest_key,
|
||||
file_info_list[i].sequence_number, ValueType::kTypeValue);
|
||||
if (!meta_list[i].smallest.Valid() || !meta_list[i].largest.Valid()) {
|
||||
return Status::Corruption("Generated table have corrupted keys");
|
||||
}
|
||||
meta_list[i].smallest_seqno = file_info_list[i].sequence_number;
|
||||
meta_list[i].largest_seqno = file_info_list[i].sequence_number;
|
||||
if (meta_list[i].smallest_seqno != 0 || meta_list[i].largest_seqno != 0) {
|
||||
return Status::InvalidArgument(
|
||||
"Non zero sequence numbers are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::list<uint64_t>::iterator> pending_outputs_inserted_elem_list(
|
||||
num_files);
|
||||
// Generate locations for the new tables
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[i], false);
|
||||
pending_outputs_inserted_elem_list[i] =
|
||||
CaptureCurrentFileNumberInPendingOutputs();
|
||||
meta_list[i].fd = FileDescriptor(versions_->NewFileNumber(), 0,
|
||||
file_info_list[i].file_size);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy/Move external files into DB
|
||||
std::vector<std::string> db_fname_list(num_files);
|
||||
size_t j = 0;
|
||||
for (; j < num_files; j++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[j], false);
|
||||
db_fname_list[j] =
|
||||
TableFileName(db_options_.db_paths, meta_list[j].fd.GetNumber(),
|
||||
meta_list[j].fd.GetPathId());
|
||||
if (move_file) {
|
||||
status = env_->LinkFile(file_info_list[j].file_path, db_fname_list[j]);
|
||||
if (status.IsNotSupported()) {
|
||||
// Original file is on a different FS, use copy instead of hard linking
|
||||
status =
|
||||
CopyFile(env_, file_info_list[j].file_path, db_fname_list[j], 0);
|
||||
}
|
||||
} else {
|
||||
status = CopyFile(env_, file_info_list[j].file_path, db_fname_list[j], 0);
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:FileCopied");
|
||||
if (!status.ok()) {
|
||||
for (size_t i = 0; i < j; i++) {
|
||||
Status s = env_->DeleteFile(db_fname_list[i]);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
db_fname_list[i].c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:MutexLock");
|
||||
|
||||
const MutableCFOptions mutable_cf_options =
|
||||
*cfd->GetLatestMutableCFOptions();
|
||||
|
||||
WriteThread::Writer w;
|
||||
write_thread_.EnterUnbatched(&w, &mutex_);
|
||||
|
||||
num_running_addfile_++;
|
||||
|
||||
if (!skip_snapshot_check && !snapshots_.empty()) {
|
||||
// Check that no snapshots are being held
|
||||
status =
|
||||
Status::NotSupported("Cannot add a file while holding snapshots");
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
// Verify that added file key range dont overlap with any keys in DB
|
||||
SuperVersion* sv = cfd->GetSuperVersion()->Ref();
|
||||
Arena arena;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
ScopedArenaIterator iter(NewInternalIterator(ro, cfd, sv, &arena));
|
||||
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[i], false);
|
||||
InternalKey range_start(file_info_list[i].smallest_key,
|
||||
kMaxSequenceNumber, kTypeValue);
|
||||
iter->Seek(range_start.Encode());
|
||||
status = iter->status();
|
||||
|
||||
if (status.ok() && iter->Valid()) {
|
||||
ParsedInternalKey seek_result;
|
||||
if (ParseInternalKey(iter->key(), &seek_result)) {
|
||||
if (user_cmp->Compare(seek_result.user_key,
|
||||
file_info_list[i].largest_key) <= 0) {
|
||||
status = Status::NotSupported("Cannot add overlapping range");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
status = Status::Corruption("DB have corrupted keys");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The levels that the files will be ingested into
|
||||
std::vector<int> target_level_list(num_files, 0);
|
||||
if (status.ok()) {
|
||||
VersionEdit edit;
|
||||
edit.SetColumnFamily(cfd->GetID());
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
StopWatch sw(env_, nullptr, 0, µ_list[i], false);
|
||||
// Add file to the lowest possible level
|
||||
target_level_list[i] = PickLevelForIngestedFile(cfd, file_info_list[i]);
|
||||
edit.AddFile(target_level_list[i], meta_list[i].fd.GetNumber(),
|
||||
meta_list[i].fd.GetPathId(), meta_list[i].fd.GetFileSize(),
|
||||
meta_list[i].smallest, meta_list[i].largest,
|
||||
meta_list[i].smallest_seqno, meta_list[i].largest_seqno,
|
||||
meta_list[i].marked_for_compaction);
|
||||
}
|
||||
status = versions_->LogAndApply(cfd, mutable_cf_options, &edit, &mutex_,
|
||||
directories_.GetDbDir());
|
||||
}
|
||||
write_thread_.ExitUnbatched(&w);
|
||||
|
||||
if (status.ok()) {
|
||||
delete InstallSuperVersionAndScheduleWork(cfd, nullptr,
|
||||
mutable_cf_options);
|
||||
}
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
// Update internal stats
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = micro_list[i];
|
||||
stats.bytes_written = meta_list[i].fd.GetFileSize();
|
||||
stats.num_output_files = 1;
|
||||
cfd->internal_stats()->AddCompactionStats(target_level_list[i], stats);
|
||||
cfd->internal_stats()->AddCFStats(InternalStats::BYTES_INGESTED_ADD_FILE,
|
||||
meta_list[i].fd.GetFileSize());
|
||||
ReleaseFileNumberFromPendingOutputs(
|
||||
pending_outputs_inserted_elem_list[i]);
|
||||
}
|
||||
|
||||
num_running_addfile_--;
|
||||
if (num_running_addfile_ == 0) {
|
||||
bg_cv_.SignalAll();
|
||||
}
|
||||
TEST_SYNC_POINT("DBImpl::AddFile:MutexUnlock");
|
||||
} // mutex_ is unlocked here;
|
||||
|
||||
if (!status.ok()) {
|
||||
// We failed to add the files to the database
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
Status s = env_->DeleteFile(db_fname_list[i]);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"AddFile() clean up for file %s failed : %s",
|
||||
db_fname_list[i].c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
} else if (status.ok() && move_file) {
|
||||
// The files were moved and added successfully, remove original file links
|
||||
for (size_t i = 0; i < num_files; i++) {
|
||||
Status s = env_->DeleteFile(file_info_list[i].file_path);
|
||||
if (!s.ok()) {
|
||||
Log(InfoLogLevel::WARN_LEVEL, db_options_.info_log,
|
||||
"%s was added to DB successfully but failed to remove original "
|
||||
"file "
|
||||
"link : %s",
|
||||
file_info_list[i].file_path.c_str(), s.ToString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// Finds the lowest level in the DB that the ingested file can be added to
|
||||
int DBImpl::PickLevelForIngestedFile(ColumnFamilyData* cfd,
|
||||
const ExternalSstFileInfo& file_info) {
|
||||
mutex_.AssertHeld();
|
||||
|
||||
int target_level = 0;
|
||||
auto* vstorage = cfd->current()->storage_info();
|
||||
auto* ucmp = vstorage->InternalComparator()->user_comparator();
|
||||
|
||||
Slice file_smallest_user_key(file_info.smallest_key);
|
||||
Slice file_largest_user_key(file_info.largest_key);
|
||||
|
||||
for (int lvl = cfd->NumberLevels() - 1; lvl >= vstorage->base_level();
|
||||
lvl--) {
|
||||
if (vstorage->OverlapInLevel(lvl, &file_smallest_user_key,
|
||||
&file_largest_user_key) == false) {
|
||||
// Make sure that the file dont overlap with the output of any
|
||||
// compaction running right now
|
||||
Slice compaction_smallest_user_key;
|
||||
Slice compaction_largest_user_key;
|
||||
bool overlap_with_compaction_output = false;
|
||||
for (Compaction* c : running_compactions_) {
|
||||
if (c->column_family_data()->GetID() != cfd->GetID() ||
|
||||
c->output_level() != lvl) {
|
||||
continue;
|
||||
}
|
||||
|
||||
compaction_smallest_user_key = c->GetSmallestUserKey();
|
||||
compaction_largest_user_key = c->GetLargestUserKey();
|
||||
|
||||
if (ucmp->Compare(file_smallest_user_key,
|
||||
compaction_largest_user_key) <= 0 &&
|
||||
ucmp->Compare(file_largest_user_key,
|
||||
compaction_smallest_user_key) >= 0) {
|
||||
overlap_with_compaction_output = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (overlap_with_compaction_output == false) {
|
||||
// Level lvl is the lowest level that dont have any files with key
|
||||
// range overlapping with our file key range and no compactions
|
||||
// planning to add overlapping files in it.
|
||||
target_level = lvl;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return target_level;
|
||||
}
|
||||
|
||||
void DBImpl::WaitForAddFile() {
|
||||
mutex_.AssertHeld();
|
||||
while (num_running_addfile_ > 0) {
|
||||
bg_cv_.Wait();
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
+26
-2
@@ -74,10 +74,17 @@ Status DBImpl::TEST_CompactRange(int level, const Slice* begin,
|
||||
disallow_trivial_move);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait) {
|
||||
Status DBImpl::TEST_FlushMemTable(bool wait, ColumnFamilyHandle* cfh) {
|
||||
FlushOptions fo;
|
||||
fo.wait = wait;
|
||||
return FlushMemTable(default_cf_handle_->cfd(), fo);
|
||||
ColumnFamilyData* cfd;
|
||||
if (cfh == nullptr) {
|
||||
cfd = default_cf_handle_->cfd();
|
||||
} else {
|
||||
auto cfhi = reinterpret_cast<ColumnFamilyHandleImpl*>(cfh);
|
||||
cfd = cfhi->cfd();
|
||||
}
|
||||
return FlushMemTable(cfd, fo);
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_WaitForFlushMemTable(ColumnFamilyHandle* column_family) {
|
||||
@@ -154,5 +161,22 @@ Status DBImpl::TEST_GetAllImmutableCFOptions(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_FindMinLogContainingOutstandingPrep() {
|
||||
return FindMinLogContainingOutstandingPrep();
|
||||
}
|
||||
|
||||
uint64_t DBImpl::TEST_FindMinPrepLogReferencedByMemTable() {
|
||||
return FindMinPrepLogReferencedByMemTable();
|
||||
}
|
||||
|
||||
Status DBImpl::TEST_GetLatestMutableCFOptions(
|
||||
ColumnFamilyHandle* column_family, MutableCFOptions* mutable_cf_options) {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
|
||||
auto cfh = reinterpret_cast<ColumnFamilyHandleImpl*>(column_family);
|
||||
*mutable_cf_options = *cfh->cfd()->GetLatestMutableCFOptions();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
#endif // NDEBUG
|
||||
@@ -49,8 +49,7 @@ Status DBImpl::SuggestCompactRange(ColumnFamilyHandle* column_family,
|
||||
}
|
||||
// Since we have some more files to compact, we should also recompute
|
||||
// compaction score
|
||||
vstorage->ComputeCompactionScore(*cfd->GetLatestMutableCFOptions(),
|
||||
CompactionOptionsFIFO());
|
||||
vstorage->ComputeCompactionScore(*cfd->GetLatestMutableCFOptions());
|
||||
SchedulePendingCompaction(cfd);
|
||||
MaybeScheduleFlushOrCompaction();
|
||||
}
|
||||
|
||||
@@ -18,12 +18,11 @@ class DBTestInPlaceUpdate : public DBTestBase {
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdate) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller size
|
||||
@@ -41,12 +40,11 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdate) {
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateLargeNewValue) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 100000;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of larger size
|
||||
@@ -64,7 +62,7 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateLargeNewValue) {
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerSize) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
|
||||
@@ -72,7 +70,6 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerSize) {
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceSmallerSize;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller size
|
||||
@@ -92,7 +89,7 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerSize) {
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerVarintSize) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
|
||||
@@ -100,7 +97,6 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerVarintSize) {
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceSmallerVarintSize;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of smaller varint size
|
||||
@@ -120,7 +116,7 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackSmallerVarintSize) {
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackLargeNewValue) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
|
||||
@@ -128,7 +124,6 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackLargeNewValue) {
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceLargerSize;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Update key with values of larger size
|
||||
@@ -146,7 +141,7 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackLargeNewValue) {
|
||||
|
||||
TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackNoAction) {
|
||||
do {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.inplace_update_support = true;
|
||||
|
||||
@@ -154,7 +149,6 @@ TEST_F(DBTestInPlaceUpdate, InPlaceUpdateCallbackNoAction) {
|
||||
options.write_buffer_size = 100000;
|
||||
options.inplace_callback =
|
||||
rocksdb::DBTestInPlaceUpdate::updateInPlaceNoAction;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Callback function requests no actions from db
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same 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 "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBIOFailureTest : public DBTestBase {
|
||||
public:
|
||||
DBIOFailureTest() : DBTestBase("/db_io_failure_test") {}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Check that number of files does not grow when writes are dropped
|
||||
TEST_F(DBIOFailureTest, DropWrites) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.paranoid_checks = false;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_EQ("v1", Get("foo"));
|
||||
Compact("a", "z");
|
||||
const size_t num_files = CountFiles();
|
||||
// Force out-of-space errors
|
||||
env_->drop_writes_.store(true, std::memory_order_release);
|
||||
env_->sleep_counter_.Reset();
|
||||
env_->no_sleep_ = true;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
for (int level = 0; level < dbfull()->NumberLevels(); level++) {
|
||||
if (level > 0 && level == dbfull()->NumberLevels() - 1) {
|
||||
break;
|
||||
}
|
||||
dbfull()->TEST_CompactRange(level, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */);
|
||||
}
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
std::string property_value;
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("5", property_value);
|
||||
|
||||
env_->drop_writes_.store(false, std::memory_order_release);
|
||||
ASSERT_LT(CountFiles(), num_files + 3);
|
||||
|
||||
// Check that compaction attempts slept after errors
|
||||
// TODO @krad: Figure out why ASSERT_EQ 5 keeps failing in certain compiler
|
||||
// versions
|
||||
ASSERT_GE(env_->sleep_counter_.Read(), 4);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
// Check background error counter bumped on flush failures.
|
||||
TEST_F(DBIOFailureTest, DropWritesFlush) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.max_background_flushes = 1;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
// Force out-of-space errors
|
||||
env_->drop_writes_.store(true, std::memory_order_release);
|
||||
|
||||
std::string property_value;
|
||||
// Background error count is 0 now.
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("0", property_value);
|
||||
|
||||
dbfull()->TEST_FlushMemTable(true);
|
||||
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.background-errors", &property_value));
|
||||
ASSERT_EQ("1", property_value);
|
||||
|
||||
env_->drop_writes_.store(false, std::memory_order_release);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
// Check that CompactRange() returns failure if there is not enough space left
|
||||
// on device
|
||||
TEST_F(DBIOFailureTest, NoSpaceCompactRange) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
Reopen(options);
|
||||
|
||||
// generate 5 tables
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
ASSERT_OK(Put(Key(i), Key(i) + "v"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// Force out-of-space errors
|
||||
env_->no_space_.store(true, std::memory_order_release);
|
||||
|
||||
Status s = dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */);
|
||||
ASSERT_TRUE(s.IsIOError());
|
||||
ASSERT_TRUE(s.IsNoSpace());
|
||||
|
||||
env_->no_space_.store(false, std::memory_order_release);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBIOFailureTest, NonWritableFileSystem) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 4096;
|
||||
options.arena_block_size = 4096;
|
||||
options.env = env_;
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
env_->non_writeable_rate_.store(100);
|
||||
std::string big(100000, 'x');
|
||||
int errors = 0;
|
||||
for (int i = 0; i < 20; i++) {
|
||||
if (!Put("foo", big).ok()) {
|
||||
errors++;
|
||||
env_->SleepForMicroseconds(100000);
|
||||
}
|
||||
}
|
||||
ASSERT_GT(errors, 0);
|
||||
env_->non_writeable_rate_.store(0);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBIOFailureTest, ManifestWriteError) {
|
||||
// Test for the following problem:
|
||||
// (a) Compaction produces file F
|
||||
// (b) Log record containing F is written to MANIFEST file, but Sync() fails
|
||||
// (c) GC deletes F
|
||||
// (d) After reopening DB, reads fail since deleted F is named in log record
|
||||
|
||||
// We iterate twice. In the second iteration, everything is the
|
||||
// same except the log record never makes it to the MANIFEST file.
|
||||
for (int iter = 0; iter < 2; iter++) {
|
||||
std::atomic<bool>* error_type = (iter == 0) ? &env_->manifest_sync_error_
|
||||
: &env_->manifest_write_error_;
|
||||
|
||||
// Insert foo=>bar mapping
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "bar"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Memtable compaction (will succeed)
|
||||
Flush();
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
const int last = 2;
|
||||
MoveFilesToLevel(2);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(last), 1); // foo=>bar is now in last level
|
||||
|
||||
// Merging compaction (will fail)
|
||||
error_type->store(true, std::memory_order_release);
|
||||
dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
error_type->store(false, std::memory_order_release);
|
||||
|
||||
// Since paranoid_checks=true, writes should fail
|
||||
ASSERT_NOK(Put("foo2", "bar2"));
|
||||
|
||||
// Recovery: should not lose data
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Try again with paranoid_checks=false
|
||||
Close();
|
||||
options.paranoid_checks = false;
|
||||
Reopen(options);
|
||||
|
||||
// Merging compaction (will fail)
|
||||
error_type->store(true, std::memory_order_release);
|
||||
dbfull()->TEST_CompactRange(last, nullptr, nullptr); // Should fail
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Recovery: should not lose data
|
||||
error_type->store(false, std::memory_order_release);
|
||||
Reopen(options);
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
|
||||
// Since paranoid_checks=false, writes should succeed
|
||||
ASSERT_OK(Put("foo2", "bar2"));
|
||||
ASSERT_EQ("bar", Get("foo"));
|
||||
ASSERT_EQ("bar2", Get("foo2"));
|
||||
}
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBIOFailureTest, PutFailsParanoid) {
|
||||
// Test the following:
|
||||
// (a) A random put fails in paranoid mode (simulate by sync fail)
|
||||
// (b) All other puts have to fail, even if writes would succeed
|
||||
// (c) All of that should happen ONLY if paranoid_checks = true
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.error_if_exists = false;
|
||||
options.paranoid_checks = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
Status s;
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "foo1", "bar1"));
|
||||
// simulate error
|
||||
env_->log_write_error_.store(true, std::memory_order_release);
|
||||
s = Put(1, "foo2", "bar2");
|
||||
ASSERT_TRUE(!s.ok());
|
||||
env_->log_write_error_.store(false, std::memory_order_release);
|
||||
s = Put(1, "foo3", "bar3");
|
||||
// the next put should fail, too
|
||||
ASSERT_TRUE(!s.ok());
|
||||
// but we're still able to read
|
||||
ASSERT_EQ("bar", Get(1, "foo"));
|
||||
|
||||
// do the same thing with paranoid checks off
|
||||
options.paranoid_checks = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(1, "foo1", "bar1"));
|
||||
// simulate error
|
||||
env_->log_write_error_.store(true, std::memory_order_release);
|
||||
s = Put(1, "foo2", "bar2");
|
||||
ASSERT_TRUE(!s.ok());
|
||||
env_->log_write_error_.store(false, std::memory_order_release);
|
||||
s = Put(1, "foo3", "bar3");
|
||||
// the next put should NOT fail
|
||||
ASSERT_TRUE(s.ok());
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+156
-138
@@ -15,6 +15,9 @@
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/merge_context.h"
|
||||
#include "db/merge_helper.h"
|
||||
#include "db/pinned_iterators_manager.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -102,12 +105,12 @@ class DBIter: public Iterator {
|
||||
InternalIterator* iter, SequenceNumber s, bool arena_mode,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
const Slice* iterate_upper_bound = nullptr,
|
||||
bool prefix_same_as_start = false)
|
||||
bool prefix_same_as_start = false, bool pin_data = false)
|
||||
: arena_mode_(arena_mode),
|
||||
env_(env),
|
||||
logger_(ioptions.info_log),
|
||||
user_comparator_(cmp),
|
||||
user_merge_operator_(ioptions.merge_operator),
|
||||
merge_operator_(ioptions.merge_operator),
|
||||
iter_(iter),
|
||||
sequence_(s),
|
||||
direction_(kForward),
|
||||
@@ -117,12 +120,22 @@ class DBIter: public Iterator {
|
||||
version_number_(version_number),
|
||||
iterate_upper_bound_(iterate_upper_bound),
|
||||
prefix_same_as_start_(prefix_same_as_start),
|
||||
iter_pinned_(false) {
|
||||
pin_thru_lifetime_(pin_data) {
|
||||
RecordTick(statistics_, NO_ITERATORS);
|
||||
prefix_extractor_ = ioptions.prefix_extractor;
|
||||
max_skip_ = max_sequential_skip_in_iterations;
|
||||
if (pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
if (iter_) {
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
}
|
||||
virtual ~DBIter() {
|
||||
// Release pinned data if any
|
||||
if (pinned_iters_mgr_.PinningEnabled()) {
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
}
|
||||
RecordTick(statistics_, NO_ITERATORS, -1);
|
||||
local_stats_.BumpGlobalStatistics(statistics_);
|
||||
if (!arena_mode_) {
|
||||
@@ -134,9 +147,7 @@ class DBIter: public Iterator {
|
||||
virtual void SetIter(InternalIterator* iter) {
|
||||
assert(iter_ == nullptr);
|
||||
iter_ = iter;
|
||||
if (iter_ && iter_pinned_) {
|
||||
iter_->PinData();
|
||||
}
|
||||
iter_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
}
|
||||
virtual bool Valid() const override { return valid_; }
|
||||
virtual Slice key() const override {
|
||||
@@ -145,8 +156,15 @@ class DBIter: public Iterator {
|
||||
}
|
||||
virtual Slice value() const override {
|
||||
assert(valid_);
|
||||
return (direction_ == kForward && !current_entry_is_merged_) ?
|
||||
iter_->value() : saved_value_;
|
||||
if (current_entry_is_merged_) {
|
||||
// If pinned_value_ is set then the result of merge operator is one of
|
||||
// the merge operands and we should return it.
|
||||
return pinned_value_.data() ? pinned_value_ : saved_value_;
|
||||
} else if (direction_ == kReverse) {
|
||||
return pinned_value_;
|
||||
} else {
|
||||
return iter_->value();
|
||||
}
|
||||
}
|
||||
virtual Status status() const override {
|
||||
if (status_.ok()) {
|
||||
@@ -155,28 +173,6 @@ class DBIter: public Iterator {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
virtual Status PinData() {
|
||||
Status s;
|
||||
if (iter_) {
|
||||
s = iter_->PinData();
|
||||
}
|
||||
if (s.ok()) {
|
||||
// Even if iter_ is nullptr, we set iter_pinned_ to true so that when
|
||||
// iter_ is updated using SetIter, we Pin it.
|
||||
iter_pinned_ = true;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
virtual Status ReleasePinnedData() {
|
||||
Status s;
|
||||
if (iter_) {
|
||||
s = iter_->ReleasePinnedData();
|
||||
}
|
||||
if (s.ok()) {
|
||||
iter_pinned_ = false;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status GetProperty(std::string prop_name,
|
||||
std::string* prop) override {
|
||||
@@ -191,7 +187,7 @@ class DBIter: public Iterator {
|
||||
return Status::OK();
|
||||
} else if (prop_name == "rocksdb.iterator.is-key-pinned") {
|
||||
if (valid_) {
|
||||
*prop = (iter_pinned_ && saved_key_.IsKeyPinned()) ? "1" : "0";
|
||||
*prop = (pin_thru_lifetime_ && saved_key_.IsKeyPinned()) ? "1" : "0";
|
||||
} else {
|
||||
*prop = "Iterator is not valid.";
|
||||
}
|
||||
@@ -214,11 +210,26 @@ class DBIter: public Iterator {
|
||||
bool FindValueForCurrentKeyUsingSeek();
|
||||
void FindPrevUserKey();
|
||||
void FindNextUserKey();
|
||||
inline void FindNextUserEntry(bool skipping);
|
||||
void FindNextUserEntryInternal(bool skipping);
|
||||
inline void FindNextUserEntry(bool skipping, bool prefix_check);
|
||||
void FindNextUserEntryInternal(bool skipping, bool prefix_check);
|
||||
bool ParseKey(ParsedInternalKey* key);
|
||||
void MergeValuesNewToOld();
|
||||
|
||||
// Temporarily pin the blocks that we encounter until ReleaseTempPinnedData()
|
||||
// is called
|
||||
void TempPinData() {
|
||||
if (!pin_thru_lifetime_) {
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
}
|
||||
}
|
||||
|
||||
// Release blocks pinned by TempPinData()
|
||||
void ReleaseTempPinnedData() {
|
||||
if (!pin_thru_lifetime_ && pinned_iters_mgr_.PinningEnabled()) {
|
||||
pinned_iters_mgr_.ReleasePinnedData();
|
||||
}
|
||||
}
|
||||
|
||||
inline void ClearSavedValue() {
|
||||
if (saved_value_.capacity() > 1048576) {
|
||||
std::string empty;
|
||||
@@ -233,13 +244,14 @@ class DBIter: public Iterator {
|
||||
Env* const env_;
|
||||
Logger* logger_;
|
||||
const Comparator* const user_comparator_;
|
||||
const MergeOperator* const user_merge_operator_;
|
||||
const MergeOperator* const merge_operator_;
|
||||
InternalIterator* iter_;
|
||||
SequenceNumber const sequence_;
|
||||
|
||||
Status status_;
|
||||
IterKey saved_key_;
|
||||
std::string saved_value_;
|
||||
Slice pinned_value_;
|
||||
Direction direction_;
|
||||
bool valid_;
|
||||
bool current_entry_is_merged_;
|
||||
@@ -247,12 +259,16 @@ class DBIter: public Iterator {
|
||||
uint64_t max_skip_;
|
||||
uint64_t version_number_;
|
||||
const Slice* iterate_upper_bound_;
|
||||
IterKey prefix_start_;
|
||||
bool prefix_same_as_start_;
|
||||
bool iter_pinned_;
|
||||
IterKey prefix_start_buf_;
|
||||
Slice prefix_start_key_;
|
||||
const bool prefix_same_as_start_;
|
||||
// Means that we will pin all data blocks we read as long the Iterator
|
||||
// is not deleted, will be true if ReadOptions::pin_data is true
|
||||
const bool pin_thru_lifetime_;
|
||||
// List of operands for merge operator.
|
||||
std::deque<std::string> merge_operands_;
|
||||
MergeContext merge_context_;
|
||||
LocalStatistics local_stats_;
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
|
||||
// No copying allowed
|
||||
DBIter(const DBIter&);
|
||||
@@ -274,6 +290,8 @@ inline bool DBIter::ParseKey(ParsedInternalKey* ikey) {
|
||||
void DBIter::Next() {
|
||||
assert(valid_);
|
||||
|
||||
// Release temporarily pinned blocks from last operation
|
||||
ReleaseTempPinnedData();
|
||||
if (direction_ == kReverse) {
|
||||
FindNextUserKey();
|
||||
direction_ = kForward;
|
||||
@@ -299,12 +317,7 @@ void DBIter::Next() {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
FindNextUserEntry(true /* skipping the current user key */);
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_ &&
|
||||
prefix_extractor_->Transform(saved_key_.GetKey())
|
||||
.compare(prefix_start_.GetKey()) != 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
FindNextUserEntry(true /* skipping the current user key */, prefix_same_as_start_);
|
||||
if (statistics_ != nullptr && valid_) {
|
||||
local_stats_.next_found_count_++;
|
||||
local_stats_.bytes_read_ += (key().size() + value().size());
|
||||
@@ -319,13 +332,18 @@ void DBIter::Next() {
|
||||
//
|
||||
// NOTE: In between, saved_key_ can point to a user key that has
|
||||
// a delete marker
|
||||
inline void DBIter::FindNextUserEntry(bool skipping) {
|
||||
//
|
||||
// The prefix_check parameter controls whether we check the iterated
|
||||
// keys against the prefix of the seeked key. Set to false when
|
||||
// performing a seek without a key (e.g. SeekToFirst). Set to
|
||||
// prefix_same_as_start_ for other iterations.
|
||||
inline void DBIter::FindNextUserEntry(bool skipping, bool prefix_check) {
|
||||
PERF_TIMER_GUARD(find_next_user_entry_time);
|
||||
FindNextUserEntryInternal(skipping);
|
||||
FindNextUserEntryInternal(skipping, prefix_check);
|
||||
}
|
||||
|
||||
// Actual implementation of DBIter::FindNextUserEntry()
|
||||
void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
void DBIter::FindNextUserEntryInternal(bool skipping, bool prefix_check) {
|
||||
// Loop until we hit an acceptable entry to yield
|
||||
assert(iter_->Valid());
|
||||
assert(direction_ == kForward);
|
||||
@@ -340,6 +358,11 @@ void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (prefix_extractor_ && prefix_check &&
|
||||
prefix_extractor_->Transform(ikey.user_key).compare(prefix_start_key_) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (ikey.sequence <= sequence_) {
|
||||
if (skipping &&
|
||||
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0) {
|
||||
@@ -351,21 +374,24 @@ void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
case kTypeSingleDeletion:
|
||||
// Arrange to skip all upcoming entries for this key since
|
||||
// they are hidden by this deletion.
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
skipping = true;
|
||||
num_skipped = 0;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeValue:
|
||||
valid_ = true;
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
return;
|
||||
case kTypeMerge:
|
||||
// By now, we are sure the current ikey is going to yield a value
|
||||
saved_key_.SetKey(ikey.user_key,
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
saved_key_.SetKey(
|
||||
ikey.user_key,
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
current_entry_is_merged_ = true;
|
||||
valid_ = true;
|
||||
MergeValuesNewToOld(); // Go to a different state machine
|
||||
@@ -403,17 +429,20 @@ void DBIter::FindNextUserEntryInternal(bool skipping) {
|
||||
// POST: saved_value_ has the merged value for the user key
|
||||
// iter_ points to the next entry (or invalid)
|
||||
void DBIter::MergeValuesNewToOld() {
|
||||
if (!user_merge_operator_) {
|
||||
if (!merge_operator_) {
|
||||
Log(InfoLogLevel::ERROR_LEVEL,
|
||||
logger_, "Options::merge_operator is null.");
|
||||
status_ = Status::InvalidArgument("user_merge_operator_ must be set.");
|
||||
status_ = Status::InvalidArgument("merge_operator_ must be set.");
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Temporarily pin the blocks that hold merge operands
|
||||
TempPinData();
|
||||
merge_context_.Clear();
|
||||
// Start the merge process by pushing the first operand
|
||||
std::deque<std::string> operands;
|
||||
operands.push_front(iter_->value().ToString());
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
for (iter_->Next(); iter_->Valid(); iter_->Next()) {
|
||||
@@ -435,42 +464,34 @@ void DBIter::MergeValuesNewToOld() {
|
||||
// final result in saved_value_. We are done!
|
||||
// ignore corruption if there is any.
|
||||
const Slice val = iter_->value();
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(ikey.user_key, &val, operands,
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
MergeHelper::TimedFullMerge(merge_operator_, ikey.user_key, &val,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
// iter_ is positioned after put
|
||||
iter_->Next();
|
||||
return;
|
||||
} else if (kTypeMerge == ikey.type) {
|
||||
// hit a merge, add the value as an operand and run associative merge.
|
||||
// when complete, add result to operands and continue.
|
||||
const Slice& val = iter_->value();
|
||||
operands.push_front(val.ToString());
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
// we either exhausted all internal keys under this user key, or hit
|
||||
// a deletion marker.
|
||||
// feed null as the existing value to the merge operator, such that
|
||||
// client can differentiate this scenario and do things accordingly.
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr, operands,
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME, timer.ElapsedNanos());
|
||||
}
|
||||
// we either exhausted all internal keys under this user key, or hit
|
||||
// a deletion marker.
|
||||
// feed null as the existing value to the merge operator, such that
|
||||
// client can differentiate this scenario and do things accordingly.
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
}
|
||||
|
||||
void DBIter::Prev() {
|
||||
assert(valid_);
|
||||
ReleaseTempPinnedData();
|
||||
if (direction_ == kForward) {
|
||||
ReverseToBackward();
|
||||
}
|
||||
@@ -484,7 +505,7 @@ void DBIter::Prev() {
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_ &&
|
||||
prefix_extractor_->Transform(saved_key_.GetKey())
|
||||
.compare(prefix_start_.GetKey()) != 0) {
|
||||
.compare(prefix_start_key_) != 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
@@ -526,7 +547,7 @@ void DBIter::PrevInternal() {
|
||||
|
||||
while (iter_->Valid()) {
|
||||
saved_key_.SetKey(ExtractUserKey(iter_->key()),
|
||||
!iter_->IsKeyPinned() /* copy */);
|
||||
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
|
||||
if (FindValueForCurrentKey()) {
|
||||
valid_ = true;
|
||||
if (!iter_->Valid()) {
|
||||
@@ -556,7 +577,8 @@ void DBIter::PrevInternal() {
|
||||
// saved_value_
|
||||
bool DBIter::FindValueForCurrentKey() {
|
||||
assert(iter_->Valid());
|
||||
merge_operands_.clear();
|
||||
merge_context_.Clear();
|
||||
current_entry_is_merged_ = false;
|
||||
// last entry before merge (could be kTypeDeletion, kTypeSingleDeletion or
|
||||
// kTypeValue)
|
||||
ValueType last_not_merge_type = kTypeDeletion;
|
||||
@@ -565,6 +587,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
ParsedInternalKey ikey;
|
||||
FindParseableKey(&ikey, kReverse);
|
||||
|
||||
// Temporarily pin blocks that hold (merge operands / the value)
|
||||
ReleaseTempPinnedData();
|
||||
TempPinData();
|
||||
size_t num_skipped = 0;
|
||||
while (iter_->Valid() && ikey.sequence <= sequence_ &&
|
||||
user_comparator_->Equal(ikey.user_key, saved_key_.GetKey())) {
|
||||
@@ -576,19 +601,21 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
last_key_entry_type = ikey.type;
|
||||
switch (last_key_entry_type) {
|
||||
case kTypeValue:
|
||||
merge_operands_.clear();
|
||||
saved_value_ = iter_->value().ToString();
|
||||
merge_context_.Clear();
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
last_not_merge_type = kTypeValue;
|
||||
break;
|
||||
case kTypeDeletion:
|
||||
case kTypeSingleDeletion:
|
||||
merge_operands_.clear();
|
||||
merge_context_.Clear();
|
||||
last_not_merge_type = last_key_entry_type;
|
||||
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
|
||||
break;
|
||||
case kTypeMerge:
|
||||
assert(user_merge_operator_ != nullptr);
|
||||
merge_operands_.push_back(iter_->value().ToString());
|
||||
assert(merge_operator_ != nullptr);
|
||||
merge_context_.PushOperandBack(
|
||||
iter_->value(), iter_->IsValuePinned() /* operand_pinned */);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
@@ -607,27 +634,18 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
valid_ = false;
|
||||
return false;
|
||||
case kTypeMerge:
|
||||
current_entry_is_merged_ = true;
|
||||
if (last_not_merge_type == kTypeDeletion) {
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr,
|
||||
merge_operands_, &saved_value_,
|
||||
logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(),
|
||||
nullptr, merge_context_.GetOperands(),
|
||||
&saved_value_, logger_, statistics_, env_,
|
||||
&pinned_value_);
|
||||
} else {
|
||||
assert(last_not_merge_type == kTypeValue);
|
||||
std::string last_put_value = saved_value_;
|
||||
Slice temp_slice(last_put_value);
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &temp_slice,
|
||||
merge_operands_, &saved_value_,
|
||||
logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME,
|
||||
timer.ElapsedNanos());
|
||||
}
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(),
|
||||
&pinned_value_,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
}
|
||||
break;
|
||||
case kTypeValue:
|
||||
@@ -644,6 +662,9 @@ bool DBIter::FindValueForCurrentKey() {
|
||||
// This function is used in FindValueForCurrentKey.
|
||||
// We use Seek() function instead of Prev() to find necessary value
|
||||
bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
// FindValueForCurrentKey will enable pinning before calling
|
||||
// FindValueForCurrentKeyUsingSeek()
|
||||
assert(pinned_iters_mgr_.PinningEnabled());
|
||||
std::string last_key;
|
||||
AppendInternalKey(&last_key, ParsedInternalKey(saved_key_.GetKey(), sequence_,
|
||||
kValueTypeForSeek));
|
||||
@@ -657,7 +678,8 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
if (ikey.type == kTypeValue || ikey.type == kTypeDeletion ||
|
||||
ikey.type == kTypeSingleDeletion) {
|
||||
if (ikey.type == kTypeValue) {
|
||||
saved_value_ = iter_->value().ToString();
|
||||
assert(iter_->IsValuePinned());
|
||||
pinned_value_ = iter_->value();
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
@@ -667,11 +689,13 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
|
||||
// kTypeMerge. We need to collect all kTypeMerge values and save them
|
||||
// in operands
|
||||
std::deque<std::string> operands;
|
||||
current_entry_is_merged_ = true;
|
||||
merge_context_.Clear();
|
||||
while (iter_->Valid() &&
|
||||
user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) &&
|
||||
ikey.type == kTypeMerge) {
|
||||
operands.push_front(iter_->value().ToString());
|
||||
merge_context_.PushOperand(iter_->value(),
|
||||
iter_->IsValuePinned() /* operand_pinned */);
|
||||
iter_->Next();
|
||||
FindParseableKey(&ikey, kForward);
|
||||
}
|
||||
@@ -679,13 +703,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
if (!iter_->Valid() ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetKey()) ||
|
||||
ikey.type == kTypeDeletion || ikey.type == kTypeSingleDeletion) {
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), nullptr, operands,
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME, timer.ElapsedNanos());
|
||||
}
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), nullptr,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
// Make iter_ valid and point to saved_key_
|
||||
if (!iter_->Valid() ||
|
||||
!user_comparator_->Equal(ikey.user_key, saved_key_.GetKey())) {
|
||||
@@ -697,13 +717,9 @@ bool DBIter::FindValueForCurrentKeyUsingSeek() {
|
||||
}
|
||||
|
||||
const Slice& val = iter_->value();
|
||||
{
|
||||
StopWatchNano timer(env_, statistics_ != nullptr);
|
||||
PERF_TIMER_GUARD(merge_operator_time_nanos);
|
||||
user_merge_operator_->FullMerge(saved_key_.GetKey(), &val, operands,
|
||||
&saved_value_, logger_);
|
||||
RecordTick(statistics_, MERGE_OPERATION_TOTAL_TIME, timer.ElapsedNanos());
|
||||
}
|
||||
MergeHelper::TimedFullMerge(merge_operator_, saved_key_.GetKey(), &val,
|
||||
merge_context_.GetOperands(), &saved_value_,
|
||||
logger_, statistics_, env_, &pinned_value_);
|
||||
valid_ = true;
|
||||
return true;
|
||||
}
|
||||
@@ -767,6 +783,7 @@ void DBIter::FindParseableKey(ParsedInternalKey* ikey, Direction direction) {
|
||||
|
||||
void DBIter::Seek(const Slice& target) {
|
||||
StopWatch sw(env_, statistics_, DB_SEEK);
|
||||
ReleaseTempPinnedData();
|
||||
saved_key_.Clear();
|
||||
// now savved_key is used to store internal key.
|
||||
saved_key_.SetInternalKey(target, sequence_);
|
||||
@@ -778,9 +795,15 @@ void DBIter::Seek(const Slice& target) {
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
if (prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_key_ = prefix_extractor_->Transform(target);
|
||||
}
|
||||
direction_ = kForward;
|
||||
ClearSavedValue();
|
||||
FindNextUserEntry(false /* not skipping */);
|
||||
FindNextUserEntry(false /* not skipping */, prefix_same_as_start_);
|
||||
if (!valid_) {
|
||||
prefix_start_key_.clear();
|
||||
}
|
||||
if (statistics_ != nullptr) {
|
||||
if (valid_) {
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
@@ -791,7 +814,8 @@ void DBIter::Seek(const Slice& target) {
|
||||
valid_ = false;
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_.SetKey(prefix_extractor_->Transform(target));
|
||||
prefix_start_buf_.SetKey(prefix_start_key_);
|
||||
prefix_start_key_ = prefix_start_buf_.GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -802,6 +826,7 @@ void DBIter::SeekToFirst() {
|
||||
max_skip_ = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
direction_ = kForward;
|
||||
ReleaseTempPinnedData();
|
||||
ClearSavedValue();
|
||||
|
||||
{
|
||||
@@ -811,7 +836,7 @@ void DBIter::SeekToFirst() {
|
||||
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK);
|
||||
if (iter_->Valid()) {
|
||||
FindNextUserEntry(false /* not skipping */);
|
||||
FindNextUserEntry(false /* not skipping */, false /* no prefix check */);
|
||||
if (statistics_ != nullptr) {
|
||||
if (valid_) {
|
||||
RecordTick(statistics_, NUMBER_DB_SEEK_FOUND);
|
||||
@@ -822,7 +847,8 @@ void DBIter::SeekToFirst() {
|
||||
valid_ = false;
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_.SetKey(prefix_extractor_->Transform(saved_key_.GetKey()));
|
||||
prefix_start_buf_.SetKey(prefix_extractor_->Transform(saved_key_.GetKey()));
|
||||
prefix_start_key_ = prefix_start_buf_.GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -833,6 +859,7 @@ void DBIter::SeekToLast() {
|
||||
max_skip_ = std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
direction_ = kReverse;
|
||||
ReleaseTempPinnedData();
|
||||
ClearSavedValue();
|
||||
|
||||
{
|
||||
@@ -870,7 +897,8 @@ void DBIter::SeekToLast() {
|
||||
}
|
||||
}
|
||||
if (valid_ && prefix_extractor_ && prefix_same_as_start_) {
|
||||
prefix_start_.SetKey(prefix_extractor_->Transform(saved_key_.GetKey()));
|
||||
prefix_start_buf_.SetKey(prefix_extractor_->Transform(saved_key_.GetKey()));
|
||||
prefix_start_key_ = prefix_start_buf_.GetKey();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -885,10 +913,7 @@ Iterator* NewDBIterator(Env* env, const ImmutableCFOptions& ioptions,
|
||||
DBIter* db_iter =
|
||||
new DBIter(env, ioptions, user_key_comparator, internal_iter, sequence,
|
||||
false, max_sequential_skip_in_iterations, version_number,
|
||||
iterate_upper_bound, prefix_same_as_start);
|
||||
if (pin_data) {
|
||||
db_iter->PinData();
|
||||
}
|
||||
iterate_upper_bound, prefix_same_as_start, pin_data);
|
||||
return db_iter;
|
||||
}
|
||||
|
||||
@@ -911,14 +936,10 @@ 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(); }
|
||||
inline Status ArenaWrappedDBIter::PinData() { return db_iter_->PinData(); }
|
||||
inline Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
|
||||
std::string* prop) {
|
||||
return db_iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
inline Status ArenaWrappedDBIter::ReleasePinnedData() {
|
||||
return db_iter_->ReleasePinnedData();
|
||||
}
|
||||
void ArenaWrappedDBIter::RegisterCleanup(CleanupFunction function, void* arg1,
|
||||
void* arg2) {
|
||||
db_iter_->RegisterCleanup(function, arg1, arg2);
|
||||
@@ -936,12 +957,9 @@ ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
DBIter* db_iter =
|
||||
new (mem) DBIter(env, ioptions, user_key_comparator, nullptr, sequence,
|
||||
true, max_sequential_skip_in_iterations, version_number,
|
||||
iterate_upper_bound, prefix_same_as_start);
|
||||
iterate_upper_bound, prefix_same_as_start, pin_data);
|
||||
|
||||
iter->SetDBIter(db_iter);
|
||||
if (pin_data) {
|
||||
iter->PinData();
|
||||
}
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
+2
-3
@@ -10,11 +10,12 @@
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "util/arena.h"
|
||||
#include "util/autovector.h"
|
||||
#include "util/cf_options.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -63,8 +64,6 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
virtual Status status() const override;
|
||||
|
||||
void RegisterCleanup(CleanupFunction function, void* arg1, void* arg2);
|
||||
virtual Status PinData();
|
||||
virtual Status ReleasePinnedData();
|
||||
virtual Status GetProperty(std::string prop_name, std::string* prop) override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -150,6 +150,9 @@ class TestIterator : public InternalIterator {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
virtual bool IsKeyPinned() const override { return true; }
|
||||
virtual bool IsValuePinned() const override { return true; }
|
||||
|
||||
private:
|
||||
bool initialized_;
|
||||
bool valid_;
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,137 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same 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 <limits>
|
||||
#include <string>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBOptionsTest : public DBTestBase {
|
||||
public:
|
||||
DBOptionsTest() : DBTestBase("/db_options_test") {}
|
||||
};
|
||||
|
||||
// RocksDB lite don't support dynamic options.
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBOptionsTest, EnableAutoCompactionAndTriggerStall) {
|
||||
const std::string kValue(1024, 'v');
|
||||
for (int method_type = 0; method_type < 2; method_type++) {
|
||||
for (int option_type = 0; option_type < 4; option_type++) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.write_buffer_size = 1024 * 1024 * 10;
|
||||
options.compression = CompressionType::kNoCompression;
|
||||
options.level0_file_num_compaction_trigger = 1;
|
||||
options.level0_stop_writes_trigger = std::numeric_limits<int>::max();
|
||||
options.level0_slowdown_writes_trigger = std::numeric_limits<int>::max();
|
||||
options.hard_pending_compaction_bytes_limit =
|
||||
std::numeric_limits<uint64_t>::max();
|
||||
options.soft_pending_compaction_bytes_limit =
|
||||
std::numeric_limits<uint64_t>::max();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
int i = 0;
|
||||
for (; i < 1024; i++) {
|
||||
Put(Key(i), kValue);
|
||||
}
|
||||
Flush();
|
||||
for (; i < 1024 * 2; i++) {
|
||||
Put(Key(i), kValue);
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
uint64_t l0_size = SizeAtLevel(0);
|
||||
|
||||
switch (option_type) {
|
||||
case 0:
|
||||
// test with level0_stop_writes_trigger
|
||||
options.level0_stop_writes_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
break;
|
||||
case 1:
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
break;
|
||||
case 2:
|
||||
options.hard_pending_compaction_bytes_limit = l0_size;
|
||||
options.soft_pending_compaction_bytes_limit = l0_size;
|
||||
break;
|
||||
case 3:
|
||||
options.soft_pending_compaction_bytes_limit = l0_size;
|
||||
break;
|
||||
}
|
||||
Reopen(options);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_FALSE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBOptionsTest::EnableAutoCompactionAndTriggerStall:1",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"DBImpl::BackgroundCompaction():BeforePickCompaction",
|
||||
"DBOptionsTest::EnableAutoCompactionAndTriggerStall:2"},
|
||||
{"DBOptionsTest::EnableAutoCompactionAndTriggerStall:3",
|
||||
"DBImpl::BackgroundCompaction():AfterPickCompaction"}});
|
||||
// Block background compaction.
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
switch (method_type) {
|
||||
case 0:
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions({{"disable_auto_compactions", "false"}}));
|
||||
break;
|
||||
case 1:
|
||||
ASSERT_OK(dbfull()->EnableAutoCompaction(
|
||||
{dbfull()->DefaultColumnFamily()}));
|
||||
break;
|
||||
}
|
||||
TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:1");
|
||||
// Wait for stall condition recalculate.
|
||||
TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:2");
|
||||
|
||||
switch (option_type) {
|
||||
case 0:
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().IsStopped());
|
||||
break;
|
||||
case 1:
|
||||
ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
break;
|
||||
case 2:
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().IsStopped());
|
||||
break;
|
||||
case 3:
|
||||
ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_TRUE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
break;
|
||||
}
|
||||
TEST_SYNC_POINT("DBOptionsTest::EnableAutoCompactionAndTriggerStall:3");
|
||||
|
||||
// Background compaction executed.
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_FALSE(dbfull()->TEST_write_controler().IsStopped());
|
||||
ASSERT_FALSE(dbfull()->TEST_write_controler().NeedsDelay());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "rocksdb/perf_level.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -223,7 +224,7 @@ void GetExpectedTableProperties(TableProperties* expected_tp,
|
||||
const int kBloomBitsPerKey,
|
||||
const size_t kBlockSize) {
|
||||
const int kKeyCount = kTableCount * kKeysPerTable;
|
||||
const int kAvgSuccessorSize = kKeySize / 2;
|
||||
const int kAvgSuccessorSize = kKeySize / 5;
|
||||
const int kEncodingSavePerKey = kKeySize / 4;
|
||||
expected_tp->raw_key_size = kKeyCount * (kKeySize + 8);
|
||||
expected_tp->raw_value_size = kKeyCount * kValueSize;
|
||||
@@ -235,7 +236,7 @@ void GetExpectedTableProperties(TableProperties* expected_tp,
|
||||
expected_tp->data_size =
|
||||
kTableCount * (kKeysPerTable * (kKeySize + 8 + kValueSize));
|
||||
expected_tp->index_size =
|
||||
expected_tp->num_data_blocks * (kAvgSuccessorSize + 12);
|
||||
expected_tp->num_data_blocks * (kAvgSuccessorSize + 8);
|
||||
expected_tp->filter_size =
|
||||
kTableCount * (kKeysPerTable * kBloomBitsPerKey / 8);
|
||||
}
|
||||
@@ -253,6 +254,35 @@ TEST_F(DBPropertiesTest, ValidatePropertyInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, ValidateSampleNumber) {
|
||||
// When "max_open_files" is -1, we read all the files for
|
||||
// "rocksdb.estimate-num-keys" computation, which is the ground truth.
|
||||
// Otherwise, we sample 20 newest files to make an estimation.
|
||||
// Formula: lastest_20_files_active_key_ratio * total_files
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.level0_stop_writes_trigger = 1000;
|
||||
DestroyAndReopen(options);
|
||||
int key = 0;
|
||||
for (int files = 20; files >= 10; files -= 10) {
|
||||
for (int i = 0; i < files; i++) {
|
||||
int rows = files / 10;
|
||||
for (int j = 0; j < rows; j++) {
|
||||
db_->Put(WriteOptions(), std::to_string(++key), "foo");
|
||||
}
|
||||
db_->Flush(FlushOptions());
|
||||
}
|
||||
}
|
||||
std::string num;
|
||||
Reopen(options);
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
||||
ASSERT_EQ("45", num);
|
||||
options.max_open_files = -1;
|
||||
Reopen(options);
|
||||
ASSERT_TRUE(dbfull()->GetProperty("rocksdb.estimate-num-keys", &num));
|
||||
ASSERT_EQ("50", num);
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, AggregatedTableProperties) {
|
||||
for (int kTableCount = 40; kTableCount <= 100; kTableCount += 30) {
|
||||
const int kKeysPerTable = 100;
|
||||
@@ -564,7 +594,8 @@ TEST_F(DBPropertiesTest, NumImmutableMemTable) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, GetProperty) {
|
||||
// TODO(techdept) : Disabled flaky test #12863555
|
||||
TEST_F(DBPropertiesTest, DISABLED_GetProperty) {
|
||||
// Set sizes to both background thread pool to be 1 and block them.
|
||||
env_->SetBackgroundThreads(1, Env::HIGH);
|
||||
env_->SetBackgroundThreads(1, Env::LOW);
|
||||
@@ -869,6 +900,48 @@ TEST_F(DBPropertiesTest, EstimatePendingCompBytes) {
|
||||
"rocksdb.estimate-pending-compaction-bytes", &int_num));
|
||||
ASSERT_EQ(int_num, 0U);
|
||||
}
|
||||
|
||||
TEST_F(DBPropertiesTest, EstimateCompressionRatio) {
|
||||
if (!Snappy_Supported()) {
|
||||
return;
|
||||
}
|
||||
const int kNumL0Files = 3;
|
||||
const int kNumEntriesPerFile = 1000;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.compression_per_level = {kNoCompression, kSnappyCompression};
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_background_flushes = 0;
|
||||
options.num_levels = 2;
|
||||
Reopen(options);
|
||||
|
||||
// compression ratio is -1.0 when no open files at level
|
||||
ASSERT_EQ(CompressionRatioAtLevel(0), -1.0);
|
||||
|
||||
const std::string kVal(100, 'a');
|
||||
for (int i = 0; i < kNumL0Files; ++i) {
|
||||
for (int j = 0; j < kNumEntriesPerFile; ++j) {
|
||||
// Put common data ("key") at end to prevent delta encoding from
|
||||
// compressing the key effectively
|
||||
std::string key = ToString(i) + ToString(j) + "key";
|
||||
ASSERT_OK(dbfull()->Put(WriteOptions(), key, kVal));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
// no compression at L0, so ratio is less than one
|
||||
ASSERT_LT(CompressionRatioAtLevel(0), 1.0);
|
||||
ASSERT_GT(CompressionRatioAtLevel(0), 0.0);
|
||||
ASSERT_EQ(CompressionRatioAtLevel(1), -1.0);
|
||||
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
||||
|
||||
ASSERT_EQ(CompressionRatioAtLevel(0), -1.0);
|
||||
// Data at L1 should be highly compressed thanks to Snappy and redundant data
|
||||
// in values (ratio is 12.846 as of 4/19/2016).
|
||||
ASSERT_GT(CompressionRatioAtLevel(1), 10.0);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
class CountingUserTblPropCollector : public TablePropertiesCollector {
|
||||
|
||||
@@ -0,0 +1,801 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under the BSD-style license found in the
|
||||
// LICENSE file in the root directory of this source tree. An additional grant
|
||||
// of patent rights can be found in the PATENTS file in the same 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 "db/db_test_util.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/sst_file_manager.h"
|
||||
#include "util/sst_file_manager_impl.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
class DBSSTTest : public DBTestBase {
|
||||
public:
|
||||
DBSSTTest() : DBTestBase("/db_sst_test") {}
|
||||
};
|
||||
|
||||
TEST_F(DBSSTTest, DontDeletePendingOutputs) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Every time we write to a table file, call FOF/POF with full DB scan. This
|
||||
// will make sure our pending_outputs_ protection work correctly
|
||||
std::function<void()> purge_obsolete_files_function = [&]() {
|
||||
JobContext job_context(0);
|
||||
dbfull()->TEST_LockMutex();
|
||||
dbfull()->FindObsoleteFiles(&job_context, true /*force*/);
|
||||
dbfull()->TEST_UnlockMutex();
|
||||
dbfull()->PurgeObsoleteFiles(job_context);
|
||||
job_context.Clean();
|
||||
};
|
||||
|
||||
env_->table_write_callback_ = &purge_obsolete_files_function;
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(Put("a", "begin"));
|
||||
ASSERT_OK(Put("z", "end"));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// If pending output guard does not work correctly, PurgeObsoleteFiles() will
|
||||
// delete the file that Compaction is trying to create, causing this: error
|
||||
// db/db_test.cc:975: IO error:
|
||||
// /tmp/rocksdbtest-1552237650/db_test/000009.sst: No such file or directory
|
||||
Compact("a", "b");
|
||||
}
|
||||
|
||||
// 1 Create some SST files by inserting K-V pairs into DB
|
||||
// 2 Close DB and change suffix from ".sst" to ".ldb" for every other SST file
|
||||
// 3 Open DB and check if all key can be read
|
||||
TEST_F(DBSSTTest, SSTsWithLdbSuffixHandling) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 110 << 10; // 110KB
|
||||
options.num_levels = 4;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
int key_id = 0;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
GenerateNewFile(&rnd, &key_id, false);
|
||||
}
|
||||
Flush();
|
||||
Close();
|
||||
int const num_files = GetSstFileCount(dbname_);
|
||||
ASSERT_GT(num_files, 0);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
GetSstFiles(dbname_, &filenames);
|
||||
int num_ldb_files = 0;
|
||||
for (size_t i = 0; i < filenames.size(); ++i) {
|
||||
if (i & 1) {
|
||||
continue;
|
||||
}
|
||||
std::string const rdb_name = dbname_ + "/" + filenames[i];
|
||||
std::string const ldb_name = Rocks2LevelTableFileName(rdb_name);
|
||||
ASSERT_TRUE(env_->RenameFile(rdb_name, ldb_name).ok());
|
||||
++num_ldb_files;
|
||||
}
|
||||
ASSERT_GT(num_ldb_files, 0);
|
||||
ASSERT_EQ(num_files, GetSstFileCount(dbname_));
|
||||
|
||||
Reopen(options);
|
||||
for (int k = 0; k < key_id; ++k) {
|
||||
ASSERT_NE("NOT_FOUND", Get(Key(k)));
|
||||
}
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBSSTTest, DontDeleteMovedFile) {
|
||||
// This test triggers move compaction and verifies that the file is not
|
||||
// deleted when it's part of move compaction
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.max_bytes_for_level_base = 1024 * 1024; // 1 MB
|
||||
options.level0_file_num_compaction_trigger =
|
||||
2; // trigger compaction when we have 2 files
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
// Create two 1MB sst files
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// Create 1MB sst file
|
||||
for (int j = 0; j < 100; ++j) {
|
||||
ASSERT_OK(Put(Key(i * 50 + j), RandomString(&rnd, 10 * 1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// this should execute both L0->L1 and L1->(move)->L2 compactions
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("0,0,1", FilesPerLevel(0));
|
||||
|
||||
// If the moved file is actually deleted (the move-safeguard in
|
||||
// ~Version::Version() is not there), we get this failure:
|
||||
// Corruption: Can't access /000009.sst
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
// This reproduces a bug where we don't delete a file because when it was
|
||||
// supposed to be deleted, it was blocked by pending_outputs
|
||||
// Consider:
|
||||
// 1. current file_number is 13
|
||||
// 2. compaction (1) starts, blocks deletion of all files starting with 13
|
||||
// (pending outputs)
|
||||
// 3. file 13 is created by compaction (2)
|
||||
// 4. file 13 is consumed by compaction (3) and file 15 was created. Since file
|
||||
// 13 has no references, it is put into VersionSet::obsolete_files_
|
||||
// 5. FindObsoleteFiles() gets file 13 from VersionSet::obsolete_files_. File 13
|
||||
// is deleted from obsolete_files_ set.
|
||||
// 6. PurgeObsoleteFiles() tries to delete file 13, but this file is blocked by
|
||||
// pending outputs since compaction (1) is still running. It is not deleted and
|
||||
// it is not present in obsolete_files_ anymore. Therefore, we never delete it.
|
||||
TEST_F(DBSSTTest, DeleteObsoleteFilesPendingOutputs) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.write_buffer_size = 2 * 1024 * 1024; // 2 MB
|
||||
options.max_bytes_for_level_base = 1024 * 1024; // 1 MB
|
||||
options.level0_file_num_compaction_trigger =
|
||||
2; // trigger compaction when we have 2 files
|
||||
options.max_background_flushes = 2;
|
||||
options.max_background_compactions = 2;
|
||||
|
||||
OnFileDeletionListener* listener = new OnFileDeletionListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
|
||||
Reopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
// Create two 1MB sst files
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
// Create 1MB sst file
|
||||
for (int j = 0; j < 100; ++j) {
|
||||
ASSERT_OK(Put(Key(i * 50 + j), RandomString(&rnd, 10 * 1024)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// this should execute both L0->L1 and L1->(move)->L2 compactions
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ("0,0,1", FilesPerLevel(0));
|
||||
|
||||
test::SleepingBackgroundTask blocking_thread;
|
||||
port::Mutex mutex_;
|
||||
bool already_blocked(false);
|
||||
|
||||
// block the flush
|
||||
std::function<void()> block_first_time = [&]() {
|
||||
bool blocking = false;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
if (!already_blocked) {
|
||||
blocking = true;
|
||||
already_blocked = true;
|
||||
}
|
||||
}
|
||||
if (blocking) {
|
||||
blocking_thread.DoSleep();
|
||||
}
|
||||
};
|
||||
env_->table_write_callback_ = &block_first_time;
|
||||
// Insert 2.5MB data, which should trigger a flush because we exceed
|
||||
// write_buffer_size. The flush will be blocked with block_first_time
|
||||
// pending_file is protecting all the files created after
|
||||
for (int j = 0; j < 256; ++j) {
|
||||
ASSERT_OK(Put(Key(j), RandomString(&rnd, 10 * 1024)));
|
||||
}
|
||||
blocking_thread.WaitUntilSleeping();
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(2, nullptr, nullptr));
|
||||
|
||||
ASSERT_EQ("0,0,0,1", FilesPerLevel(0));
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
ASSERT_EQ(metadata.size(), 1U);
|
||||
auto file_on_L2 = metadata[0].name;
|
||||
listener->SetExpectedFileName(dbname_ + file_on_L2);
|
||||
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(3, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */));
|
||||
ASSERT_EQ("0,0,0,0,1", FilesPerLevel(0));
|
||||
|
||||
// finish the flush!
|
||||
blocking_thread.WakeUp();
|
||||
blocking_thread.WaitUntilDone();
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_EQ("1,0,0,0,1", FilesPerLevel(0));
|
||||
|
||||
metadata.clear();
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
ASSERT_EQ(metadata.size(), 2U);
|
||||
|
||||
// This file should have been deleted during last compaction
|
||||
ASSERT_EQ(Status::NotFound(), env_->FileExists(dbname_ + file_on_L2));
|
||||
listener->VerifyMatchedCount(1);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBSSTTest, DBWithSstFileManager) {
|
||||
std::shared_ptr<SstFileManager> sst_file_manager(NewSstFileManager(env_));
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
|
||||
int files_added = 0;
|
||||
int files_deleted = 0;
|
||||
int files_moved = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnAddFile", [&](void* arg) { files_added++; });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnDeleteFile", [&](void* arg) { files_deleted++; });
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"SstFileManagerImpl::OnMoveFile", [&](void* arg) { files_moved++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
for (int i = 0; i < 25; i++) {
|
||||
GenerateNewRandomFile(&rnd);
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
// Verify that we are tracking all sst files in dbname_
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), GetAllSSTFiles());
|
||||
}
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
auto files_in_db = GetAllSSTFiles();
|
||||
// Verify that we are tracking all sst files in dbname_
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
// Verify the total files size
|
||||
uint64_t total_files_size = 0;
|
||||
for (auto& file_to_size : files_in_db) {
|
||||
total_files_size += file_to_size.second;
|
||||
}
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
// We flushed at least 25 files
|
||||
ASSERT_GE(files_added, 25);
|
||||
// Compaction must have deleted some files
|
||||
ASSERT_GT(files_deleted, 0);
|
||||
// No files were moved
|
||||
ASSERT_EQ(files_moved, 0);
|
||||
|
||||
Close();
|
||||
Reopen(options);
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
|
||||
// Verify that we track all the files again after the DB is closed and opened
|
||||
Close();
|
||||
sst_file_manager.reset(NewSstFileManager(env_));
|
||||
options.sst_file_manager = sst_file_manager;
|
||||
sfm = static_cast<SstFileManagerImpl*>(sst_file_manager.get());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ(sfm->GetTrackedFiles(), files_in_db);
|
||||
ASSERT_EQ(sfm->GetTotalSize(), total_files_size);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBSSTTest, RateLimitedDelete) {
|
||||
Destroy(last_options_);
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency({
|
||||
{"DBSSTTest::RateLimitedDelete:1",
|
||||
"DeleteScheduler::BackgroundEmptyTrash"},
|
||||
});
|
||||
|
||||
std::vector<uint64_t> penalties;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::BackgroundEmptyTrash:Wait",
|
||||
[&](void* arg) { penalties.push_back(*(static_cast<int*>(arg))); });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
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, rate_bytes_per_sec, false, &s));
|
||||
ASSERT_OK(s);
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// Create 4 files in L0
|
||||
for (char v = 'a'; v <= 'd'; v++) {
|
||||
ASSERT_OK(Put("Key2", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key3", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key1", DummyString(1024, v)));
|
||||
ASSERT_OK(Put("Key4", DummyString(1024, v)));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
ASSERT_EQ("4", FilesPerLevel(0));
|
||||
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
|
||||
// Compaction will move the 4 files in L0 to trash and create 1 L1 file
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
uint64_t delete_start_time = env_->NowMicros();
|
||||
// Hold BackgroundEmptyTrash
|
||||
TEST_SYNC_POINT("DBSSTTest::RateLimitedDelete:1");
|
||||
sfm->WaitForEmptyTrash();
|
||||
uint64_t time_spent_deleting = env_->NowMicros() - delete_start_time;
|
||||
|
||||
uint64_t total_files_size = 0;
|
||||
uint64_t expected_penlty = 0;
|
||||
ASSERT_EQ(penalties.size(), metadata.size());
|
||||
for (size_t i = 0; i < metadata.size(); i++) {
|
||||
total_files_size += metadata[i].size;
|
||||
expected_penlty = ((total_files_size * 1000000) / rate_bytes_per_sec);
|
||||
ASSERT_EQ(expected_penlty, penalties[i]);
|
||||
}
|
||||
ASSERT_GT(time_spent_deleting, expected_penlty * 0.9);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
// 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
|
||||
// files in the second path were not.
|
||||
TEST_F(DBSSTTest, DeleteSchedulerMultipleDBPaths) {
|
||||
int bg_delete_file = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* arg) { bg_delete_file++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.db_paths.emplace_back(dbname_, 1024 * 100);
|
||||
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));
|
||||
ASSERT_OK(s);
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create 4 files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'A')));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
ASSERT_EQ("4", FilesPerLevel(0));
|
||||
// Compaction will delete files from L0 in first db path and generate a new
|
||||
// file in L1 in second db path
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.target_path_id = 1;
|
||||
Slice begin("Key0");
|
||||
Slice end("Key3");
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &begin, &end));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
// Create 4 files in L0
|
||||
for (int i = 4; i < 8; i++) {
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'B')));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_EQ("4,1", FilesPerLevel(0));
|
||||
|
||||
// Compaction will delete files from L0 in first db path and generate a new
|
||||
// file in L1 in second db path
|
||||
begin = "Key4";
|
||||
end = "Key7";
|
||||
ASSERT_OK(db_->CompactRange(compact_options, &begin, &end));
|
||||
ASSERT_EQ("0,2", FilesPerLevel(0));
|
||||
|
||||
sfm->WaitForEmptyTrash();
|
||||
ASSERT_EQ(bg_delete_file, 8);
|
||||
|
||||
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);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, DestroyDBWithRateLimitedDelete) {
|
||||
int bg_delete_file = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DeleteScheduler::DeleteTrashFile:DeleteFile",
|
||||
[&](void* arg) { bg_delete_file++; });
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create 4 files in L0
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ASSERT_OK(Put("Key" + ToString(i), DummyString(1024, 'A')));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
// We created 4 sst files in L0
|
||||
ASSERT_EQ("4", FilesPerLevel(0));
|
||||
|
||||
// Close DB and destroy it using DeleteScheduler
|
||||
Close();
|
||||
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));
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(options.sst_file_manager.get());
|
||||
sfm->WaitForEmptyTrash();
|
||||
// We have deleted the 4 sst files in the delete_scheduler
|
||||
ASSERT_EQ(bg_delete_file, 4);
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBSSTTest, DBWithMaxSpaceAllowed) {
|
||||
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.disable_auto_compactions = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
|
||||
// Generate a file containing 100 keys.
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ASSERT_OK(Put(Key(i), RandomString(&rnd, 50)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
uint64_t first_file_size = 0;
|
||||
auto files_in_db = GetAllSSTFiles(&first_file_size);
|
||||
ASSERT_EQ(sfm->GetTotalSize(), first_file_size);
|
||||
|
||||
// Set the maximum allowed space usage to the current total size
|
||||
sfm->SetMaxAllowedSpaceUsage(first_file_size + 1);
|
||||
|
||||
ASSERT_OK(Put("key1", "val1"));
|
||||
// This flush will cause bg_error_ and will fail
|
||||
ASSERT_NOK(Flush());
|
||||
}
|
||||
|
||||
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.
|
||||
// When bg_error_ is set we will verify that the DB size is greater
|
||||
// than the limit.
|
||||
|
||||
std::vector<int> max_space_limits_mbs = {1, 2, 4, 8, 10};
|
||||
|
||||
bool bg_error_set = false;
|
||||
uint64_t total_sst_files_size = 0;
|
||||
|
||||
int reached_max_space_on_flush = 0;
|
||||
int reached_max_space_on_compaction = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::FlushMemTableToOutputFile:MaxAllowedSpaceReached",
|
||||
[&](void* arg) {
|
||||
bg_error_set = true;
|
||||
GetAllSSTFiles(&total_sst_files_size);
|
||||
reached_max_space_on_flush++;
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"CompactionJob::FinishCompactionOutputFile:MaxAllowedSpaceReached",
|
||||
[&](void* arg) {
|
||||
bg_error_set = true;
|
||||
GetAllSSTFiles(&total_sst_files_size);
|
||||
reached_max_space_on_compaction++;
|
||||
});
|
||||
|
||||
for (auto limit_mb : max_space_limits_mbs) {
|
||||
bg_error_set = false;
|
||||
total_sst_files_size = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
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.write_buffer_size = 1024 * 512; // 512 Kb
|
||||
DestroyAndReopen(options);
|
||||
Random rnd(301);
|
||||
|
||||
sfm->SetMaxAllowedSpaceUsage(limit_mb * 1024 * 1024);
|
||||
|
||||
int keys_written = 0;
|
||||
uint64_t estimated_db_size = 0;
|
||||
while (true) {
|
||||
auto s = Put(RandomString(&rnd, 10), RandomString(&rnd, 50));
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
}
|
||||
keys_written++;
|
||||
// Check the estimated db size vs the db limit just to make sure we
|
||||
// dont run into an infinite loop
|
||||
estimated_db_size = keys_written * 60; // ~60 bytes per key
|
||||
ASSERT_LT(estimated_db_size, limit_mb * 1024 * 1024 * 2);
|
||||
}
|
||||
ASSERT_TRUE(bg_error_set);
|
||||
ASSERT_GE(total_sst_files_size, limit_mb * 1024 * 1024);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
ASSERT_GT(reached_max_space_on_flush, 0);
|
||||
ASSERT_GT(reached_max_space_on_compaction, 0);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBSSTTest, OpenDBWithInfiniteMaxOpenFiles) {
|
||||
// Open DB with infinite max open files
|
||||
// - First iteration use 1 thread to open files
|
||||
// - Second iteration use 5 threads to open files
|
||||
for (int iter = 0; iter < 2; iter++) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.write_buffer_size = 100000;
|
||||
options.disable_auto_compactions = true;
|
||||
options.max_open_files = -1;
|
||||
if (iter == 0) {
|
||||
options.max_file_opening_threads = 1;
|
||||
} else {
|
||||
options.max_file_opening_threads = 5;
|
||||
}
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create 12 Files in L0 (then move then to L2)
|
||||
for (int i = 0; i < 12; i++) {
|
||||
std::string k = "L2_" + Key(i);
|
||||
ASSERT_OK(Put(k, k + std::string(1000, 'a')));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
db_->CompactRange(compact_options, nullptr, nullptr);
|
||||
|
||||
// Create 12 Files in L0
|
||||
for (int i = 0; i < 12; i++) {
|
||||
std::string k = "L0_" + Key(i);
|
||||
ASSERT_OK(Put(k, k + std::string(1000, 'a')));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
Close();
|
||||
|
||||
// Reopening the DB will load all exisitng files
|
||||
Reopen(options);
|
||||
ASSERT_EQ("12,0,12", FilesPerLevel(0));
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(db_->DefaultColumnFamily(), &files);
|
||||
|
||||
for (const auto& level : files) {
|
||||
for (const auto& file : level) {
|
||||
ASSERT_TRUE(file.table_reader_handle != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 12; i++) {
|
||||
ASSERT_EQ(Get("L0_" + Key(i)), "L0_" + Key(i) + std::string(1000, 'a'));
|
||||
ASSERT_EQ(Get("L2_" + Key(i)), "L2_" + Key(i) + std::string(1000, 'a'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, GetTotalSstFilesSize) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
// Generate 5 files in L0
|
||||
for (int i = 0; i < 5; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
std::string val = "val_file_" + ToString(i);
|
||||
ASSERT_OK(Put(Key(j), val));
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
ASSERT_EQ("5", FilesPerLevel(0));
|
||||
|
||||
std::vector<LiveFileMetaData> live_files_meta;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files_meta);
|
||||
ASSERT_EQ(live_files_meta.size(), 5);
|
||||
uint64_t single_file_size = live_files_meta[0].size;
|
||||
|
||||
uint64_t live_sst_files_size = 0;
|
||||
uint64_t total_sst_files_size = 0;
|
||||
for (const auto& file_meta : live_files_meta) {
|
||||
live_sst_files_size += file_meta.size;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
// Live SST files = 5
|
||||
// Total SST files = 5
|
||||
ASSERT_EQ(live_sst_files_size, 5 * single_file_size);
|
||||
ASSERT_EQ(total_sst_files_size, 5 * single_file_size);
|
||||
|
||||
// hold current version
|
||||
std::unique_ptr<Iterator> iter1(dbfull()->NewIterator(ReadOptions()));
|
||||
|
||||
// Compact 5 files into 1 file in L0
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,1", FilesPerLevel(0));
|
||||
|
||||
live_files_meta.clear();
|
||||
dbfull()->GetLiveFilesMetaData(&live_files_meta);
|
||||
ASSERT_EQ(live_files_meta.size(), 1);
|
||||
|
||||
live_sst_files_size = 0;
|
||||
total_sst_files_size = 0;
|
||||
for (const auto& file_meta : live_files_meta) {
|
||||
live_sst_files_size += file_meta.size;
|
||||
}
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
// Live SST files = 1 (compacted file)
|
||||
// Total SST files = 6 (5 original files + compacted file)
|
||||
ASSERT_EQ(live_sst_files_size, 1 * single_file_size);
|
||||
ASSERT_EQ(total_sst_files_size, 6 * single_file_size);
|
||||
|
||||
// hold current version
|
||||
std::unique_ptr<Iterator> iter2(dbfull()->NewIterator(ReadOptions()));
|
||||
|
||||
// Delete all keys and compact, this will delete all live files
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
Flush();
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("", FilesPerLevel(0));
|
||||
|
||||
live_files_meta.clear();
|
||||
dbfull()->GetLiveFilesMetaData(&live_files_meta);
|
||||
ASSERT_EQ(live_files_meta.size(), 0);
|
||||
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
// Live SST files = 0
|
||||
// Total SST files = 6 (5 original files + compacted file)
|
||||
ASSERT_EQ(total_sst_files_size, 6 * single_file_size);
|
||||
|
||||
iter1.reset();
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
// Live SST files = 0
|
||||
// Total SST files = 1 (compacted file)
|
||||
ASSERT_EQ(total_sst_files_size, 1 * single_file_size);
|
||||
|
||||
iter2.reset();
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
// Live SST files = 0
|
||||
// Total SST files = 0
|
||||
ASSERT_EQ(total_sst_files_size, 0);
|
||||
}
|
||||
|
||||
TEST_F(DBSSTTest, GetTotalSstFilesSizeVersionsFilesShared) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.compression = kNoCompression;
|
||||
DestroyAndReopen(options);
|
||||
// Generate 5 files in L0
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_OK(Put(Key(i), "val"));
|
||||
Flush();
|
||||
}
|
||||
ASSERT_EQ("5", FilesPerLevel(0));
|
||||
|
||||
std::vector<LiveFileMetaData> live_files_meta;
|
||||
dbfull()->GetLiveFilesMetaData(&live_files_meta);
|
||||
ASSERT_EQ(live_files_meta.size(), 5);
|
||||
uint64_t single_file_size = live_files_meta[0].size;
|
||||
|
||||
uint64_t live_sst_files_size = 0;
|
||||
uint64_t total_sst_files_size = 0;
|
||||
for (const auto& file_meta : live_files_meta) {
|
||||
live_sst_files_size += file_meta.size;
|
||||
}
|
||||
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
|
||||
// Live SST files = 5
|
||||
// Total SST files = 5
|
||||
ASSERT_EQ(live_sst_files_size, 5 * single_file_size);
|
||||
ASSERT_EQ(total_sst_files_size, 5 * single_file_size);
|
||||
|
||||
// hold current version
|
||||
std::unique_ptr<Iterator> iter1(dbfull()->NewIterator(ReadOptions()));
|
||||
|
||||
// Compaction will do trivial move from L0 to L1
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("0,5", FilesPerLevel(0));
|
||||
|
||||
live_files_meta.clear();
|
||||
dbfull()->GetLiveFilesMetaData(&live_files_meta);
|
||||
ASSERT_EQ(live_files_meta.size(), 5);
|
||||
|
||||
live_sst_files_size = 0;
|
||||
total_sst_files_size = 0;
|
||||
for (const auto& file_meta : live_files_meta) {
|
||||
live_sst_files_size += file_meta.size;
|
||||
}
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
// Live SST files = 5
|
||||
// Total SST files = 5 (used in 2 version)
|
||||
ASSERT_EQ(live_sst_files_size, 5 * single_file_size);
|
||||
ASSERT_EQ(total_sst_files_size, 5 * single_file_size);
|
||||
|
||||
// hold current version
|
||||
std::unique_ptr<Iterator> iter2(dbfull()->NewIterator(ReadOptions()));
|
||||
|
||||
// Delete all keys and compact, this will delete all live files
|
||||
for (int i = 0; i < 5; i++) {
|
||||
ASSERT_OK(Delete(Key(i)));
|
||||
}
|
||||
Flush();
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("", FilesPerLevel(0));
|
||||
|
||||
live_files_meta.clear();
|
||||
dbfull()->GetLiveFilesMetaData(&live_files_meta);
|
||||
ASSERT_EQ(live_files_meta.size(), 0);
|
||||
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
// Live SST files = 0
|
||||
// Total SST files = 5 (used in 2 version)
|
||||
ASSERT_EQ(total_sst_files_size, 5 * single_file_size);
|
||||
|
||||
iter1.reset();
|
||||
iter2.reset();
|
||||
|
||||
ASSERT_TRUE(dbfull()->GetIntProperty("rocksdb.total-sst-files-size",
|
||||
&total_sst_files_size));
|
||||
// Live SST files = 0
|
||||
// Total SST files = 0
|
||||
ASSERT_EQ(total_sst_files_size, 0);
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
rocksdb::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -138,14 +138,14 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesInRange) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.write_buffer_size = 4096;
|
||||
options.max_write_buffer_number = 8;
|
||||
options.max_write_buffer_number = 3;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.level0_slowdown_writes_trigger = 2;
|
||||
options.level0_stop_writes_trigger = 4;
|
||||
options.target_file_size_base = 2048;
|
||||
options.max_bytes_for_level_base = 10240;
|
||||
options.max_bytes_for_level_multiplier = 4;
|
||||
options.soft_rate_limit = 1.1;
|
||||
options.hard_pending_compaction_bytes_limit = 16 * 1024;
|
||||
options.num_levels = 8;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
@@ -155,6 +155,12 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesInRange) {
|
||||
ASSERT_OK(Put(test::RandomKey(&rnd, 5), RandomString(&rnd, 102)));
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
if (NumTableFilesAtLevel(0) == 0) {
|
||||
ASSERT_OK(Put(test::RandomKey(&rnd, 5), RandomString(&rnd, 102)));
|
||||
Flush();
|
||||
}
|
||||
|
||||
db_->PauseBackgroundWork();
|
||||
|
||||
// Ensure that we have at least L0, L1 and L2
|
||||
@@ -215,6 +221,34 @@ TEST_F(DBTablePropertiesTest, GetPropertiesOfTablesInRange) {
|
||||
TestGetPropertiesOfTablesInRange(std::move(ranges));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBTablePropertiesTest, GetColumnFamilyNameProperty) {
|
||||
std::string kExtraCfName = "pikachu";
|
||||
CreateAndReopenWithCF({kExtraCfName}, CurrentOptions());
|
||||
|
||||
// Create one table per CF, then verify it was created with the column family
|
||||
// name property.
|
||||
for (int cf = 0; cf < 2; ++cf) {
|
||||
Put(cf, "key", "val");
|
||||
Flush(cf);
|
||||
|
||||
TablePropertiesCollection fname_to_props;
|
||||
ASSERT_OK(db_->GetPropertiesOfAllTables(handles_[cf], &fname_to_props));
|
||||
ASSERT_EQ(1U, fname_to_props.size());
|
||||
|
||||
std::string expected_cf_name;
|
||||
if (cf > 0) {
|
||||
expected_cf_name = kExtraCfName;
|
||||
} else {
|
||||
expected_cf_name = kDefaultColumnFamilyName;
|
||||
}
|
||||
ASSERT_EQ(expected_cf_name,
|
||||
fname_to_props.begin()->second->column_family_name);
|
||||
ASSERT_EQ(cf, static_cast<uint32_t>(
|
||||
fname_to_props.begin()->second->column_family_id));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
+428
-5564
File diff suppressed because it is too large.
Load diff
+2008
File diff suppressed because it is too large.
Load diff
+173
-9
@@ -30,6 +30,8 @@ SpecialEnv::SpecialEnv(Env* base)
|
||||
manifest_write_error_.store(false, std::memory_order_release);
|
||||
log_write_error_.store(false, std::memory_order_release);
|
||||
random_file_open_counter_.store(0, std::memory_order_relaxed);
|
||||
delete_count_.store(0, std::memory_order_relaxed);
|
||||
num_open_wal_file_.store(0);
|
||||
log_write_slowdown_ = 0;
|
||||
bytes_written_ = 0;
|
||||
sync_counter_ = 0;
|
||||
@@ -91,10 +93,6 @@ bool DBTestBase::ShouldSkipOptions(int option_config, int skip_mask) {
|
||||
}
|
||||
#endif
|
||||
|
||||
if ((skip_mask & kSkipDeletesFilterFirst) &&
|
||||
option_config == kDeletesFilterFirst) {
|
||||
return true;
|
||||
}
|
||||
if ((skip_mask & kSkipUniversalCompaction) &&
|
||||
(option_config == kUniversalCompaction ||
|
||||
option_config == kUniversalCompactionMultiLevel)) {
|
||||
@@ -210,6 +208,13 @@ Options DBTestBase::CurrentOptions(
|
||||
const anon::OptionsOverride& options_override) {
|
||||
Options options;
|
||||
options.write_buffer_size = 4090 * 4096;
|
||||
options.target_file_size_base = 2 * 1024 * 1024;
|
||||
options.max_bytes_for_level_base = 10 * 1024 * 1024;
|
||||
options.max_open_files = 5000;
|
||||
options.base_background_compactions = -1;
|
||||
options.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options.compaction_pri = CompactionPri::kByCompensatedSize;
|
||||
|
||||
return CurrentOptions(options, options_override);
|
||||
}
|
||||
|
||||
@@ -301,11 +306,9 @@ Options DBTestBase::CurrentOptions(
|
||||
case kPerfOptions:
|
||||
options.soft_rate_limit = 2.0;
|
||||
options.delayed_write_rate = 8 * 1024 * 1024;
|
||||
options.report_bg_io_stats = true;
|
||||
// TODO(3.13) -- test more options
|
||||
break;
|
||||
case kDeletesFilterFirst:
|
||||
options.filter_deletes = true;
|
||||
break;
|
||||
case kUniversalCompaction:
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = 1;
|
||||
@@ -441,7 +444,7 @@ void DBTestBase::Reopen(const Options& options) {
|
||||
|
||||
void DBTestBase::Close() {
|
||||
for (auto h : handles_) {
|
||||
delete h;
|
||||
db_->DestroyColumnFamilyHandle(h);
|
||||
}
|
||||
handles_.clear();
|
||||
delete db_;
|
||||
@@ -711,9 +714,25 @@ int DBTestBase::NumTableFilesAtLevel(int level, int cf) {
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
|
||||
double DBTestBase::CompressionRatioAtLevel(int level, int cf) {
|
||||
std::string property;
|
||||
if (cf == 0) {
|
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.compression-ratio-at-level" + NumberToString(level),
|
||||
&property));
|
||||
} else {
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
handles_[cf],
|
||||
"rocksdb.compression-ratio-at-level" + NumberToString(level),
|
||||
&property));
|
||||
}
|
||||
return std::stod(property);
|
||||
}
|
||||
|
||||
int DBTestBase::TotalTableFiles(int cf, int levels) {
|
||||
if (levels == -1) {
|
||||
levels = CurrentOptions().num_levels;
|
||||
levels = (cf == 0) ? db_->NumberLevels() : db_->NumberLevels(handles_[1]);
|
||||
}
|
||||
int result = 0;
|
||||
for (int level = 0; level < levels; level++) {
|
||||
@@ -1040,4 +1059,149 @@ std::unordered_map<std::string, uint64_t> DBTestBase::GetAllSSTFiles(
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<std::uint64_t> DBTestBase::ListTableFiles(Env* env,
|
||||
const std::string& path) {
|
||||
std::vector<std::string> files;
|
||||
std::vector<uint64_t> file_numbers;
|
||||
env->GetChildren(path, &files);
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
for (size_t i = 0; i < files.size(); ++i) {
|
||||
if (ParseFileName(files[i], &number, &type)) {
|
||||
if (type == kTableFile) {
|
||||
file_numbers.push_back(number);
|
||||
}
|
||||
}
|
||||
}
|
||||
return file_numbers;
|
||||
}
|
||||
|
||||
void DBTestBase::VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
size_t* total_reads_res) {
|
||||
size_t total_reads = 0;
|
||||
|
||||
for (auto& kv : true_data) {
|
||||
ASSERT_EQ(Get(kv.first), kv.second);
|
||||
total_reads++;
|
||||
}
|
||||
|
||||
// Normal Iterator
|
||||
{
|
||||
int iter_cnt = 0;
|
||||
ReadOptions ro;
|
||||
ro.total_order_seek = true;
|
||||
Iterator* iter = db_->NewIterator(ro);
|
||||
// Verify Iterator::Next()
|
||||
iter_cnt = 0;
|
||||
auto data_iter = true_data.begin();
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_iter->first);
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
ASSERT_EQ(data_iter, true_data.end()) << iter_cnt << " / "
|
||||
<< true_data.size();
|
||||
|
||||
// Verify Iterator::Prev()
|
||||
iter_cnt = 0;
|
||||
auto data_rev = true_data.rbegin();
|
||||
for (iter->SeekToLast(); iter->Valid(); iter->Prev(), data_rev++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_rev->first);
|
||||
ASSERT_EQ(iter->value().ToString(), data_rev->second);
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
ASSERT_EQ(data_rev, true_data.rend()) << iter_cnt << " / "
|
||||
<< true_data.size();
|
||||
|
||||
// Verify Iterator::Seek()
|
||||
for (auto kv : true_data) {
|
||||
iter->Seek(kv.first);
|
||||
ASSERT_EQ(kv.first, iter->key().ToString());
|
||||
ASSERT_EQ(kv.second, iter->value().ToString());
|
||||
total_reads++;
|
||||
}
|
||||
|
||||
delete iter;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Tailing iterator
|
||||
int iter_cnt = 0;
|
||||
ReadOptions ro;
|
||||
ro.tailing = true;
|
||||
ro.total_order_seek = true;
|
||||
Iterator* iter = db_->NewIterator(ro);
|
||||
|
||||
// Verify ForwardIterator::Next()
|
||||
iter_cnt = 0;
|
||||
auto data_iter = true_data.begin();
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next(), data_iter++) {
|
||||
ASSERT_EQ(iter->key().ToString(), data_iter->first);
|
||||
ASSERT_EQ(iter->value().ToString(), data_iter->second);
|
||||
iter_cnt++;
|
||||
total_reads++;
|
||||
}
|
||||
ASSERT_EQ(data_iter, true_data.end()) << iter_cnt << " / "
|
||||
<< true_data.size();
|
||||
|
||||
// Verify ForwardIterator::Seek()
|
||||
for (auto kv : true_data) {
|
||||
iter->Seek(kv.first);
|
||||
ASSERT_EQ(kv.first, iter->key().ToString());
|
||||
ASSERT_EQ(kv.second, iter->value().ToString());
|
||||
total_reads++;
|
||||
}
|
||||
|
||||
delete iter;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
if (total_reads_res) {
|
||||
*total_reads_res = total_reads;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
Status DBTestBase::GenerateAndAddExternalFile(const Options options,
|
||||
std::vector<int> keys,
|
||||
size_t file_id) {
|
||||
std::string file_path =
|
||||
test::TmpDir(env_) + "/sst_files/" + ToString(file_id);
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, options.comparator);
|
||||
|
||||
Status s = sst_file_writer.Open(file_path);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
for (auto& entry : keys) {
|
||||
std::string k = Key(entry);
|
||||
std::string v = k + ToString(file_id);
|
||||
s = sst_file_writer.Add(k, v);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
s = sst_file_writer.Finish();
|
||||
|
||||
if (s.ok()) {
|
||||
s = db_->AddFile(std::vector<std::string>(1, file_path));
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
uint64_t DBTestBase::GetNumberOfSstFilesForColumnFamily(
|
||||
DB* db, std::string column_family_name) {
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
db->GetLiveFilesMetaData(&metadata);
|
||||
uint64_t result = 0;
|
||||
for (auto& fileMetadata : metadata) {
|
||||
result += (fileMetadata.column_family_name == column_family_name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
+87
-19
@@ -39,6 +39,8 @@
|
||||
#include "rocksdb/filter_policy.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/sst_file_writer.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/utilities/checkpoint.h"
|
||||
#include "table/block_based_table_factory.h"
|
||||
@@ -219,7 +221,7 @@ class SpecialEnv : public EnvWrapper {
|
||||
// Drop writes on the floor
|
||||
return Status::OK();
|
||||
} else if (env_->no_space_.load(std::memory_order_acquire)) {
|
||||
return Status::IOError("No space left on device");
|
||||
return Status::NoSpace("No space left on device");
|
||||
} else {
|
||||
env_->bytes_written_ += data.size();
|
||||
return base_->Append(data);
|
||||
@@ -283,7 +285,10 @@ class SpecialEnv : public EnvWrapper {
|
||||
class WalFile : public WritableFile {
|
||||
public:
|
||||
WalFile(SpecialEnv* env, unique_ptr<WritableFile>&& b)
|
||||
: env_(env), base_(std::move(b)) {}
|
||||
: env_(env), base_(std::move(b)) {
|
||||
env_->num_open_wal_file_.fetch_add(1);
|
||||
}
|
||||
virtual ~WalFile() { env_->num_open_wal_file_.fetch_add(-1); }
|
||||
Status Append(const Slice& data) override {
|
||||
#if !(defined NDEBUG) || !defined(OS_WIN)
|
||||
TEST_SYNC_POINT("SpecialEnv::WalFile::Append:1");
|
||||
@@ -357,23 +362,30 @@ class SpecialEnv : public EnvWrapper {
|
||||
class CountingFile : public RandomAccessFile {
|
||||
public:
|
||||
CountingFile(unique_ptr<RandomAccessFile>&& target,
|
||||
anon::AtomicCounter* counter)
|
||||
: target_(std::move(target)), counter_(counter) {}
|
||||
anon::AtomicCounter* counter,
|
||||
std::atomic<size_t>* bytes_read)
|
||||
: target_(std::move(target)),
|
||||
counter_(counter),
|
||||
bytes_read_(bytes_read) {}
|
||||
virtual Status Read(uint64_t offset, size_t n, Slice* result,
|
||||
char* scratch) const override {
|
||||
counter_->Increment();
|
||||
return target_->Read(offset, n, result, scratch);
|
||||
Status s = target_->Read(offset, n, result, scratch);
|
||||
*bytes_read_ += result->size();
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
unique_ptr<RandomAccessFile> target_;
|
||||
anon::AtomicCounter* counter_;
|
||||
std::atomic<size_t>* bytes_read_;
|
||||
};
|
||||
|
||||
Status s = target()->NewRandomAccessFile(f, r, soptions);
|
||||
random_file_open_counter_++;
|
||||
if (s.ok() && count_random_reads_) {
|
||||
r->reset(new CountingFile(std::move(*r), &random_read_counter_));
|
||||
r->reset(new CountingFile(std::move(*r), &random_read_counter_,
|
||||
&random_read_bytes_counter_));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
@@ -434,6 +446,11 @@ class SpecialEnv : public EnvWrapper {
|
||||
addon_time_.load();
|
||||
}
|
||||
|
||||
virtual Status DeleteFile(const std::string& fname) override {
|
||||
delete_count_.fetch_add(1);
|
||||
return target()->DeleteFile(fname);
|
||||
}
|
||||
|
||||
Random rnd_;
|
||||
port::Mutex rnd_mutex_; // Lock to pretect rnd_
|
||||
|
||||
@@ -461,8 +478,12 @@ class SpecialEnv : public EnvWrapper {
|
||||
// Slow down every log write, in micro-seconds.
|
||||
std::atomic<int> log_write_slowdown_;
|
||||
|
||||
// Number of WAL files that are still open for write.
|
||||
std::atomic<int> num_open_wal_file_;
|
||||
|
||||
bool count_random_reads_;
|
||||
anon::AtomicCounter random_read_counter_;
|
||||
std::atomic<size_t> random_read_bytes_counter_;
|
||||
std::atomic<int> random_file_open_counter_;
|
||||
|
||||
bool count_sequential_reads_;
|
||||
@@ -484,6 +505,8 @@ class SpecialEnv : public EnvWrapper {
|
||||
|
||||
std::atomic<int64_t> addon_time_;
|
||||
|
||||
std::atomic<int> delete_count_;
|
||||
|
||||
bool time_elapse_only_sleep_;
|
||||
|
||||
bool no_sleep_;
|
||||
@@ -491,6 +514,33 @@ class SpecialEnv : public EnvWrapper {
|
||||
std::atomic<bool> is_wal_sync_thread_safe_{true};
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class OnFileDeletionListener : public EventListener {
|
||||
public:
|
||||
OnFileDeletionListener() : matched_count_(0), expected_file_name_("") {}
|
||||
|
||||
void SetExpectedFileName(const std::string file_name) {
|
||||
expected_file_name_ = file_name;
|
||||
}
|
||||
|
||||
void VerifyMatchedCount(size_t expected_value) {
|
||||
ASSERT_EQ(matched_count_, expected_value);
|
||||
}
|
||||
|
||||
void OnTableFileDeleted(const TableFileDeletionInfo& info) override {
|
||||
if (expected_file_name_ != "") {
|
||||
ASSERT_EQ(expected_file_name_, info.file_path);
|
||||
expected_file_name_ = "";
|
||||
matched_count_++;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
size_t matched_count_;
|
||||
std::string expected_file_name_;
|
||||
};
|
||||
#endif
|
||||
|
||||
class DBTestBase : public testing::Test {
|
||||
protected:
|
||||
// Sequence of option configurations to try
|
||||
@@ -514,19 +564,18 @@ class DBTestBase : public testing::Test {
|
||||
kWalDirAndMmapReads = 16,
|
||||
kManifestFileSize = 17,
|
||||
kPerfOptions = 18,
|
||||
kDeletesFilterFirst = 19,
|
||||
kHashSkipList = 20,
|
||||
kUniversalCompaction = 21,
|
||||
kUniversalCompactionMultiLevel = 22,
|
||||
kCompressedBlockCache = 23,
|
||||
kInfiniteMaxOpenFiles = 24,
|
||||
kxxHashChecksum = 25,
|
||||
kFIFOCompaction = 26,
|
||||
kOptimizeFiltersForHits = 27,
|
||||
kRowCache = 28,
|
||||
kRecycleLogFiles = 29,
|
||||
kConcurrentSkipList = 30,
|
||||
kEnd = 31,
|
||||
kHashSkipList = 19,
|
||||
kUniversalCompaction = 20,
|
||||
kUniversalCompactionMultiLevel = 21,
|
||||
kCompressedBlockCache = 22,
|
||||
kInfiniteMaxOpenFiles = 23,
|
||||
kxxHashChecksum = 24,
|
||||
kFIFOCompaction = 25,
|
||||
kOptimizeFiltersForHits = 26,
|
||||
kRowCache = 27,
|
||||
kRecycleLogFiles = 28,
|
||||
kConcurrentSkipList = 29,
|
||||
kEnd = 30,
|
||||
kLevelSubcompactions = 31,
|
||||
kUniversalSubcompactions = 32,
|
||||
kBlockBasedTableWithIndexRestartInterval = 33,
|
||||
@@ -672,6 +721,8 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
int NumTableFilesAtLevel(int level, int cf = 0);
|
||||
|
||||
double CompressionRatioAtLevel(int level, int cf = 0);
|
||||
|
||||
int TotalTableFiles(int cf = 0, int levels = -1);
|
||||
|
||||
// Return spread of files per level
|
||||
@@ -756,6 +807,23 @@ class DBTestBase : public testing::Test {
|
||||
|
||||
std::unordered_map<std::string, uint64_t> GetAllSSTFiles(
|
||||
uint64_t* total_size = nullptr);
|
||||
|
||||
std::vector<std::uint64_t> ListTableFiles(Env* env, const std::string& path);
|
||||
|
||||
void VerifyDBFromMap(std::map<std::string, std::string> true_data,
|
||||
size_t* total_reads_res = nullptr);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status GenerateAndAddExternalFile(const Options options,
|
||||
std::vector<int> keys, size_t file_id);
|
||||
|
||||
uint64_t GetNumberOfSstFilesForColumnFamily(DB* db,
|
||||
std::string column_family_name);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
uint64_t TestGetTickerCount(const Options& options, Tickers ticker_type) {
|
||||
return options.statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -14,11 +14,6 @@
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
static uint64_t TestGetTickerCount(const Options& options,
|
||||
Tickers ticker_type) {
|
||||
return options.statistics->getTickerCount(ticker_type);
|
||||
}
|
||||
|
||||
static std::string CompressibleString(Random* rnd, int len) {
|
||||
std::string r;
|
||||
test::CompressibleString(rnd, 0.8, len, &r);
|
||||
@@ -124,8 +119,7 @@ class DelayFilterFactory : public CompactionFilterFactory {
|
||||
// Make sure we don't trigger a problem if the trigger conditon is given
|
||||
// to be 0, which is invalid.
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionSingleSortedRun) {
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
Options options = CurrentOptions();
|
||||
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = num_levels_;
|
||||
@@ -133,7 +127,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSingleSortedRun) {
|
||||
options.level0_file_num_compaction_trigger = 0;
|
||||
options.compaction_options_universal.size_ratio = 10;
|
||||
options.compaction_options_universal.min_merge_width = 2;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 1;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 0;
|
||||
|
||||
options.write_buffer_size = 105 << 10; // 105KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
@@ -157,11 +151,13 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSingleSortedRun) {
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumSortedRuns(0), 1);
|
||||
}
|
||||
ASSERT_OK(Put(Key(key_idx), ""));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_EQ(NumSortedRuns(0), 1);
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, OptimizeFiltersForHits) {
|
||||
Options options;
|
||||
options = CurrentOptions(options);
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.num_levels = num_levels_;
|
||||
@@ -337,13 +333,12 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrigger) {
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionSizeAmplification) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -388,12 +383,12 @@ TEST_P(DBTestUniversalCompaction, CompactFilesOnUniversalCompaction) {
|
||||
ChangeCompactOptions();
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
|
||||
options.compaction_style = kCompactionStyleLevel;
|
||||
options.num_levels = 1;
|
||||
options.target_file_size_base = options.write_buffer_size;
|
||||
options.compression = kNoCompression;
|
||||
options = CurrentOptions(options);
|
||||
options.write_buffer_size = kEntrySize * kEntriesPerBuffer;
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_EQ(options.compaction_style, kCompactionStyleUniversal);
|
||||
Random rnd(301);
|
||||
@@ -451,12 +446,11 @@ TEST_P(DBTestUniversalCompaction, CompactFilesOnUniversalCompaction) {
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionTargetLevel) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.num_levels = 7;
|
||||
options.disable_auto_compactions = true;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Generate 3 overlapping files
|
||||
@@ -496,14 +490,13 @@ class DBTestUniversalCompactionMultiLevels
|
||||
};
|
||||
|
||||
TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionMultiLevels) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.level0_file_num_compaction_trigger = 8;
|
||||
options.max_background_compactions = 3;
|
||||
options.target_file_size_base = 32 * 1024;
|
||||
options = CurrentOptions(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
// Trigger compaction if size amplification exceeds 110%
|
||||
@@ -539,7 +532,7 @@ TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionTrivialMove) {
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.allow_trivial_move = true;
|
||||
options.num_levels = 3;
|
||||
@@ -547,7 +540,6 @@ TEST_P(DBTestUniversalCompactionMultiLevels, UniversalCompactionTrivialMove) {
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_background_compactions = 2;
|
||||
options.target_file_size_base = 32 * 1024;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -586,7 +578,7 @@ class DBTestUniversalCompactionParallel :
|
||||
};
|
||||
|
||||
TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 1 << 10; // 1KB
|
||||
@@ -595,7 +587,6 @@ TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) {
|
||||
options.max_background_flushes = 3;
|
||||
options.target_file_size_base = 1 * 1024;
|
||||
options.compaction_options_universal.max_size_amplification_percent = 110;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -646,13 +637,135 @@ TEST_P(DBTestUniversalCompactionParallel, UniversalCompactionParallel) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompactionParallel, PickByFileNumberBug) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.num_levels = num_levels_;
|
||||
options.write_buffer_size = 1 * 1024; // 1KB
|
||||
options.level0_file_num_compaction_trigger = 7;
|
||||
options.max_background_compactions = 2;
|
||||
options.target_file_size_base = 1024 * 1024; // 1MB
|
||||
|
||||
// Disable size amplifiction compaction
|
||||
options.compaction_options_universal.max_size_amplification_percent =
|
||||
UINT_MAX;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBTestUniversalCompactionParallel::PickByFileNumberBug:0",
|
||||
"BackgroundCallCompaction:0"},
|
||||
{"UniversalCompactionPicker::PickCompaction:Return",
|
||||
"DBTestUniversalCompactionParallel::PickByFileNumberBug:1"},
|
||||
{"DBTestUniversalCompactionParallel::PickByFileNumberBug:2",
|
||||
"CompactionJob::Run():Start"}});
|
||||
|
||||
int total_picked_compactions = 0;
|
||||
rocksdb::SyncPoint::GetInstance()->SetCallBack(
|
||||
"UniversalCompactionPicker::PickCompaction:Return", [&](void* arg) {
|
||||
if (arg) {
|
||||
total_picked_compactions++;
|
||||
}
|
||||
});
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Write 7 files to trigger compaction
|
||||
int key_idx = 1;
|
||||
for (int i = 1; i <= 70; i++) {
|
||||
std::string k = Key(key_idx++);
|
||||
ASSERT_OK(Put(k, k));
|
||||
if (i % 10 == 0) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the 1st background compaction process to start
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:0");
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:1");
|
||||
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
||||
|
||||
// Write 3 files while 1st compaction is held
|
||||
// These 3 files have different sizes to avoid compacting based on size_ratio
|
||||
int num_keys = 1000;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
for (int j = 1; j <= num_keys; j++) {
|
||||
std::string k = Key(key_idx++);
|
||||
ASSERT_OK(Put(k, k));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
num_keys -= 100;
|
||||
}
|
||||
|
||||
// Wait for the 2nd background compaction process to start
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:0");
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:1");
|
||||
|
||||
// Hold the 1st and 2nd compaction from finishing
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:2");
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
// Although 2 compaction threads started, the second one did not compact
|
||||
// anything because the number of files not being compacted is less than
|
||||
// level0_file_num_compaction_trigger
|
||||
EXPECT_EQ(total_picked_compactions, 1);
|
||||
EXPECT_EQ(TotalTableFiles(), 4);
|
||||
|
||||
// Stop SyncPoint and destroy the DB and reopen it again
|
||||
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
key_idx = 1;
|
||||
total_picked_compactions = 0;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Write 7 files to trigger compaction
|
||||
for (int i = 1; i <= 70; i++) {
|
||||
std::string k = Key(key_idx++);
|
||||
ASSERT_OK(Put(k, k));
|
||||
if (i % 10 == 0) {
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the 1st background compaction process to start
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:0");
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:1");
|
||||
rocksdb::SyncPoint::GetInstance()->ClearTrace();
|
||||
|
||||
// Write 8 files while 1st compaction is held
|
||||
// These 8 files have different sizes to avoid compacting based on size_ratio
|
||||
num_keys = 1000;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
for (int j = 1; j <= num_keys; j++) {
|
||||
std::string k = Key(key_idx++);
|
||||
ASSERT_OK(Put(k, k));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
num_keys -= 100;
|
||||
}
|
||||
|
||||
// Wait for the 2nd background compaction process to start
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:0");
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:1");
|
||||
|
||||
// Hold the 1st and 2nd compaction from finishing
|
||||
TEST_SYNC_POINT("DBTestUniversalCompactionParallel::PickByFileNumberBug:2");
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
|
||||
// This time we will trigger a compaction because of size ratio and
|
||||
// another compaction because of number of files that are not compacted
|
||||
// greater than 7
|
||||
EXPECT_GE(total_picked_compactions, 2);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBTestUniversalCompactionParallel,
|
||||
DBTestUniversalCompactionParallel,
|
||||
::testing::Combine(::testing::Values(1, 10),
|
||||
::testing::Bool()));
|
||||
::testing::Values(false)));
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionOptions) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 105 << 10; // 105KB
|
||||
options.arena_block_size = 4 << 10; // 4KB
|
||||
@@ -660,7 +773,6 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionOptions) {
|
||||
options.level0_file_num_compaction_trigger = 4;
|
||||
options.num_levels = num_levels_;
|
||||
options.compaction_options_universal.compression_size_percent = -1;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -773,14 +885,13 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionCompressRatio1) {
|
||||
return;
|
||||
}
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = num_levels_;
|
||||
options.compaction_options_universal.compression_size_percent = 70;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -841,14 +952,13 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionCompressRatio2) {
|
||||
if (!Snappy_Supported()) {
|
||||
return;
|
||||
}
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.write_buffer_size = 100 << 10; // 100KB
|
||||
options.target_file_size_base = 32 << 10; // 32KB
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = num_levels_;
|
||||
options.compaction_options_universal.compression_size_percent = 95;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Random rnd(301);
|
||||
@@ -884,7 +994,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest1) {
|
||||
});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.allow_trivial_move = true;
|
||||
options.num_levels = 2;
|
||||
@@ -892,7 +1002,6 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest1) {
|
||||
options.level0_file_num_compaction_trigger = 3;
|
||||
options.max_background_compactions = 1;
|
||||
options.target_file_size_base = 32 * 1024;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -929,7 +1038,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest2) {
|
||||
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.allow_trivial_move = true;
|
||||
options.num_levels = 15;
|
||||
@@ -937,7 +1046,6 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest2) {
|
||||
options.level0_file_num_compaction_trigger = 8;
|
||||
options.max_background_compactions = 4;
|
||||
options.target_file_size_base = 64 * 1024;
|
||||
options = CurrentOptions(options);
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
@@ -963,7 +1071,7 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionTrivialMoveTest2) {
|
||||
}
|
||||
|
||||
TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) {
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.db_paths.emplace_back(dbname_, 300 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 300 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_3", 500 * 1024);
|
||||
@@ -972,11 +1080,10 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) {
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.write_buffer_size = 110 << 10; // 105KB
|
||||
options.write_buffer_size = 111 << 10; // 114KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 1;
|
||||
options = CurrentOptions(options);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
env_->GetChildren(options.db_paths[1].path, &filenames);
|
||||
@@ -1011,13 +1118,13 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) {
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 2, 4)
|
||||
// (1, 2, 4) -> (3, 4)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 2, 4) -> (8)
|
||||
// (1, 3, 4) -> (8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
|
||||
@@ -1031,22 +1138,22 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionFourPaths) {
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
|
||||
// (1, 2, 8)
|
||||
// (1, 2, 8) -> (3, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 2, 8) -> (4, 8)
|
||||
// (1, 3, 8) -> (4, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
|
||||
// (1, 4, 8)
|
||||
// (1, 4, 8) -> (5, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[3].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[2].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
for (int i = 0; i < key_idx; i++) {
|
||||
auto v = Get(Key(i));
|
||||
@@ -1104,6 +1211,7 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
// each value is 10K
|
||||
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
@@ -1121,6 +1229,7 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
// each value is 10K
|
||||
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
@@ -1152,6 +1261,7 @@ TEST_P(DBTestUniversalCompaction, IncreaseUniversalCompactionNumLevels) {
|
||||
// each value is 10K
|
||||
ASSERT_OK(Put(1, Key(i), RandomString(&rnd, 10000)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[1]);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
@@ -1163,19 +1273,17 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) {
|
||||
if (!Snappy_Supported()) {
|
||||
return;
|
||||
}
|
||||
Options options;
|
||||
Options options = CurrentOptions();
|
||||
options.db_paths.emplace_back(dbname_, 500 * 1024);
|
||||
options.db_paths.emplace_back(dbname_ + "_2", 1024 * 1024 * 1024);
|
||||
options.compaction_style = kCompactionStyleUniversal;
|
||||
options.compaction_options_universal.size_ratio = 5;
|
||||
options.write_buffer_size = 110 << 10; // 105KB
|
||||
options.arena_block_size = 4 * 1024;
|
||||
options.write_buffer_size = 111 << 10; // 114KB
|
||||
options.arena_block_size = 4 << 10;
|
||||
options.level0_file_num_compaction_trigger = 2;
|
||||
options.num_levels = 1;
|
||||
options.memtable_factory.reset(
|
||||
new SpecialSkipListFactory(KNumKeysByGenerateNewFile - 1));
|
||||
options = CurrentOptions(options);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
env_->GetChildren(options.db_paths[1].path, &filenames);
|
||||
@@ -1209,12 +1317,12 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) {
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 2, 4)
|
||||
// (1, 2, 4) -> (3, 4)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(2, GetSstFileCount(dbname_));
|
||||
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 2, 4) -> (8)
|
||||
// (1, 3, 4) -> (8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
@@ -1229,20 +1337,20 @@ TEST_P(DBTestUniversalCompaction, UniversalCompactionSecondPathRatio) {
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 2, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(1, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(2, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 1, 2, 8) -> (4, 8)
|
||||
// (1, 2, 8) -> (3, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 4, 8)
|
||||
// (1, 3, 8) -> (4, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(1, GetSstFileCount(dbname_));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// (1, 4, 8) -> (5, 8)
|
||||
GenerateNewFile(&rnd, &key_idx);
|
||||
ASSERT_EQ(2, GetSstFileCount(options.db_paths[1].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
for (int i = 0; i < key_idx; i++) {
|
||||
auto v = Get(Key(i));
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/options_helper.h"
|
||||
#include "util/sync_point.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -127,6 +129,918 @@ TEST_F(DBWALTest, SyncWALNotWaitWrite) {
|
||||
ASSERT_EQ(Get("foo2"), "bar2");
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, Recover) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "baz", "v5"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
|
||||
ASSERT_EQ("v1", Get(1, "foo"));
|
||||
ASSERT_EQ("v5", Get(1, "baz"));
|
||||
ASSERT_OK(Put(1, "bar", "v2"));
|
||||
ASSERT_OK(Put(1, "foo", "v3"));
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_EQ("v3", Get(1, "foo"));
|
||||
ASSERT_OK(Put(1, "foo", "v4"));
|
||||
ASSERT_EQ("v4", Get(1, "foo"));
|
||||
ASSERT_EQ("v2", Get(1, "bar"));
|
||||
ASSERT_EQ("v5", Get(1, "baz"));
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithTableHandle) {
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "bar", "v2"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "foo", "v3"));
|
||||
ASSERT_OK(Put(1, "bar", "v4"));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "big", std::string(100, 'a')));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
|
||||
std::vector<std::vector<FileMetaData>> files;
|
||||
dbfull()->TEST_GetFilesMetaData(handles_[1], &files);
|
||||
size_t total_files = 0;
|
||||
for (const auto& level : files) {
|
||||
total_files += level.size();
|
||||
}
|
||||
ASSERT_EQ(total_files, 3);
|
||||
for (const auto& level : files) {
|
||||
for (const auto& file : level) {
|
||||
if (kInfiniteMaxOpenFiles == option_config_) {
|
||||
ASSERT_TRUE(file.table_reader_handle != nullptr);
|
||||
} else {
|
||||
ASSERT_TRUE(file.table_reader_handle == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, IgnoreRecoveredLog) {
|
||||
std::string backup_logs = dbname_ + "/backup_logs";
|
||||
|
||||
// delete old files in backup_logs directory
|
||||
env_->CreateDirIfMissing(backup_logs);
|
||||
std::vector<std::string> old_files;
|
||||
env_->GetChildren(backup_logs, &old_files);
|
||||
for (auto& file : old_files) {
|
||||
if (file != "." && file != "..") {
|
||||
env_->DeleteFile(backup_logs + "/" + file);
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.merge_operator = MergeOperators::CreateUInt64AddOperator();
|
||||
options.wal_dir = dbname_ + "/logs";
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// fill up the DB
|
||||
std::string one, two;
|
||||
PutFixed64(&one, 1);
|
||||
PutFixed64(&two, 2);
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one)));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Slice("foo"), Slice(one)));
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), Slice("bar"), Slice(one)));
|
||||
|
||||
// copy the logs to backup
|
||||
std::vector<std::string> logs;
|
||||
env_->GetChildren(options.wal_dir, &logs);
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(options.wal_dir + "/" + log, backup_logs + "/" + log);
|
||||
}
|
||||
}
|
||||
|
||||
// recover the DB
|
||||
Reopen(options);
|
||||
ASSERT_EQ(two, Get("foo"));
|
||||
ASSERT_EQ(one, Get("bar"));
|
||||
Close();
|
||||
|
||||
// copy the logs from backup back to wal dir
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
||||
}
|
||||
}
|
||||
// this should ignore the log files, recovery should not happen again
|
||||
// if the recovery happens, the same merge operator would be called twice,
|
||||
// leading to incorrect results
|
||||
Reopen(options);
|
||||
ASSERT_EQ(two, Get("foo"));
|
||||
ASSERT_EQ(one, Get("bar"));
|
||||
Close();
|
||||
Destroy(options);
|
||||
Reopen(options);
|
||||
Close();
|
||||
|
||||
// copy the logs from backup back to wal dir
|
||||
env_->CreateDirIfMissing(options.wal_dir);
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
||||
}
|
||||
}
|
||||
// assert that we successfully recovered only from logs, even though we
|
||||
// destroyed the DB
|
||||
Reopen(options);
|
||||
ASSERT_EQ(two, Get("foo"));
|
||||
ASSERT_EQ(one, Get("bar"));
|
||||
|
||||
// Recovery will fail if DB directory doesn't exist.
|
||||
Destroy(options);
|
||||
// copy the logs from backup back to wal dir
|
||||
env_->CreateDirIfMissing(options.wal_dir);
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(backup_logs + "/" + log, options.wal_dir + "/" + log);
|
||||
// we won't be needing this file no more
|
||||
env_->DeleteFile(backup_logs + "/" + log);
|
||||
}
|
||||
}
|
||||
Status s = TryReopen(options);
|
||||
ASSERT_TRUE(!s.ok());
|
||||
} while (ChangeOptions(kSkipHashCuckoo));
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoveryWithEmptyLog) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "foo", "v2"));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_OK(Put(1, "foo", "v3"));
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
ASSERT_EQ("v3", Get(1, "foo"));
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBWALTest, GetSortedWalFiles) {
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
VectorLogPtr log_files;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files));
|
||||
ASSERT_EQ(0, log_files.size());
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files));
|
||||
ASSERT_EQ(1, log_files.size());
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoveryWithLogDataForSomeCFs) {
|
||||
// Test for regression of WAL cleanup missing files that don't contain data
|
||||
// for every column family.
|
||||
do {
|
||||
CreateAndReopenWithCF({"pikachu"}, CurrentOptions());
|
||||
ASSERT_OK(Put(1, "foo", "v1"));
|
||||
ASSERT_OK(Put(1, "foo", "v2"));
|
||||
uint64_t earliest_log_nums[2];
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (i > 0) {
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, CurrentOptions());
|
||||
}
|
||||
VectorLogPtr log_files;
|
||||
ASSERT_OK(dbfull()->GetSortedWalFiles(log_files));
|
||||
if (log_files.size() > 0) {
|
||||
earliest_log_nums[i] = log_files[0]->LogNumber();
|
||||
} else {
|
||||
earliest_log_nums[i] = port::kMaxUint64;
|
||||
}
|
||||
}
|
||||
// Check at least the first WAL was cleaned up during the recovery.
|
||||
ASSERT_LT(earliest_log_nums[0], earliest_log_nums[1]);
|
||||
} while (ChangeOptions());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithLargeLog) {
|
||||
do {
|
||||
{
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
ASSERT_OK(Put(1, "big1", std::string(200000, '1')));
|
||||
ASSERT_OK(Put(1, "big2", std::string(200000, '2')));
|
||||
ASSERT_OK(Put(1, "small3", std::string(10, '3')));
|
||||
ASSERT_OK(Put(1, "small4", std::string(10, '4')));
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
}
|
||||
|
||||
// Make sure that if we re-open with a small write buffer size that
|
||||
// we flush table files in the middle of a large log file.
|
||||
Options options;
|
||||
options.write_buffer_size = 100000;
|
||||
options = CurrentOptions(options);
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 3);
|
||||
ASSERT_EQ(std::string(200000, '1'), Get(1, "big1"));
|
||||
ASSERT_EQ(std::string(200000, '2'), Get(1, "big2"));
|
||||
ASSERT_EQ(std::string(10, '3'), Get(1, "small3"));
|
||||
ASSERT_EQ(std::string(10, '4'), Get(1, "small4"));
|
||||
ASSERT_GT(NumTableFilesAtLevel(0, 1), 1);
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
// In https://reviews.facebook.net/D20661 we change
|
||||
// recovery behavior: previously for each log file each column family
|
||||
// memtable was flushed, even it was empty. Now it's changed:
|
||||
// we try to create the smallest number of table files by merging
|
||||
// updates from multiple logs
|
||||
TEST_F(DBWALTest, RecoverCheckFileAmountWithSmallWriteBuffer) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 5000000;
|
||||
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
||||
|
||||
// Since we will reopen DB with smaller write_buffer_size,
|
||||
// each key will go to new SST file
|
||||
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
||||
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
||||
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
||||
ASSERT_OK(Put(1, Key(10), DummyString(1000000)));
|
||||
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
||||
// Make 'dobrynia' to be flushed and new WAL file to be created
|
||||
ASSERT_OK(Put(2, Key(10), DummyString(7500000)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[2]);
|
||||
{
|
||||
auto tables = ListTableFiles(env_, dbname_);
|
||||
ASSERT_EQ(tables.size(), static_cast<size_t>(1));
|
||||
// Make sure 'dobrynia' was flushed: check sst files amount
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
// New WAL file
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1)));
|
||||
|
||||
options.write_buffer_size = 4096;
|
||||
options.arena_block_size = 4096;
|
||||
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
||||
options);
|
||||
{
|
||||
// No inserts => default is empty
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(0));
|
||||
// First 4 keys goes to separate SSTs + 1 more SST for 2 smaller keys
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(5));
|
||||
// 1 SST for big key + 1 SST for small one
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(2));
|
||||
// 1 SST for all keys
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
}
|
||||
|
||||
// In https://reviews.facebook.net/D20661 we change
|
||||
// recovery behavior: previously for each log file each column family
|
||||
// memtable was flushed, even it wasn't empty. Now it's changed:
|
||||
// we try to create the smallest number of table files by merging
|
||||
// updates from multiple logs
|
||||
TEST_F(DBWALTest, RecoverCheckFileAmount) {
|
||||
Options options = CurrentOptions();
|
||||
options.write_buffer_size = 100000;
|
||||
options.arena_block_size = 4 * 1024;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
CreateAndReopenWithCF({"pikachu", "dobrynia", "nikitich"}, options);
|
||||
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
|
||||
// Make 'nikitich' memtable to be flushed
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1002400)));
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
// 4 memtable are not flushed, 1 sst file
|
||||
{
|
||||
auto tables = ListTableFiles(env_, dbname_);
|
||||
ASSERT_EQ(tables.size(), static_cast<size_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
// Memtable for 'nikitich' has flushed, new WAL file has opened
|
||||
// 4 memtable still not flushed
|
||||
|
||||
// Write to new WAL file
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
|
||||
// Fill up 'nikitich' one more time
|
||||
ASSERT_OK(Put(3, Key(10), DummyString(1002400)));
|
||||
// make it flush
|
||||
ASSERT_OK(Put(3, Key(1), DummyString(1)));
|
||||
dbfull()->TEST_WaitForFlushMemTable(handles_[3]);
|
||||
// There are still 4 memtable not flushed, and 2 sst tables
|
||||
ASSERT_OK(Put(0, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(1, Key(1), DummyString(1)));
|
||||
ASSERT_OK(Put(2, Key(1), DummyString(1)));
|
||||
|
||||
{
|
||||
auto tables = ListTableFiles(env_, dbname_);
|
||||
ASSERT_EQ(tables.size(), static_cast<size_t>(2));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(2));
|
||||
}
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu", "dobrynia", "nikitich"},
|
||||
options);
|
||||
{
|
||||
std::vector<uint64_t> table_files = ListTableFiles(env_, dbname_);
|
||||
// Check, that records for 'default', 'dobrynia' and 'pikachu' from
|
||||
// first, second and third WALs went to the same SST.
|
||||
// So, there is 6 SSTs: three for 'nikitich', one for 'default', one for
|
||||
// 'dobrynia', one for 'pikachu'
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "default"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "nikitich"),
|
||||
static_cast<uint64_t>(3));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "dobrynia"),
|
||||
static_cast<uint64_t>(1));
|
||||
ASSERT_EQ(GetNumberOfSstFilesForColumnFamily(db_, "pikachu"),
|
||||
static_cast<uint64_t>(1));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, SyncMultipleLogs) {
|
||||
const uint64_t kNumBatches = 2;
|
||||
const int kBatchSize = 1000;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.write_buffer_size = 4096;
|
||||
Reopen(options);
|
||||
|
||||
WriteBatch batch;
|
||||
WriteOptions wo;
|
||||
wo.sync = true;
|
||||
|
||||
for (uint64_t b = 0; b < kNumBatches; b++) {
|
||||
batch.Clear();
|
||||
for (int i = 0; i < kBatchSize; i++) {
|
||||
batch.Put(Key(i), DummyString(128));
|
||||
}
|
||||
|
||||
dbfull()->Write(wo, &batch);
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SyncWAL());
|
||||
}
|
||||
|
||||
// Github issue 1339. Prior the fix we read sequence id from the first log to
|
||||
// a local variable, then keep increase the variable as we replay logs,
|
||||
// ignoring actual sequence id of the records. This is incorrect if some writes
|
||||
// come with WAL disabled.
|
||||
TEST_F(DBWALTest, PartOfWritesWithWALDisabled) {
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_env(
|
||||
new FaultInjectionTestEnv(env_));
|
||||
Options options = CurrentOptions();
|
||||
options.env = fault_env.get();
|
||||
options.disable_auto_compactions = true;
|
||||
// TODO(yiwu): fix for 2PC.
|
||||
options.allow_2pc = false;
|
||||
WriteOptions wal_on, wal_off;
|
||||
wal_on.sync = true;
|
||||
wal_on.disableWAL = false;
|
||||
wal_off.disableWAL = true;
|
||||
CreateAndReopenWithCF({"dummy"}, options);
|
||||
ASSERT_OK(Put(1, "dummy", "d1", wal_on)); // seq id 1
|
||||
ASSERT_OK(Put(1, "dummy", "d2", wal_off));
|
||||
ASSERT_OK(Put(1, "dummy", "d3", wal_off));
|
||||
ASSERT_OK(Put(0, "key", "v4", wal_on)); // seq id 4
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_OK(Put(0, "key", "v5", wal_on)); // seq id 5
|
||||
ASSERT_EQ("v5", Get(0, "key"));
|
||||
// Simulate a crash.
|
||||
fault_env->SetFilesystemActive(false);
|
||||
Close();
|
||||
fault_env->ResetState();
|
||||
ReopenWithColumnFamilies({"default", "dummy"}, options);
|
||||
// Prior to the fix, we may incorrectly recover "v5" with sequence id = 3.
|
||||
ASSERT_EQ("v5", Get(0, "key"));
|
||||
// Destroy DB before destruct fault_env.
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
//
|
||||
// Test WAL recovery for the various modes available
|
||||
//
|
||||
class RecoveryTestHelper {
|
||||
public:
|
||||
// Number of WAL files to generate
|
||||
static const int kWALFilesCount = 10;
|
||||
// Starting number for the WAL file name like 00010.log
|
||||
static const int kWALFileOffset = 10;
|
||||
// Keys to be written per WAL file
|
||||
static const int kKeysPerWALFile = 1024;
|
||||
// Size of the value
|
||||
static const int kValueSize = 10;
|
||||
|
||||
// Create WAL files with values filled in
|
||||
static void FillData(DBWALTest* test, const Options& options,
|
||||
const size_t wal_count, size_t* count) {
|
||||
const DBOptions& db_options = options;
|
||||
|
||||
*count = 0;
|
||||
|
||||
shared_ptr<Cache> table_cache = NewLRUCache(50000, 16);
|
||||
EnvOptions env_options;
|
||||
WriteBufferManager write_buffer_manager(db_options.db_write_buffer_size);
|
||||
|
||||
unique_ptr<VersionSet> versions;
|
||||
unique_ptr<WalManager> wal_manager;
|
||||
WriteController write_controller;
|
||||
|
||||
versions.reset(new VersionSet(test->dbname_, &db_options, env_options,
|
||||
table_cache.get(), &write_buffer_manager,
|
||||
&write_controller));
|
||||
|
||||
wal_manager.reset(new WalManager(db_options, env_options));
|
||||
|
||||
std::unique_ptr<log::Writer> current_log_writer;
|
||||
|
||||
for (size_t j = kWALFileOffset; j < wal_count + kWALFileOffset; j++) {
|
||||
uint64_t current_log_number = j;
|
||||
std::string fname = LogFileName(test->dbname_, current_log_number);
|
||||
unique_ptr<WritableFile> file;
|
||||
ASSERT_OK(db_options.env->NewWritableFile(fname, &file, env_options));
|
||||
unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), env_options));
|
||||
current_log_writer.reset(
|
||||
new log::Writer(std::move(file_writer), current_log_number,
|
||||
db_options.recycle_log_file_num > 0));
|
||||
|
||||
for (int i = 0; i < kKeysPerWALFile; i++) {
|
||||
std::string key = "key" + ToString((*count)++);
|
||||
std::string value = test->DummyString(kValueSize);
|
||||
assert(current_log_writer.get() != nullptr);
|
||||
uint64_t seq = versions->LastSequence() + 1;
|
||||
WriteBatch batch;
|
||||
batch.Put(key, value);
|
||||
WriteBatchInternal::SetSequence(&batch, seq);
|
||||
current_log_writer->AddRecord(WriteBatchInternal::Contents(&batch));
|
||||
versions->SetLastSequence(seq);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recreate and fill the store with some data
|
||||
static size_t FillData(DBWALTest* test, Options* options) {
|
||||
options->create_if_missing = true;
|
||||
test->DestroyAndReopen(*options);
|
||||
test->Close();
|
||||
|
||||
size_t count = 0;
|
||||
FillData(test, *options, kWALFilesCount, &count);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Read back all the keys we wrote and return the number of keys found
|
||||
static size_t GetData(DBWALTest* test) {
|
||||
size_t count = 0;
|
||||
for (size_t i = 0; i < kWALFilesCount * kKeysPerWALFile; i++) {
|
||||
if (test->Get("key" + ToString(i)) != "NOT_FOUND") {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
// Manuall corrupt the specified WAL
|
||||
static void CorruptWAL(DBWALTest* test, const Options& options,
|
||||
const double off, const double len,
|
||||
const int wal_file_id, const bool trunc = false) {
|
||||
Env* env = options.env;
|
||||
std::string fname = LogFileName(test->dbname_, wal_file_id);
|
||||
uint64_t size;
|
||||
ASSERT_OK(env->GetFileSize(fname, &size));
|
||||
ASSERT_GT(size, 0);
|
||||
#ifdef OS_WIN
|
||||
// Windows disk cache behaves differently. When we truncate
|
||||
// the original content is still in the cache due to the original
|
||||
// handle is still open. Generally, in Windows, one prohibits
|
||||
// shared access to files and it is not needed for WAL but we allow
|
||||
// it to induce corruption at various tests.
|
||||
test->Close();
|
||||
#endif
|
||||
if (trunc) {
|
||||
ASSERT_EQ(0, truncate(fname.c_str(), static_cast<int64_t>(size * off)));
|
||||
} else {
|
||||
InduceCorruption(fname, static_cast<size_t>(size * off),
|
||||
static_cast<size_t>(size * len));
|
||||
}
|
||||
}
|
||||
|
||||
// Overwrite data with 'a' from offset for length len
|
||||
static void InduceCorruption(const std::string& filename, size_t offset,
|
||||
size_t len) {
|
||||
ASSERT_GT(len, 0U);
|
||||
|
||||
int fd = open(filename.c_str(), O_RDWR);
|
||||
|
||||
// On windows long is 32-bit
|
||||
ASSERT_LE(offset, std::numeric_limits<long>::max());
|
||||
|
||||
ASSERT_GT(fd, 0);
|
||||
ASSERT_EQ(offset, lseek(fd, static_cast<long>(offset), SEEK_SET));
|
||||
|
||||
void* buf = alloca(len);
|
||||
memset(buf, 'a', len);
|
||||
ASSERT_EQ(len, write(fd, buf, static_cast<unsigned int>(len)));
|
||||
|
||||
close(fd);
|
||||
}
|
||||
};
|
||||
|
||||
// Test scope:
|
||||
// - We expect to open the data store when there is incomplete trailing writes
|
||||
// at the end of any of the logs
|
||||
// - We do not expect to open the data store for corruption
|
||||
TEST_F(DBWALTest, kTolerateCorruptedTailRecords) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
|
||||
for (auto trunc : {true, false}) { /* Corruption style */
|
||||
for (int i = 0; i < 4; i++) { /* Corruption offset position */
|
||||
for (int j = jstart; j < jend; j++) { /* WAL file */
|
||||
// Fill data for testing
|
||||
Options options = CurrentOptions();
|
||||
const size_t row_count = RecoveryTestHelper::FillData(this, &options);
|
||||
// test checksum failure or parsing
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, /*wal=*/j, trunc);
|
||||
|
||||
if (trunc) {
|
||||
options.wal_recovery_mode =
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
const size_t recovered_row_count = RecoveryTestHelper::GetData(this);
|
||||
ASSERT_TRUE(i == 0 || recovered_row_count > 0);
|
||||
ASSERT_LT(recovered_row_count, row_count);
|
||||
} else {
|
||||
options.wal_recovery_mode =
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
ASSERT_NOK(TryReopen(options));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test scope:
|
||||
// We don't expect the data store to be opened if there is any corruption
|
||||
// (leading, middle or trailing -- incomplete writes or corruption)
|
||||
TEST_F(DBWALTest, kAbsoluteConsistency) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
|
||||
// Verify clean slate behavior
|
||||
Options options = CurrentOptions();
|
||||
const size_t row_count = RecoveryTestHelper::FillData(this, &options);
|
||||
options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
ASSERT_EQ(RecoveryTestHelper::GetData(this), row_count);
|
||||
|
||||
for (auto trunc : {true, false}) { /* Corruption style */
|
||||
for (int i = 0; i < 4; i++) { /* Corruption offset position */
|
||||
if (trunc && i == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int j = jstart; j < jend; j++) { /* wal files */
|
||||
// fill with new date
|
||||
RecoveryTestHelper::FillData(this, &options);
|
||||
// corrupt the wal
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, j, trunc);
|
||||
// verify
|
||||
options.wal_recovery_mode = WALRecoveryMode::kAbsoluteConsistency;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_NOK(TryReopen(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
|
||||
TEST_F(DBWALTest, kPointInTimeRecovery) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
const int maxkeys =
|
||||
RecoveryTestHelper::kWALFilesCount * RecoveryTestHelper::kKeysPerWALFile;
|
||||
|
||||
for (auto trunc : {true, false}) { /* Corruption style */
|
||||
for (int i = 0; i < 4; i++) { /* Offset of corruption */
|
||||
for (int j = jstart; j < jend; j++) { /* WAL file */
|
||||
// Fill data for testing
|
||||
Options options = CurrentOptions();
|
||||
const size_t row_count = RecoveryTestHelper::FillData(this, &options);
|
||||
|
||||
// Corrupt the wal
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, j, trunc);
|
||||
|
||||
// Verify
|
||||
options.wal_recovery_mode = WALRecoveryMode::kPointInTimeRecovery;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// Probe data for invariants
|
||||
size_t recovered_row_count = RecoveryTestHelper::GetData(this);
|
||||
ASSERT_LT(recovered_row_count, row_count);
|
||||
|
||||
bool expect_data = true;
|
||||
for (size_t k = 0; k < maxkeys; ++k) {
|
||||
bool found = Get("key" + ToString(i)) != "NOT_FOUND";
|
||||
if (expect_data && !found) {
|
||||
expect_data = false;
|
||||
}
|
||||
ASSERT_EQ(found, expect_data);
|
||||
}
|
||||
|
||||
const size_t min = RecoveryTestHelper::kKeysPerWALFile *
|
||||
(j - RecoveryTestHelper::kWALFileOffset);
|
||||
ASSERT_GE(recovered_row_count, min);
|
||||
if (!trunc && i != 0) {
|
||||
const size_t max = RecoveryTestHelper::kKeysPerWALFile *
|
||||
(j - RecoveryTestHelper::kWALFileOffset + 1);
|
||||
ASSERT_LE(recovered_row_count, max);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test scope:
|
||||
// - We expect to open the data store under all scenarios
|
||||
// - We expect to have recovered records past the corruption zone
|
||||
TEST_F(DBWALTest, kSkipAnyCorruptedRecords) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
|
||||
for (auto trunc : {true, false}) { /* Corruption style */
|
||||
for (int i = 0; i < 4; i++) { /* Corruption offset */
|
||||
for (int j = jstart; j < jend; j++) { /* wal files */
|
||||
// Fill data for testing
|
||||
Options options = CurrentOptions();
|
||||
const size_t row_count = RecoveryTestHelper::FillData(this, &options);
|
||||
|
||||
// Corrupt the WAL
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, j, trunc);
|
||||
|
||||
// Verify behavior
|
||||
options.wal_recovery_mode = WALRecoveryMode::kSkipAnyCorruptedRecords;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// Probe data for invariants
|
||||
size_t recovered_row_count = RecoveryTestHelper::GetData(this);
|
||||
ASSERT_LT(recovered_row_count, row_count);
|
||||
|
||||
if (!trunc) {
|
||||
ASSERT_TRUE(i != 0 || recovered_row_count > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, AvoidFlushDuringRecovery) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
options.avoid_flush_during_recovery = false;
|
||||
|
||||
// Test with flush after recovery.
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "v1"));
|
||||
ASSERT_OK(Put("bar", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "v3"));
|
||||
ASSERT_OK(Put("bar", "v4"));
|
||||
ASSERT_EQ(1, TotalTableFiles());
|
||||
// Reopen DB. Check if WAL logs flushed.
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v3", Get("foo"));
|
||||
ASSERT_EQ("v4", Get("bar"));
|
||||
ASSERT_EQ(2, TotalTableFiles());
|
||||
|
||||
// Test without flush after recovery.
|
||||
options.avoid_flush_during_recovery = true;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "v5"));
|
||||
ASSERT_OK(Put("bar", "v6"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "v7"));
|
||||
ASSERT_OK(Put("bar", "v8"));
|
||||
ASSERT_EQ(1, TotalTableFiles());
|
||||
// Reopen DB. WAL logs should not be flushed this time.
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v7", Get("foo"));
|
||||
ASSERT_EQ("v8", Get("bar"));
|
||||
ASSERT_EQ(1, TotalTableFiles());
|
||||
|
||||
// Force flush with allow_2pc.
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.allow_2pc = true;
|
||||
ASSERT_OK(Put("foo", "v9"));
|
||||
ASSERT_OK(Put("bar", "v10"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("foo", "v11"));
|
||||
ASSERT_OK(Put("bar", "v12"));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v11", Get("foo"));
|
||||
ASSERT_EQ("v12", Get("bar"));
|
||||
ASSERT_EQ(2, TotalTableFiles());
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithoutFlush) {
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.create_if_missing = false;
|
||||
options.disable_auto_compactions = true;
|
||||
options.write_buffer_size = 64 * 1024 * 1024;
|
||||
|
||||
size_t count = RecoveryTestHelper::FillData(this, &options);
|
||||
auto validateData = [this, count]() {
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
ASSERT_NE(Get("key" + ToString(i)), "NOT_FOUND");
|
||||
}
|
||||
};
|
||||
Reopen(options);
|
||||
validateData();
|
||||
// Insert some data without flush
|
||||
ASSERT_OK(Put("foo", "foo_v1"));
|
||||
ASSERT_OK(Put("bar", "bar_v1"));
|
||||
Reopen(options);
|
||||
validateData();
|
||||
ASSERT_EQ(Get("foo"), "foo_v1");
|
||||
ASSERT_EQ(Get("bar"), "bar_v1");
|
||||
// Insert again and reopen
|
||||
ASSERT_OK(Put("foo", "foo_v2"));
|
||||
ASSERT_OK(Put("bar", "bar_v2"));
|
||||
Reopen(options);
|
||||
validateData();
|
||||
ASSERT_EQ(Get("foo"), "foo_v2");
|
||||
ASSERT_EQ(Get("bar"), "bar_v2");
|
||||
// manual flush and insert again
|
||||
Flush();
|
||||
ASSERT_EQ(Get("foo"), "foo_v2");
|
||||
ASSERT_EQ(Get("bar"), "bar_v2");
|
||||
ASSERT_OK(Put("foo", "foo_v3"));
|
||||
ASSERT_OK(Put("bar", "bar_v3"));
|
||||
Reopen(options);
|
||||
validateData();
|
||||
ASSERT_EQ(Get("foo"), "foo_v3");
|
||||
ASSERT_EQ(Get("bar"), "bar_v3");
|
||||
}
|
||||
|
||||
TEST_F(DBWALTest, RecoverWithoutFlushMultipleCF) {
|
||||
const std::string kSmallValue = "v";
|
||||
const std::string kLargeValue = DummyString(1024);
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.create_if_missing = false;
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
auto countWalFiles = [this]() {
|
||||
VectorLogPtr log_files;
|
||||
dbfull()->GetSortedWalFiles(log_files);
|
||||
return log_files.size();
|
||||
};
|
||||
|
||||
// Create DB with multiple column families and multiple log files.
|
||||
CreateAndReopenWithCF({"one", "two"}, options);
|
||||
ASSERT_OK(Put(0, "key1", kSmallValue));
|
||||
ASSERT_OK(Put(1, "key2", kLargeValue));
|
||||
Flush(1);
|
||||
ASSERT_EQ(1, countWalFiles());
|
||||
ASSERT_OK(Put(0, "key3", kSmallValue));
|
||||
ASSERT_OK(Put(2, "key4", kLargeValue));
|
||||
Flush(2);
|
||||
ASSERT_EQ(2, countWalFiles());
|
||||
|
||||
// Reopen, insert and flush.
|
||||
options.db_write_buffer_size = 64 * 1024 * 1024;
|
||||
ReopenWithColumnFamilies({"default", "one", "two"}, options);
|
||||
ASSERT_EQ(Get(0, "key1"), kSmallValue);
|
||||
ASSERT_EQ(Get(1, "key2"), kLargeValue);
|
||||
ASSERT_EQ(Get(0, "key3"), kSmallValue);
|
||||
ASSERT_EQ(Get(2, "key4"), kLargeValue);
|
||||
// Insert more data.
|
||||
ASSERT_OK(Put(0, "key5", kLargeValue));
|
||||
ASSERT_OK(Put(1, "key6", kLargeValue));
|
||||
ASSERT_EQ(3, countWalFiles());
|
||||
Flush(1);
|
||||
ASSERT_OK(Put(2, "key7", kLargeValue));
|
||||
ASSERT_EQ(4, countWalFiles());
|
||||
|
||||
// Reopen twice and validate.
|
||||
for (int i = 0; i < 2; i++) {
|
||||
ReopenWithColumnFamilies({"default", "one", "two"}, options);
|
||||
ASSERT_EQ(Get(0, "key1"), kSmallValue);
|
||||
ASSERT_EQ(Get(1, "key2"), kLargeValue);
|
||||
ASSERT_EQ(Get(0, "key3"), kSmallValue);
|
||||
ASSERT_EQ(Get(2, "key4"), kLargeValue);
|
||||
ASSERT_EQ(Get(0, "key5"), kLargeValue);
|
||||
ASSERT_EQ(Get(1, "key6"), kLargeValue);
|
||||
ASSERT_EQ(Get(2, "key7"), kLargeValue);
|
||||
ASSERT_EQ(4, countWalFiles());
|
||||
}
|
||||
}
|
||||
|
||||
// In this test we are trying to do the following:
|
||||
// 1. Create a DB with corrupted WAL log;
|
||||
// 2. Open with avoid_flush_during_recovery = true;
|
||||
// 3. Append more data without flushing, which creates new WAL log.
|
||||
// 4. Open again. See if it can correctly handle previous corruption.
|
||||
TEST_F(DBWALTest, RecoverFromCorruptedWALWithoutFlush) {
|
||||
const int jstart = RecoveryTestHelper::kWALFileOffset;
|
||||
const int jend = jstart + RecoveryTestHelper::kWALFilesCount;
|
||||
const int kAppendKeys = 100;
|
||||
Options options = CurrentOptions();
|
||||
options.avoid_flush_during_recovery = true;
|
||||
options.create_if_missing = false;
|
||||
options.disable_auto_compactions = true;
|
||||
options.write_buffer_size = 64 * 1024 * 1024;
|
||||
|
||||
auto getAll = [this]() {
|
||||
std::vector<std::pair<std::string, std::string>> data;
|
||||
ReadOptions ropt;
|
||||
Iterator* iter = dbfull()->NewIterator(ropt);
|
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
|
||||
data.push_back(
|
||||
std::make_pair(iter->key().ToString(), iter->value().ToString()));
|
||||
}
|
||||
delete iter;
|
||||
return data;
|
||||
};
|
||||
for (auto& mode : wal_recovery_mode_string_map) {
|
||||
options.wal_recovery_mode = mode.second;
|
||||
for (auto trunc : {true, false}) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = jstart; j < jend; j++) {
|
||||
// Create corrupted WAL
|
||||
RecoveryTestHelper::FillData(this, &options);
|
||||
RecoveryTestHelper::CorruptWAL(this, options, /*off=*/i * .3,
|
||||
/*len%=*/.1, /*wal=*/j, trunc);
|
||||
// Skip the test if DB won't open.
|
||||
if (!TryReopen(options).ok()) {
|
||||
ASSERT_TRUE(options.wal_recovery_mode ==
|
||||
WALRecoveryMode::kAbsoluteConsistency ||
|
||||
(!trunc &&
|
||||
options.wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords));
|
||||
continue;
|
||||
}
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// Append some more data.
|
||||
for (int k = 0; k < kAppendKeys; k++) {
|
||||
std::string key = "extra_key" + ToString(k);
|
||||
std::string value = DummyString(RecoveryTestHelper::kValueSize);
|
||||
ASSERT_OK(Put(key, value));
|
||||
}
|
||||
// Save data for comparison.
|
||||
auto data = getAll();
|
||||
// Reopen. Verify data.
|
||||
ASSERT_OK(TryReopen(options));
|
||||
auto actual_data = getAll();
|
||||
ASSERT_EQ(data, actual_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ namespace rocksdb {
|
||||
|
||||
uint64_t PackSequenceAndType(uint64_t seq, ValueType t) {
|
||||
assert(seq <= kMaxSequenceNumber);
|
||||
assert(IsValueType(t));
|
||||
assert(IsExtendedValueType(t));
|
||||
return (seq << 8) | t;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ void UnPackSequenceAndType(uint64_t packed, uint64_t* seq, ValueType* t) {
|
||||
*t = static_cast<ValueType>(packed & 0xff);
|
||||
|
||||
assert(*seq <= kMaxSequenceNumber);
|
||||
assert(IsValueType(*t));
|
||||
assert(IsExtendedValueType(*t));
|
||||
}
|
||||
|
||||
void AppendInternalKey(std::string* result, const ParsedInternalKey& key) {
|
||||
|
||||
+61
-4
@@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/filter_policy.h"
|
||||
@@ -39,6 +40,13 @@ enum ValueType : unsigned char {
|
||||
kTypeColumnFamilyMerge = 0x6, // WAL only.
|
||||
kTypeSingleDeletion = 0x7,
|
||||
kTypeColumnFamilySingleDeletion = 0x8, // WAL only.
|
||||
kTypeBeginPrepareXID = 0x9, // WAL only.
|
||||
kTypeEndPrepareXID = 0xA, // WAL only.
|
||||
kTypeCommitXID = 0xB, // WAL only.
|
||||
kTypeRollbackXID = 0xC, // WAL only.
|
||||
kTypeNoop = 0xD, // WAL only.
|
||||
kTypeColumnFamilyRangeDeletion = 0xE, // WAL only.
|
||||
kTypeRangeDeletion = 0xF, // meta block
|
||||
kMaxValue = 0x7F // Not used for storing records.
|
||||
};
|
||||
|
||||
@@ -50,12 +58,18 @@ enum ValueType : unsigned char {
|
||||
// ValueType, not the lowest).
|
||||
static const ValueType kValueTypeForSeek = kTypeSingleDeletion;
|
||||
|
||||
// Checks whether a type is a value type (i.e. a type used in memtables and sst
|
||||
// files).
|
||||
// Checks whether a type is an inline value type
|
||||
// (i.e. a type used in memtable skiplist and sst file datablock).
|
||||
inline bool IsValueType(ValueType t) {
|
||||
return t <= kTypeMerge || t == kTypeSingleDeletion;
|
||||
}
|
||||
|
||||
// Checks whether a type is from user operation
|
||||
// kTypeRangeDeletion is in meta block so this API is separated from above
|
||||
inline bool IsExtendedValueType(ValueType t) {
|
||||
return IsValueType(t) || t == kTypeRangeDeletion;
|
||||
}
|
||||
|
||||
// We leave eight bits empty at the bottom so a type and sequence#
|
||||
// can be packed together into 64-bits.
|
||||
static const SequenceNumber kMaxSequenceNumber =
|
||||
@@ -174,6 +188,10 @@ class InternalKey {
|
||||
Slice user_key() const { return ExtractUserKey(rep_); }
|
||||
size_t size() { return rep_.size(); }
|
||||
|
||||
void Set(const Slice& _user_key, SequenceNumber s, ValueType t) {
|
||||
SetFrom(ParsedInternalKey(_user_key, s, t));
|
||||
}
|
||||
|
||||
void SetFrom(const ParsedInternalKey& p) {
|
||||
rep_.clear();
|
||||
AppendInternalKey(&rep_, p);
|
||||
@@ -199,7 +217,7 @@ inline bool ParseInternalKey(const Slice& internal_key,
|
||||
result->type = static_cast<ValueType>(c);
|
||||
assert(result->type <= ValueType::kMaxValue);
|
||||
result->user_key = Slice(internal_key.data(), n - 8);
|
||||
return IsValueType(result->type);
|
||||
return IsExtendedValueType(result->type);
|
||||
}
|
||||
|
||||
// Update the sequence number in the internal key.
|
||||
@@ -465,6 +483,12 @@ class InternalKeySliceTransform : public SliceTransform {
|
||||
const SliceTransform* const transform_;
|
||||
};
|
||||
|
||||
// Read the key of a record from a write batch.
|
||||
// if this record represent the default column family then cf_record
|
||||
// must be passed as false, otherwise it must be passed as true.
|
||||
extern bool ReadKeyFromWriteBatchEntry(Slice* input, Slice* key,
|
||||
bool cf_record);
|
||||
|
||||
// Read record from a write batch piece from input.
|
||||
// tag, column_family, key, value and blob are return values. Callers own the
|
||||
// Slice they point to.
|
||||
@@ -472,5 +496,38 @@ class InternalKeySliceTransform : public SliceTransform {
|
||||
// input will be advanced to after the record.
|
||||
extern Status ReadRecordFromWriteBatch(Slice* input, char* tag,
|
||||
uint32_t* column_family, Slice* key,
|
||||
Slice* value, Slice* blob);
|
||||
Slice* value, Slice* blob, Slice* xid);
|
||||
|
||||
// When user call DeleteRange() to delete a range of keys,
|
||||
// we will store a serialized RangeTombstone in MemTable and SST.
|
||||
// the struct here is a easy-understood form
|
||||
// start/end_key_ is the start/end user key of the range to be deleted
|
||||
struct RangeTombstone {
|
||||
Slice start_key_;
|
||||
Slice end_key_;
|
||||
SequenceNumber seq_;
|
||||
explicit RangeTombstone(Slice sk, Slice ek, SequenceNumber sn)
|
||||
: start_key_(sk), end_key_(ek), seq_(sn) {}
|
||||
|
||||
explicit RangeTombstone(Slice internal_key, Slice value) {
|
||||
ParsedInternalKey parsed_key;
|
||||
if (ParseInternalKey(internal_key, &parsed_key)) {
|
||||
start_key_ = parsed_key.user_key;
|
||||
seq_ = parsed_key.sequence;
|
||||
end_key_ = value;
|
||||
}
|
||||
}
|
||||
|
||||
// be careful to use Serialize(); InternalKey() allocates new memory
|
||||
std::pair<InternalKey, Slice> Serialize() {
|
||||
auto key = InternalKey(start_key_, seq_, kTypeRangeDeletion);
|
||||
Slice value = end_key_;
|
||||
return std::make_pair(std::move(key), std::move(value));
|
||||
}
|
||||
|
||||
InternalKey SerializeKey() {
|
||||
return InternalKey(start_key_, seq_, kTypeRangeDeletion);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
@@ -92,6 +92,30 @@ TEST_F(FormatTest, InternalKeyShortSeparator) {
|
||||
Shorten(IKey("foo", 100, kTypeValue),
|
||||
IKey("hello", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(IKey("ABC2", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("ABC1AAAAA", 100, kTypeValue),
|
||||
IKey("ABC2ABB", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("AAA1AAA", 100, kTypeValue),
|
||||
IKey("AAA2AA", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(
|
||||
IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("AAA1AAA", 100, kTypeValue), IKey("AAA4", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(
|
||||
IKey("AAA1B", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("AAA1AAA", 100, kTypeValue), IKey("AAA2", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(IKey("AAA2", kMaxSequenceNumber, kValueTypeForSeek),
|
||||
Shorten(IKey("AAA1AAA", 100, kTypeValue),
|
||||
IKey("AAA2A", 200, kTypeValue)));
|
||||
|
||||
ASSERT_EQ(
|
||||
IKey("AAA1", 100, kTypeValue),
|
||||
Shorten(IKey("AAA1", 100, kTypeValue), IKey("AAA2", 200, kTypeValue)));
|
||||
|
||||
// When start user key is prefix of limit user key
|
||||
ASSERT_EQ(IKey("foo", 100, kTypeValue),
|
||||
Shorten(IKey("foo", 100, kTypeValue),
|
||||
|
||||
+130
-8
@@ -9,20 +9,21 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "rocksdb/db.h"
|
||||
#include <stdlib.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/transaction_log.h"
|
||||
#include <vector>
|
||||
#include <stdlib.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "util/string_util.h"
|
||||
#include "util/sync_point.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -152,6 +153,15 @@ class DeleteFileTest : public testing::Test {
|
||||
ASSERT_EQ(required_manifest, manifest_cnt);
|
||||
}
|
||||
|
||||
static void DoSleep(void* arg) {
|
||||
auto test = reinterpret_cast<DeleteFileTest*>(arg);
|
||||
test->env_->SleepForMicroseconds(2 * 1000 * 1000);
|
||||
}
|
||||
|
||||
// An empty job to guard all jobs are processed
|
||||
static void GuardFinish(void* arg) {
|
||||
TEST_SYNC_POINT("DeleteFileTest::GuardFinish");
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DeleteFileTest, AddKeysAndQueryLevels) {
|
||||
@@ -231,6 +241,118 @@ TEST_F(DeleteFileTest, PurgeObsoleteFilesTest) {
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeTest) {
|
||||
std::string first("0"), last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
Iterator* itr = 0;
|
||||
CreateTwoLevels();
|
||||
ReadOptions options;
|
||||
options.background_purge_on_iterator_cleanup = true;
|
||||
itr = db_->NewIterator(options);
|
||||
db_->CompactRange(compact_options, &first_slice, &last_slice);
|
||||
// 3 sst after compaction with live iterator
|
||||
CheckFileTypeCounts(dbname_, 0, 3, 1);
|
||||
test::SleepingBackgroundTask sleeping_task_before;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_task_before, Env::Priority::HIGH);
|
||||
delete itr;
|
||||
test::SleepingBackgroundTask sleeping_task_after;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_task_after, Env::Priority::HIGH);
|
||||
|
||||
// Make sure no purges are executed foreground
|
||||
CheckFileTypeCounts(dbname_, 0, 3, 1);
|
||||
sleeping_task_before.WakeUp();
|
||||
sleeping_task_before.WaitUntilDone();
|
||||
|
||||
// Make sure all background purges are executed
|
||||
sleeping_task_after.WakeUp();
|
||||
sleeping_task_after.WaitUntilDone();
|
||||
// 1 sst after iterator deletion
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
// This test is to reproduce a bug that read invalid ReadOption in iterator
|
||||
// cleanup function
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeCopyOptions) {
|
||||
std::string first("0"), last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
Iterator* itr = 0;
|
||||
CreateTwoLevels();
|
||||
ReadOptions* options = new ReadOptions();
|
||||
options->background_purge_on_iterator_cleanup = true;
|
||||
itr = db_->NewIterator(*options);
|
||||
// ReadOptions is deleted, but iterator cleanup function should not be
|
||||
// affected
|
||||
delete options;
|
||||
|
||||
db_->CompactRange(compact_options, &first_slice, &last_slice);
|
||||
// 3 sst after compaction with live iterator
|
||||
CheckFileTypeCounts(dbname_, 0, 3, 1);
|
||||
delete itr;
|
||||
|
||||
test::SleepingBackgroundTask sleeping_task_after;
|
||||
env_->Schedule(&test::SleepingBackgroundTask::DoSleepTask,
|
||||
&sleeping_task_after, Env::Priority::HIGH);
|
||||
|
||||
// Make sure all background purges are executed
|
||||
sleeping_task_after.WakeUp();
|
||||
sleeping_task_after.WaitUntilDone();
|
||||
// 1 sst after iterator deletion
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
|
||||
CloseDB();
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, BackgroundPurgeTestMultipleJobs) {
|
||||
std::string first("0"), last("999999");
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 2;
|
||||
Slice first_slice(first), last_slice(last);
|
||||
|
||||
// We keep an iterator alive
|
||||
CreateTwoLevels();
|
||||
ReadOptions options;
|
||||
options.background_purge_on_iterator_cleanup = true;
|
||||
Iterator* itr1 = db_->NewIterator(options);
|
||||
CreateTwoLevels();
|
||||
Iterator* itr2 = db_->NewIterator(options);
|
||||
db_->CompactRange(compact_options, &first_slice, &last_slice);
|
||||
// 5 sst files after 2 compactions with 2 live iterators
|
||||
CheckFileTypeCounts(dbname_, 0, 5, 1);
|
||||
|
||||
// ~DBImpl should wait until all BGWorkPurge are finished
|
||||
rocksdb::SyncPoint::GetInstance()->LoadDependency(
|
||||
{{"DBImpl::~DBImpl:WaitJob", "DBImpl::BGWorkPurge"},
|
||||
{"DeleteFileTest::GuardFinish",
|
||||
"DeleteFileTest::BackgroundPurgeTestMultipleJobs:DBClose"}});
|
||||
rocksdb::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
delete itr1;
|
||||
env_->Schedule(&DeleteFileTest::DoSleep, this, Env::Priority::HIGH);
|
||||
delete itr2;
|
||||
env_->Schedule(&DeleteFileTest::GuardFinish, nullptr, Env::Priority::HIGH);
|
||||
CloseDB();
|
||||
|
||||
TEST_SYNC_POINT("DeleteFileTest::BackgroundPurgeTestMultipleJobs:DBClose");
|
||||
// 1 sst after iterator deletion
|
||||
CheckFileTypeCounts(dbname_, 0, 1, 1);
|
||||
rocksdb::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
TEST_F(DeleteFileTest, DeleteFileWithIterator) {
|
||||
CreateTwoLevels();
|
||||
ReadOptions options;
|
||||
|
||||
+64
-38
@@ -20,57 +20,83 @@ void EventHelpers::AppendCurrentTime(JSONWriter* jwriter) {
|
||||
std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
void EventHelpers::LogAndNotifyTableFileCreation(
|
||||
#ifndef ROCKSDB_LITE
|
||||
void EventHelpers::NotifyTableFileCreationStarted(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const std::string& db_name, const std::string& cf_name,
|
||||
const std::string& file_path, int job_id, TableFileCreationReason reason) {
|
||||
TableFileCreationBriefInfo info;
|
||||
info.db_name = db_name;
|
||||
info.cf_name = cf_name;
|
||||
info.file_path = file_path;
|
||||
info.job_id = job_id;
|
||||
info.reason = reason;
|
||||
for (auto& listener : listeners) {
|
||||
listener->OnTableFileCreationStarted(info);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
void EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const FileDescriptor& fd, const TableFileCreationInfo& info) {
|
||||
assert(event_logger);
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
jwriter << "cf_name" << info.cf_name
|
||||
<< "job" << info.job_id
|
||||
<< "event" << "table_file_creation"
|
||||
<< "file_number" << fd.GetNumber()
|
||||
<< "file_size" << fd.GetFileSize();
|
||||
const std::string& db_name, const std::string& cf_name,
|
||||
const std::string& file_path, int job_id, const FileDescriptor& fd,
|
||||
const TableProperties& table_properties, TableFileCreationReason reason,
|
||||
const Status& s) {
|
||||
if (s.ok() && event_logger) {
|
||||
JSONWriter jwriter;
|
||||
AppendCurrentTime(&jwriter);
|
||||
jwriter << "cf_name" << cf_name << "job" << job_id << "event"
|
||||
<< "table_file_creation"
|
||||
<< "file_number" << fd.GetNumber() << "file_size"
|
||||
<< fd.GetFileSize();
|
||||
|
||||
// table_properties
|
||||
{
|
||||
jwriter << "table_properties";
|
||||
jwriter.StartObject();
|
||||
// table_properties
|
||||
{
|
||||
jwriter << "table_properties";
|
||||
jwriter.StartObject();
|
||||
|
||||
// basic properties:
|
||||
jwriter << "data_size" << info.table_properties.data_size
|
||||
<< "index_size" << info.table_properties.index_size
|
||||
<< "filter_size" << info.table_properties.filter_size
|
||||
<< "raw_key_size" << info.table_properties.raw_key_size
|
||||
<< "raw_average_key_size" << SafeDivide(
|
||||
info.table_properties.raw_key_size,
|
||||
info.table_properties.num_entries)
|
||||
<< "raw_value_size" << info.table_properties.raw_value_size
|
||||
<< "raw_average_value_size" << SafeDivide(
|
||||
info.table_properties.raw_value_size,
|
||||
info.table_properties.num_entries)
|
||||
<< "num_data_blocks" << info.table_properties.num_data_blocks
|
||||
<< "num_entries" << info.table_properties.num_entries
|
||||
<< "filter_policy_name" <<
|
||||
info.table_properties.filter_policy_name;
|
||||
// basic properties:
|
||||
jwriter << "data_size" << table_properties.data_size << "index_size"
|
||||
<< table_properties.index_size << "filter_size"
|
||||
<< table_properties.filter_size << "raw_key_size"
|
||||
<< table_properties.raw_key_size << "raw_average_key_size"
|
||||
<< SafeDivide(table_properties.raw_key_size,
|
||||
table_properties.num_entries)
|
||||
<< "raw_value_size" << table_properties.raw_value_size
|
||||
<< "raw_average_value_size"
|
||||
<< SafeDivide(table_properties.raw_value_size,
|
||||
table_properties.num_entries)
|
||||
<< "num_data_blocks" << table_properties.num_data_blocks
|
||||
<< "num_entries" << table_properties.num_entries
|
||||
<< "filter_policy_name" << table_properties.filter_policy_name;
|
||||
|
||||
// user collected properties
|
||||
for (const auto& prop : info.table_properties.readable_properties) {
|
||||
jwriter << prop.first << prop.second;
|
||||
// user collected properties
|
||||
for (const auto& prop : table_properties.readable_properties) {
|
||||
jwriter << prop.first << prop.second;
|
||||
}
|
||||
jwriter.EndObject();
|
||||
}
|
||||
jwriter.EndObject();
|
||||
}
|
||||
jwriter.EndObject();
|
||||
|
||||
event_logger->Log(jwriter);
|
||||
event_logger->Log(jwriter);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (listeners.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto listener : listeners) {
|
||||
TableFileCreationInfo info;
|
||||
info.db_name = db_name;
|
||||
info.cf_name = cf_name;
|
||||
info.file_path = file_path;
|
||||
info.file_size = fd.file_size;
|
||||
info.job_id = job_id;
|
||||
info.table_properties = table_properties;
|
||||
info.reason = reason;
|
||||
info.status = s;
|
||||
for (auto& listener : listeners) {
|
||||
listener->OnTableFileCreated(info);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
+17
-2
@@ -19,15 +19,30 @@ namespace rocksdb {
|
||||
class EventHelpers {
|
||||
public:
|
||||
static void AppendCurrentTime(JSONWriter* json_writer);
|
||||
static void LogAndNotifyTableFileCreation(
|
||||
#ifndef ROCKSDB_LITE
|
||||
static void NotifyTableFileCreationStarted(
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const std::string& db_name, const std::string& cf_name,
|
||||
const std::string& file_path, int job_id, TableFileCreationReason reason);
|
||||
#endif // !ROCKSDB_LITE
|
||||
static void LogAndNotifyTableFileCreationFinished(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const FileDescriptor& fd, const TableFileCreationInfo& info);
|
||||
const std::string& db_name, const std::string& cf_name,
|
||||
const std::string& file_path, int job_id, const FileDescriptor& fd,
|
||||
const TableProperties& table_properties, TableFileCreationReason reason,
|
||||
const Status& s);
|
||||
static void LogAndNotifyTableFileDeletion(
|
||||
EventLogger* event_logger, int job_id,
|
||||
uint64_t file_number, const std::string& file_path,
|
||||
const Status& status, const std::string& db_name,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners);
|
||||
|
||||
private:
|
||||
static void LogAndNotifyTableFileCreation(
|
||||
EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const FileDescriptor& fd, const TableFileCreationInfo& info);
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
File diff suppressed because it is too large.
Load diff
+4
-398
@@ -11,8 +11,6 @@
|
||||
// the last "sync". It then checks for data loss errors by purposely dropping
|
||||
// file data (or entire files) not protected by a "sync".
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include "db/db_impl.h"
|
||||
#include "db/filename.h"
|
||||
#include "db/log_format.h"
|
||||
@@ -22,6 +20,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "util/fault_injection_test_env.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/mock_env.h"
|
||||
#include "util/mutexlock.h"
|
||||
@@ -35,401 +34,6 @@ static const int kValueSize = 1000;
|
||||
static const int kMaxNumValues = 2000;
|
||||
static const size_t kNumIterations = 3;
|
||||
|
||||
class TestWritableFile;
|
||||
class FaultInjectionTestEnv;
|
||||
|
||||
namespace {
|
||||
|
||||
// Assume a filename, and not a directory name like "/foo/bar/"
|
||||
static std::string GetDirName(const std::string filename) {
|
||||
size_t found = filename.find_last_of("/\\");
|
||||
if (found == std::string::npos) {
|
||||
return "";
|
||||
} else {
|
||||
return filename.substr(0, found);
|
||||
}
|
||||
}
|
||||
|
||||
// Trim the tailing "/" in the end of `str`
|
||||
static std::string TrimDirname(const std::string& str) {
|
||||
size_t found = str.find_last_not_of("/");
|
||||
if (found == std::string::npos) {
|
||||
return str;
|
||||
}
|
||||
return str.substr(0, found + 1);
|
||||
}
|
||||
|
||||
// Return pair <parent directory name, file name> of a full path.
|
||||
static std::pair<std::string, std::string> GetDirAndName(
|
||||
const std::string& name) {
|
||||
std::string dirname = GetDirName(name);
|
||||
std::string fname = name.substr(dirname.size() + 1);
|
||||
return std::make_pair(dirname, fname);
|
||||
}
|
||||
|
||||
// A basic file truncation function suitable for this test.
|
||||
Status Truncate(Env* env, const std::string& filename, uint64_t length) {
|
||||
unique_ptr<SequentialFile> orig_file;
|
||||
const EnvOptions options;
|
||||
Status s = env->NewSequentialFile(filename, &orig_file, options);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(),
|
||||
s.ToString().c_str());
|
||||
return s;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> scratch(new char[length]);
|
||||
rocksdb::Slice result;
|
||||
s = orig_file->Read(length, &result, scratch.get());
|
||||
#ifdef OS_WIN
|
||||
orig_file.reset();
|
||||
#endif
|
||||
if (s.ok()) {
|
||||
std::string tmp_name = GetDirName(filename) + "/truncate.tmp";
|
||||
unique_ptr<WritableFile> tmp_file;
|
||||
s = env->NewWritableFile(tmp_name, &tmp_file, options);
|
||||
if (s.ok()) {
|
||||
s = tmp_file->Append(result);
|
||||
if (s.ok()) {
|
||||
s = env->RenameFile(tmp_name, filename);
|
||||
} else {
|
||||
fprintf(stderr, "Cannot rename file %s to %s: %s\n", tmp_name.c_str(),
|
||||
filename.c_str(), s.ToString().c_str());
|
||||
env->DeleteFile(tmp_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Cannot truncate file %s: %s\n", filename.c_str(),
|
||||
s.ToString().c_str());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
struct FileState {
|
||||
std::string filename_;
|
||||
ssize_t pos_;
|
||||
ssize_t pos_at_last_sync_;
|
||||
ssize_t pos_at_last_flush_;
|
||||
|
||||
explicit FileState(const std::string& filename)
|
||||
: filename_(filename),
|
||||
pos_(-1),
|
||||
pos_at_last_sync_(-1),
|
||||
pos_at_last_flush_(-1) { }
|
||||
|
||||
FileState() : pos_(-1), pos_at_last_sync_(-1), pos_at_last_flush_(-1) {}
|
||||
|
||||
bool IsFullySynced() const { return pos_ <= 0 || pos_ == pos_at_last_sync_; }
|
||||
|
||||
Status DropUnsyncedData(Env* env) const;
|
||||
|
||||
Status DropRandomUnsyncedData(Env* env, Random* rand) const;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// A wrapper around WritableFileWriter* file
|
||||
// is written to or sync'ed.
|
||||
class TestWritableFile : public WritableFile {
|
||||
public:
|
||||
explicit TestWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>&& f,
|
||||
FaultInjectionTestEnv* env);
|
||||
virtual ~TestWritableFile();
|
||||
virtual Status Append(const Slice& data) override;
|
||||
virtual Status Truncate(uint64_t size) override { return target_->Truncate(size); }
|
||||
virtual Status Close() override;
|
||||
virtual Status Flush() override;
|
||||
virtual Status Sync() override;
|
||||
virtual bool IsSyncThreadSafe() const override { return true; }
|
||||
|
||||
private:
|
||||
FileState state_;
|
||||
unique_ptr<WritableFile> target_;
|
||||
bool writable_file_opened_;
|
||||
FaultInjectionTestEnv* env_;
|
||||
};
|
||||
|
||||
class TestDirectory : public Directory {
|
||||
public:
|
||||
explicit TestDirectory(FaultInjectionTestEnv* env, std::string dirname,
|
||||
Directory* dir)
|
||||
: env_(env), dirname_(dirname), dir_(dir) {}
|
||||
~TestDirectory() {}
|
||||
|
||||
virtual Status Fsync() override;
|
||||
|
||||
private:
|
||||
FaultInjectionTestEnv* env_;
|
||||
std::string dirname_;
|
||||
unique_ptr<Directory> dir_;
|
||||
};
|
||||
|
||||
class FaultInjectionTestEnv : public EnvWrapper {
|
||||
public:
|
||||
explicit FaultInjectionTestEnv(Env* base)
|
||||
: EnvWrapper(base),
|
||||
filesystem_active_(true) {}
|
||||
virtual ~FaultInjectionTestEnv() { }
|
||||
|
||||
Status NewDirectory(const std::string& name,
|
||||
unique_ptr<Directory>* result) override {
|
||||
unique_ptr<Directory> r;
|
||||
Status s = target()->NewDirectory(name, &r);
|
||||
EXPECT_OK(s);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
result->reset(new TestDirectory(this, TrimDirname(name), r.release()));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& soptions) override {
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
// Not allow overwriting files
|
||||
Status s = target()->FileExists(fname);
|
||||
if (s.ok()) {
|
||||
return Status::Corruption("File already exists.");
|
||||
} else if (!s.IsNotFound()) {
|
||||
assert(s.IsIOError());
|
||||
return s;
|
||||
}
|
||||
s = target()->NewWritableFile(fname, result, soptions);
|
||||
if (s.ok()) {
|
||||
result->reset(new TestWritableFile(fname, std::move(*result), this));
|
||||
// WritableFileWriter* file is opened
|
||||
// again then it will be truncated - so forget our saved state.
|
||||
UntrackFile(fname);
|
||||
MutexLock l(&mutex_);
|
||||
open_files_.insert(fname);
|
||||
auto dir_and_name = GetDirAndName(fname);
|
||||
auto& list = dir_to_new_files_since_last_sync_[dir_and_name.first];
|
||||
list.insert(dir_and_name.second);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status DeleteFile(const std::string& f) override {
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status s = EnvWrapper::DeleteFile(f);
|
||||
if (!s.ok()) {
|
||||
fprintf(stderr, "Cannot delete file %s: %s\n", f.c_str(),
|
||||
s.ToString().c_str());
|
||||
}
|
||||
EXPECT_OK(s);
|
||||
if (s.ok()) {
|
||||
UntrackFile(f);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
virtual Status RenameFile(const std::string& s,
|
||||
const std::string& t) override {
|
||||
if (!IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status ret = EnvWrapper::RenameFile(s, t);
|
||||
|
||||
if (ret.ok()) {
|
||||
MutexLock l(&mutex_);
|
||||
if (db_file_state_.find(s) != db_file_state_.end()) {
|
||||
db_file_state_[t] = db_file_state_[s];
|
||||
db_file_state_.erase(s);
|
||||
}
|
||||
|
||||
auto sdn = GetDirAndName(s);
|
||||
auto tdn = GetDirAndName(t);
|
||||
if (dir_to_new_files_since_last_sync_[sdn.first].erase(sdn.second) != 0) {
|
||||
auto& tlist = dir_to_new_files_since_last_sync_[tdn.first];
|
||||
assert(tlist.find(tdn.second) == tlist.end());
|
||||
tlist.insert(tdn.second);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void WritableFileClosed(const FileState& state) {
|
||||
MutexLock l(&mutex_);
|
||||
if (open_files_.find(state.filename_) != open_files_.end()) {
|
||||
db_file_state_[state.filename_] = state;
|
||||
open_files_.erase(state.filename_);
|
||||
}
|
||||
}
|
||||
|
||||
// For every file that is not fully synced, make a call to `func` with
|
||||
// FileState of the file as the parameter.
|
||||
Status DropFileData(std::function<Status(Env*, FileState)> func) {
|
||||
Status s;
|
||||
MutexLock l(&mutex_);
|
||||
for (std::map<std::string, FileState>::const_iterator it =
|
||||
db_file_state_.begin();
|
||||
s.ok() && it != db_file_state_.end(); ++it) {
|
||||
const FileState& state = it->second;
|
||||
if (!state.IsFullySynced()) {
|
||||
s = func(target(), state);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status DropUnsyncedFileData() {
|
||||
return DropFileData([&](Env* env, const FileState& state) {
|
||||
return state.DropUnsyncedData(env);
|
||||
});
|
||||
}
|
||||
|
||||
Status DropRandomUnsyncedFileData(Random* rnd) {
|
||||
return DropFileData([&](Env* env, const FileState& state) {
|
||||
return state.DropRandomUnsyncedData(env, rnd);
|
||||
});
|
||||
}
|
||||
|
||||
Status DeleteFilesCreatedAfterLastDirSync() {
|
||||
// Because DeleteFile access this container make a copy to avoid deadlock
|
||||
std::map<std::string, std::set<std::string>> map_copy;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
map_copy.insert(dir_to_new_files_since_last_sync_.begin(),
|
||||
dir_to_new_files_since_last_sync_.end());
|
||||
}
|
||||
|
||||
for (auto& pair : map_copy) {
|
||||
for (std::string name : pair.second) {
|
||||
Status s = DeleteFile(pair.first + "/" + name);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
void ResetState() {
|
||||
MutexLock l(&mutex_);
|
||||
db_file_state_.clear();
|
||||
dir_to_new_files_since_last_sync_.clear();
|
||||
SetFilesystemActiveNoLock(true);
|
||||
}
|
||||
|
||||
void UntrackFile(const std::string& f) {
|
||||
MutexLock l(&mutex_);
|
||||
auto dir_and_name = GetDirAndName(f);
|
||||
dir_to_new_files_since_last_sync_[dir_and_name.first].erase(
|
||||
dir_and_name.second);
|
||||
db_file_state_.erase(f);
|
||||
open_files_.erase(f);
|
||||
}
|
||||
|
||||
void SyncDir(const std::string& dirname) {
|
||||
MutexLock l(&mutex_);
|
||||
dir_to_new_files_since_last_sync_.erase(dirname);
|
||||
}
|
||||
|
||||
// Setting the filesystem to inactive is the test equivalent to simulating a
|
||||
// system reset. Setting to inactive will freeze our saved filesystem state so
|
||||
// that it will stop being recorded. It can then be reset back to the state at
|
||||
// the time of the reset.
|
||||
bool IsFilesystemActive() {
|
||||
MutexLock l(&mutex_);
|
||||
return filesystem_active_;
|
||||
}
|
||||
void SetFilesystemActiveNoLock(bool active) { filesystem_active_ = active; }
|
||||
void SetFilesystemActive(bool active) {
|
||||
MutexLock l(&mutex_);
|
||||
SetFilesystemActiveNoLock(active);
|
||||
}
|
||||
void AssertNoOpenFile() { ASSERT_TRUE(open_files_.empty()); }
|
||||
|
||||
private:
|
||||
port::Mutex mutex_;
|
||||
std::map<std::string, FileState> db_file_state_;
|
||||
std::set<std::string> open_files_;
|
||||
std::unordered_map<std::string, std::set<std::string>>
|
||||
dir_to_new_files_since_last_sync_;
|
||||
bool filesystem_active_; // Record flushes, syncs, writes
|
||||
};
|
||||
|
||||
Status FileState::DropUnsyncedData(Env* env) const {
|
||||
ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
|
||||
return Truncate(env, filename_, sync_pos);
|
||||
}
|
||||
|
||||
Status FileState::DropRandomUnsyncedData(Env* env, Random* rand) const {
|
||||
ssize_t sync_pos = pos_at_last_sync_ == -1 ? 0 : pos_at_last_sync_;
|
||||
assert(pos_ >= sync_pos);
|
||||
int range = static_cast<int>(pos_ - sync_pos);
|
||||
uint64_t truncated_size =
|
||||
static_cast<uint64_t>(sync_pos) + rand->Uniform(range);
|
||||
return Truncate(env, filename_, truncated_size);
|
||||
}
|
||||
|
||||
Status TestDirectory::Fsync() {
|
||||
env_->SyncDir(dirname_);
|
||||
return dir_->Fsync();
|
||||
}
|
||||
|
||||
TestWritableFile::TestWritableFile(const std::string& fname,
|
||||
unique_ptr<WritableFile>&& f,
|
||||
FaultInjectionTestEnv* env)
|
||||
: state_(fname),
|
||||
target_(std::move(f)),
|
||||
writable_file_opened_(true),
|
||||
env_(env) {
|
||||
assert(target_ != nullptr);
|
||||
state_.pos_ = 0;
|
||||
}
|
||||
|
||||
TestWritableFile::~TestWritableFile() {
|
||||
if (writable_file_opened_) {
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
Status TestWritableFile::Append(const Slice& data) {
|
||||
if (!env_->IsFilesystemActive()) {
|
||||
return Status::Corruption("Not Active");
|
||||
}
|
||||
Status s = target_->Append(data);
|
||||
if (s.ok()) {
|
||||
state_.pos_ += data.size();
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TestWritableFile::Close() {
|
||||
writable_file_opened_ = false;
|
||||
Status s = target_->Close();
|
||||
if (s.ok()) {
|
||||
env_->WritableFileClosed(state_);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TestWritableFile::Flush() {
|
||||
Status s = target_->Flush();
|
||||
if (s.ok() && env_->IsFilesystemActive()) {
|
||||
state_.pos_at_last_flush_ = state_.pos_;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
Status TestWritableFile::Sync() {
|
||||
if (!env_->IsFilesystemActive()) {
|
||||
return Status::OK();
|
||||
}
|
||||
// No need to actual sync.
|
||||
state_.pos_at_last_sync_ = state_.pos_;
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
class FaultInjectionTest : public testing::Test,
|
||||
public testing::WithParamInterface<bool> {
|
||||
protected:
|
||||
@@ -732,7 +336,7 @@ class FaultInjectionTest : public testing::Test,
|
||||
}
|
||||
|
||||
void WaitCompactionFinish() {
|
||||
static_cast<DBImpl*>(db_)->TEST_WaitForCompact();
|
||||
static_cast<DBImpl*>(db_->GetRootDB())->TEST_WaitForCompact();
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "", ""));
|
||||
}
|
||||
};
|
||||
@@ -801,6 +405,7 @@ TEST_P(FaultInjectionTest, WriteOptionSyncTest) {
|
||||
env_->SetFilesystemActive(false);
|
||||
NoWriteTestReopenWithFault(kResetDropAndDeleteUnsynced);
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
|
||||
ASSERT_OK(OpenDB());
|
||||
std::string val;
|
||||
@@ -885,6 +490,7 @@ TEST_P(FaultInjectionTest, ManualLogSyncTest) {
|
||||
env_->SetFilesystemActive(false);
|
||||
NoWriteTestReopenWithFault(kResetDropAndDeleteUnsynced);
|
||||
sleeping_task_low.WakeUp();
|
||||
sleeping_task_low.WaitUntilDone();
|
||||
|
||||
ASSERT_OK(OpenDB());
|
||||
std::string val;
|
||||
|
||||
+99
-75
@@ -66,7 +66,7 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger)
|
||||
EventLogger* event_logger, bool measure_io_stats)
|
||||
: dbname_(dbname),
|
||||
cfd_(cfd),
|
||||
db_options_(db_options),
|
||||
@@ -83,7 +83,9 @@ FlushJob::FlushJob(const std::string& dbname, ColumnFamilyData* cfd,
|
||||
output_file_directory_(output_file_directory),
|
||||
output_compression_(output_compression),
|
||||
stats_(stats),
|
||||
event_logger_(event_logger) {
|
||||
event_logger_(event_logger),
|
||||
measure_io_stats_(measure_io_stats),
|
||||
pick_memtable_called(false) {
|
||||
// Update the thread status to indicate flush.
|
||||
ReportStartedFlush();
|
||||
TEST_SYNC_POINT("FlushJob::FlushJob()");
|
||||
@@ -114,38 +116,70 @@ void FlushJob::ReportFlushInputSize(const autovector<MemTable*>& mems) {
|
||||
}
|
||||
|
||||
void FlushJob::RecordFlushIOStats() {
|
||||
ThreadStatusUtil::SetThreadOperationProperty(
|
||||
RecordTick(stats_, FLUSH_WRITE_BYTES, IOSTATS(bytes_written));
|
||||
ThreadStatusUtil::IncreaseThreadOperationProperty(
|
||||
ThreadStatus::FLUSH_BYTES_WRITTEN, IOSTATS(bytes_written));
|
||||
IOSTATS_RESET(bytes_written);
|
||||
}
|
||||
|
||||
void FlushJob::PickMemTable() {
|
||||
db_mutex_->AssertHeld();
|
||||
assert(!pick_memtable_called);
|
||||
pick_memtable_called = true;
|
||||
// Save the contents of the earliest memtable as a new Table
|
||||
cfd_->imm()->PickMemtablesToFlush(&mems_);
|
||||
if (mems_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReportFlushInputSize(mems_);
|
||||
|
||||
// entries mems are (implicitly) sorted in ascending order by their created
|
||||
// time. We will use the first memtable's `edit` to keep the meta info for
|
||||
// this flush.
|
||||
MemTable* m = mems_[0];
|
||||
edit_ = m->GetEdits();
|
||||
edit_->SetPrevLogNumber(0);
|
||||
// SetLogNumber(log_num) indicates logs with number smaller than log_num
|
||||
// will no longer be picked up for recovery.
|
||||
edit_->SetLogNumber(mems_.back()->GetNextLogNumber());
|
||||
edit_->SetColumnFamily(cfd_->GetID());
|
||||
|
||||
// path 0 for level 0 file.
|
||||
meta_.fd = FileDescriptor(versions_->NewFileNumber(), 0, 0);
|
||||
|
||||
base_ = cfd_->current();
|
||||
base_->Ref(); // it is likely that we do not need this reference
|
||||
}
|
||||
|
||||
Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
db_mutex_->AssertHeld();
|
||||
assert(pick_memtable_called);
|
||||
AutoThreadOperationStageUpdater stage_run(
|
||||
ThreadStatus::STAGE_FLUSH_RUN);
|
||||
// Save the contents of the earliest memtable as a new Table
|
||||
FileMetaData meta;
|
||||
autovector<MemTable*> mems;
|
||||
cfd_->imm()->PickMemtablesToFlush(&mems);
|
||||
if (mems.empty()) {
|
||||
if (mems_.empty()) {
|
||||
LogToBuffer(log_buffer_, "[%s] Nothing in memtable to flush",
|
||||
cfd_->GetName().c_str());
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
ReportFlushInputSize(mems);
|
||||
|
||||
// entries mems are (implicitly) sorted in ascending order by their created
|
||||
// time. We will use the first memtable's `edit` to keep the meta info for
|
||||
// this flush.
|
||||
MemTable* m = mems[0];
|
||||
VersionEdit* edit = m->GetEdits();
|
||||
edit->SetPrevLogNumber(0);
|
||||
// SetLogNumber(log_num) indicates logs with number smaller than log_num
|
||||
// will no longer be picked up for recovery.
|
||||
edit->SetLogNumber(mems.back()->GetNextLogNumber());
|
||||
edit->SetColumnFamily(cfd_->GetID());
|
||||
// I/O measurement variables
|
||||
PerfLevel prev_perf_level = PerfLevel::kEnableTime;
|
||||
uint64_t prev_write_nanos = 0;
|
||||
uint64_t prev_fsync_nanos = 0;
|
||||
uint64_t prev_range_sync_nanos = 0;
|
||||
uint64_t prev_prepare_write_nanos = 0;
|
||||
if (measure_io_stats_) {
|
||||
prev_perf_level = GetPerfLevel();
|
||||
SetPerfLevel(PerfLevel::kEnableTime);
|
||||
prev_write_nanos = IOSTATS(write_nanos);
|
||||
prev_fsync_nanos = IOSTATS(fsync_nanos);
|
||||
prev_range_sync_nanos = IOSTATS(range_sync_nanos);
|
||||
prev_prepare_write_nanos = IOSTATS(prepare_write_nanos);
|
||||
}
|
||||
|
||||
// This will release and re-acquire the mutex.
|
||||
Status s = WriteLevel0Table(mems, edit, &meta);
|
||||
Status s = WriteLevel0Table();
|
||||
|
||||
if (s.ok() &&
|
||||
(shutting_down_->load(std::memory_order_acquire) || cfd_->IsDropped())) {
|
||||
@@ -154,18 +188,18 @@ Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
cfd_->imm()->RollbackMemtableFlush(mems, meta.fd.GetNumber());
|
||||
cfd_->imm()->RollbackMemtableFlush(mems_, meta_.fd.GetNumber());
|
||||
} else {
|
||||
TEST_SYNC_POINT("FlushJob::InstallResults");
|
||||
// Replace immutable memtable with the generated Table
|
||||
s = cfd_->imm()->InstallMemtableFlushResults(
|
||||
cfd_, mutable_cf_options_, mems, versions_, db_mutex_,
|
||||
meta.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
|
||||
cfd_, mutable_cf_options_, mems_, versions_, db_mutex_,
|
||||
meta_.fd.GetNumber(), &job_context_->memtables_to_free, db_directory_,
|
||||
log_buffer_);
|
||||
}
|
||||
|
||||
if (s.ok() && file_meta != nullptr) {
|
||||
*file_meta = meta;
|
||||
*file_meta = meta_;
|
||||
}
|
||||
RecordFlushIOStats();
|
||||
|
||||
@@ -179,21 +213,28 @@ Status FlushJob::Run(FileMetaData* file_meta) {
|
||||
stream << vstorage->NumLevelFiles(level);
|
||||
}
|
||||
stream.EndArray();
|
||||
stream << "immutable_memtables" << cfd_->imm()->NumNotFlushed();
|
||||
|
||||
if (measure_io_stats_) {
|
||||
if (prev_perf_level != PerfLevel::kEnableTime) {
|
||||
SetPerfLevel(prev_perf_level);
|
||||
}
|
||||
stream << "file_write_nanos" << (IOSTATS(write_nanos) - prev_write_nanos);
|
||||
stream << "file_range_sync_nanos"
|
||||
<< (IOSTATS(range_sync_nanos) - prev_range_sync_nanos);
|
||||
stream << "file_fsync_nanos" << (IOSTATS(fsync_nanos) - prev_fsync_nanos);
|
||||
stream << "file_prepare_write_nanos"
|
||||
<< (IOSTATS(prepare_write_nanos) - prev_prepare_write_nanos);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
VersionEdit* edit, FileMetaData* meta) {
|
||||
Status FlushJob::WriteLevel0Table() {
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_FLUSH_WRITE_L0);
|
||||
db_mutex_->AssertHeld();
|
||||
const uint64_t start_micros = db_options_.env->NowMicros();
|
||||
// path 0 for level 0 file.
|
||||
meta->fd = FileDescriptor(versions_->NewFileNumber(), 0, 0);
|
||||
|
||||
Version* base = cfd_->current();
|
||||
base->Ref(); // it is likely that we do not need this reference
|
||||
Status s;
|
||||
{
|
||||
db_mutex_->Unlock();
|
||||
@@ -206,7 +247,7 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
Arena arena;
|
||||
uint64_t total_num_entries = 0, total_num_deletes = 0;
|
||||
size_t total_memory_usage = 0;
|
||||
for (MemTable* m : mems) {
|
||||
for (MemTable* m : mems_) {
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Flushing memtable with next log file: %" PRIu64 "\n",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, m->GetNextLogNumber());
|
||||
@@ -218,55 +259,40 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
|
||||
event_logger_->Log() << "job" << job_context_->job_id << "event"
|
||||
<< "flush_started"
|
||||
<< "num_memtables" << mems.size() << "num_entries"
|
||||
<< "num_memtables" << mems_.size() << "num_entries"
|
||||
<< total_num_entries << "num_deletes"
|
||||
<< total_num_deletes << "memory_usage"
|
||||
<< total_memory_usage;
|
||||
|
||||
TableFileCreationInfo info;
|
||||
{
|
||||
ScopedArenaIterator iter(
|
||||
NewMergingIterator(&cfd_->internal_comparator(), &memtables[0],
|
||||
static_cast<int>(memtables.size()), &arena));
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": started",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta->fd.GetNumber());
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta_.fd.GetNumber());
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("FlushJob::WriteLevel0Table:output_compression",
|
||||
&output_compression_);
|
||||
s = BuildTable(dbname_, db_options_.env, *cfd_->ioptions(), env_options_,
|
||||
cfd_->table_cache(), iter.get(), meta,
|
||||
cfd_->internal_comparator(),
|
||||
cfd_->int_tbl_prop_collector_factories(), cfd_->GetID(),
|
||||
existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
output_compression_, cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks,
|
||||
cfd_->internal_stats(), Env::IO_HIGH, &table_properties_);
|
||||
info.table_properties = table_properties_;
|
||||
s = BuildTable(
|
||||
dbname_, db_options_.env, *cfd_->ioptions(), mutable_cf_options_,
|
||||
env_options_, cfd_->table_cache(), iter.get(), &meta_,
|
||||
cfd_->internal_comparator(), cfd_->int_tbl_prop_collector_factories(),
|
||||
cfd_->GetID(), cfd_->GetName(), existing_snapshots_,
|
||||
earliest_write_conflict_snapshot_, output_compression_,
|
||||
cfd_->ioptions()->compression_opts,
|
||||
mutable_cf_options_.paranoid_file_checks, cfd_->internal_stats(),
|
||||
TableFileCreationReason::kFlush, event_logger_, job_context_->job_id,
|
||||
Env::IO_HIGH, &table_properties_, 0 /* level */);
|
||||
LogFlush(db_options_.info_log);
|
||||
}
|
||||
Log(InfoLogLevel::INFO_LEVEL, db_options_.info_log,
|
||||
"[%s] [JOB %d] Level-0 flush table #%" PRIu64 ": %" PRIu64
|
||||
" bytes %s"
|
||||
"%s",
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta->fd.GetNumber(),
|
||||
meta->fd.GetFileSize(), s.ToString().c_str(),
|
||||
meta->marked_for_compaction ? " (needs compaction)" : "");
|
||||
|
||||
// output to event logger
|
||||
if (s.ok()) {
|
||||
info.db_name = dbname_;
|
||||
info.cf_name = cfd_->GetName();
|
||||
info.file_path = TableFileName(db_options_.db_paths,
|
||||
meta->fd.GetNumber(),
|
||||
meta->fd.GetPathId());
|
||||
info.file_size = meta->fd.GetFileSize();
|
||||
info.job_id = job_context_->job_id;
|
||||
EventHelpers::LogAndNotifyTableFileCreation(
|
||||
event_logger_, db_options_.listeners,
|
||||
meta->fd, info);
|
||||
TEST_SYNC_POINT("FlushJob::LogAndNotifyTableFileCreation()");
|
||||
}
|
||||
cfd_->GetName().c_str(), job_context_->job_id, meta_.fd.GetNumber(),
|
||||
meta_.fd.GetFileSize(), s.ToString().c_str(),
|
||||
meta_.marked_for_compaction ? " (needs compaction)" : "");
|
||||
|
||||
if (!db_options_.disableDataSync && output_file_directory_ != nullptr) {
|
||||
output_file_directory_->Fsync();
|
||||
@@ -274,32 +300,30 @@ Status FlushJob::WriteLevel0Table(const autovector<MemTable*>& mems,
|
||||
TEST_SYNC_POINT("FlushJob::WriteLevel0Table");
|
||||
db_mutex_->Lock();
|
||||
}
|
||||
base->Unref();
|
||||
|
||||
// re-acquire the most current version
|
||||
base = cfd_->current();
|
||||
base_->Unref();
|
||||
|
||||
// Note that if file_size is zero, the file has been deleted and
|
||||
// should not be added to the manifest.
|
||||
if (s.ok() && meta->fd.GetFileSize() > 0) {
|
||||
if (s.ok() && meta_.fd.GetFileSize() > 0) {
|
||||
// if we have more than 1 background thread, then we cannot
|
||||
// insert files directly into higher levels because some other
|
||||
// threads could be concurrently producing compacted files for
|
||||
// that key range.
|
||||
// Add file to L0
|
||||
edit->AddFile(0 /* level */, meta->fd.GetNumber(), meta->fd.GetPathId(),
|
||||
meta->fd.GetFileSize(), meta->smallest, meta->largest,
|
||||
meta->smallest_seqno, meta->largest_seqno,
|
||||
meta->marked_for_compaction);
|
||||
edit_->AddFile(0 /* level */, meta_.fd.GetNumber(), meta_.fd.GetPathId(),
|
||||
meta_.fd.GetFileSize(), meta_.smallest, meta_.largest,
|
||||
meta_.smallest_seqno, meta_.largest_seqno,
|
||||
meta_.marked_for_compaction);
|
||||
}
|
||||
|
||||
// Note that here we treat flush as level 0 compaction in internal stats
|
||||
InternalStats::CompactionStats stats(1);
|
||||
stats.micros = db_options_.env->NowMicros() - start_micros;
|
||||
stats.bytes_written = meta->fd.GetFileSize();
|
||||
stats.bytes_written = meta_.fd.GetFileSize();
|
||||
cfd_->internal_stats()->AddCompactionStats(0 /* level */, stats);
|
||||
cfd_->internal_stats()->AddCFStats(InternalStats::BYTES_FLUSHED,
|
||||
meta->fd.GetFileSize());
|
||||
RecordTick(stats_, COMPACT_WRITE_BYTES, meta->fd.GetFileSize());
|
||||
meta_.fd.GetFileSize());
|
||||
RecordFlushIOStats();
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
+18
-9
@@ -16,12 +16,17 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/job_context.h"
|
||||
#include "db/log_writer.h"
|
||||
#include "db/memtable_list.h"
|
||||
#include "db/snapshot_impl.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
@@ -33,11 +38,6 @@
|
||||
#include "util/instrumented_mutex.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/thread_local.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/write_thread.h"
|
||||
#include "db/job_context.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -62,10 +62,12 @@ class FlushJob {
|
||||
JobContext* job_context, LogBuffer* log_buffer,
|
||||
Directory* db_directory, Directory* output_file_directory,
|
||||
CompressionType output_compression, Statistics* stats,
|
||||
EventLogger* event_logger);
|
||||
EventLogger* event_logger, bool measure_io_stats);
|
||||
|
||||
~FlushJob();
|
||||
|
||||
// Require db_mutex held
|
||||
void PickMemTable();
|
||||
Status Run(FileMetaData* file_meta = nullptr);
|
||||
TableProperties GetTableProperties() const { return table_properties_; }
|
||||
|
||||
@@ -73,8 +75,7 @@ class FlushJob {
|
||||
void ReportStartedFlush();
|
||||
void ReportFlushInputSize(const autovector<MemTable*>& mems);
|
||||
void RecordFlushIOStats();
|
||||
Status WriteLevel0Table(const autovector<MemTable*>& mems, VersionEdit* edit,
|
||||
FileMetaData* meta);
|
||||
Status WriteLevel0Table();
|
||||
const std::string& dbname_;
|
||||
ColumnFamilyData* cfd_;
|
||||
const DBOptions& db_options_;
|
||||
@@ -93,6 +94,14 @@ class FlushJob {
|
||||
Statistics* stats_;
|
||||
EventLogger* event_logger_;
|
||||
TableProperties table_properties_;
|
||||
bool measure_io_stats_;
|
||||
|
||||
// Variables below are set by PickMemTable():
|
||||
FileMetaData meta_;
|
||||
autovector<MemTable*> mems_;
|
||||
VersionEdit* edit_;
|
||||
Version* base_;
|
||||
bool pick_memtable_called;
|
||||
};
|
||||
|
||||
} // namespace rocksdb
|
||||
+20
-14
@@ -7,16 +7,16 @@
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "db/flush_job.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/flush_job.h"
|
||||
#include "db/version_set.h"
|
||||
#include "db/writebuffer.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/mock_table.h"
|
||||
#include "util/file_reader_writer.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/testharness.h"
|
||||
#include "util/testutil.h"
|
||||
#include "table/mock_table.h"
|
||||
|
||||
namespace rocksdb {
|
||||
|
||||
@@ -29,9 +29,9 @@ class FlushJobTest : public testing::Test {
|
||||
: env_(Env::Default()),
|
||||
dbname_(test::TmpDir() + "/flush_job_test"),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_(db_options_.db_write_buffer_size),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
versions_(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_)),
|
||||
shutting_down_(false),
|
||||
mock_table_factory_(new mock::MockTableFactory()) {
|
||||
@@ -77,7 +77,7 @@ class FlushJobTest : public testing::Test {
|
||||
std::shared_ptr<Cache> table_cache_;
|
||||
WriteController write_controller_;
|
||||
DBOptions db_options_;
|
||||
WriteBuffer write_buffer_;
|
||||
WriteBufferManager write_buffer_manager_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
std::unique_ptr<VersionSet> versions_;
|
||||
InstrumentedMutex mutex_;
|
||||
@@ -93,8 +93,12 @@ TEST_F(FlushJobTest, Empty) {
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
{}, kMaxSequenceNumber, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger);
|
||||
ASSERT_OK(flush_job.Run());
|
||||
nullptr, kNoCompression, nullptr, &event_logger, false);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run());
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
|
||||
@@ -132,9 +136,10 @@ TEST_F(FlushJobTest, NonEmpty) {
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
{}, kMaxSequenceNumber, &job_context, nullptr, nullptr,
|
||||
nullptr, kNoCompression, nullptr, &event_logger);
|
||||
nullptr, kNoCompression, nullptr, &event_logger, true);
|
||||
FileMetaData fd;
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run(&fd));
|
||||
mutex_.Unlock();
|
||||
ASSERT_EQ(ToString(0), fd.smallest.user_key().ToString());
|
||||
@@ -192,12 +197,13 @@ TEST_F(FlushJobTest, Snapshots) {
|
||||
}
|
||||
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
FlushJob flush_job(dbname_, versions_->GetColumnFamilySet()->GetDefault(),
|
||||
db_options_, *cfd->GetLatestMutableCFOptions(),
|
||||
env_options_, versions_.get(), &mutex_, &shutting_down_,
|
||||
snapshots, kMaxSequenceNumber, &job_context, nullptr,
|
||||
nullptr, nullptr, kNoCompression, nullptr, &event_logger);
|
||||
FlushJob flush_job(
|
||||
dbname_, versions_->GetColumnFamilySet()->GetDefault(), db_options_,
|
||||
*cfd->GetLatestMutableCFOptions(), env_options_, versions_.get(), &mutex_,
|
||||
&shutting_down_, snapshots, kMaxSequenceNumber, &job_context, nullptr,
|
||||
nullptr, nullptr, kNoCompression, nullptr, &event_logger, true);
|
||||
mutex_.Lock();
|
||||
flush_job.PickMemTable();
|
||||
ASSERT_OK(flush_job.Run());
|
||||
mutex_.Unlock();
|
||||
mock_table_factory_->AssertSingleFile(inserted_keys);
|
||||
|
||||
@@ -20,6 +20,8 @@ void FlushScheduler::ScheduleFlush(ColumnFamilyData* cfd) {
|
||||
}
|
||||
#endif // NDEBUG
|
||||
cfd->Ref();
|
||||
// Suppress false positive clang analyzer warnings.
|
||||
#ifndef __clang_analyzer__
|
||||
Node* node = new Node{cfd, head_.load(std::memory_order_relaxed)};
|
||||
while (!head_.compare_exchange_strong(
|
||||
node->next, node, std::memory_order_relaxed, std::memory_order_relaxed)) {
|
||||
@@ -28,6 +30,7 @@ void FlushScheduler::ScheduleFlush(ColumnFamilyData* cfd) {
|
||||
// inter-thread synchronization, so we don't even need release
|
||||
// semantics for this CAS
|
||||
}
|
||||
#endif // __clang_analyzer__
|
||||
}
|
||||
|
||||
ColumnFamilyData* FlushScheduler::TakeNextColumnFamily() {
|
||||
|
||||
+147
-24
@@ -32,10 +32,24 @@ namespace rocksdb {
|
||||
class LevelIterator : public InternalIterator {
|
||||
public:
|
||||
LevelIterator(const ColumnFamilyData* const cfd,
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files)
|
||||
: cfd_(cfd), read_options_(read_options), files_(files), valid_(false),
|
||||
file_index_(std::numeric_limits<uint32_t>::max()) {}
|
||||
const ReadOptions& read_options,
|
||||
const std::vector<FileMetaData*>& files)
|
||||
: cfd_(cfd),
|
||||
read_options_(read_options),
|
||||
files_(files),
|
||||
valid_(false),
|
||||
file_index_(std::numeric_limits<uint32_t>::max()),
|
||||
file_iter_(nullptr),
|
||||
pinned_iters_mgr_(nullptr) {}
|
||||
|
||||
~LevelIterator() {
|
||||
// Reset current pointer
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
pinned_iters_mgr_->PinIterator(file_iter_);
|
||||
} else {
|
||||
delete file_iter_;
|
||||
}
|
||||
}
|
||||
|
||||
void SetFileIndex(uint32_t file_index) {
|
||||
assert(file_index < files_.size());
|
||||
@@ -47,10 +61,20 @@ class LevelIterator : public InternalIterator {
|
||||
}
|
||||
void Reset() {
|
||||
assert(file_index_ < files_.size());
|
||||
file_iter_.reset(cfd_->table_cache()->NewIterator(
|
||||
|
||||
// Reset current pointer
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
pinned_iters_mgr_->PinIterator(file_iter_);
|
||||
} else {
|
||||
delete file_iter_;
|
||||
}
|
||||
|
||||
file_iter_ = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *(cfd_->soptions()), cfd_->internal_comparator(),
|
||||
files_[file_index_]->fd, nullptr /* table_reader_ptr */, nullptr,
|
||||
false));
|
||||
false);
|
||||
|
||||
file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
void SeekToLast() override {
|
||||
status_ = Status::NotSupported("LevelIterator::SeekToLast()");
|
||||
@@ -105,6 +129,20 @@ class LevelIterator : public InternalIterator {
|
||||
}
|
||||
return Status::OK();
|
||||
}
|
||||
bool IsKeyPinned() const override {
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
file_iter_->IsKeyPinned();
|
||||
}
|
||||
bool IsValuePinned() const override {
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
file_iter_->IsValuePinned();
|
||||
}
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
pinned_iters_mgr_ = pinned_iters_mgr;
|
||||
if (file_iter_) {
|
||||
file_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const ColumnFamilyData* const cfd_;
|
||||
@@ -114,7 +152,8 @@ class LevelIterator : public InternalIterator {
|
||||
bool valid_;
|
||||
uint32_t file_index_;
|
||||
Status status_;
|
||||
std::unique_ptr<InternalIterator> file_iter_;
|
||||
InternalIterator* file_iter_;
|
||||
PinnedIteratorsManager* pinned_iters_mgr_;
|
||||
};
|
||||
|
||||
ForwardIterator::ForwardIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
@@ -135,7 +174,8 @@ ForwardIterator::ForwardIterator(DBImpl* db, const ReadOptions& read_options,
|
||||
has_iter_trimmed_for_upper_bound_(false),
|
||||
current_over_upper_bound_(false),
|
||||
is_prev_set_(false),
|
||||
is_prev_inclusive_(false) {
|
||||
is_prev_inclusive_(false),
|
||||
pinned_iters_mgr_(nullptr) {
|
||||
if (sv_) {
|
||||
RebuildIterators(false);
|
||||
}
|
||||
@@ -145,6 +185,13 @@ ForwardIterator::~ForwardIterator() {
|
||||
Cleanup(true);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Used in PinnedIteratorsManager to release pinned SuperVersion
|
||||
static void ReleaseSuperVersionFunc(void* sv) {
|
||||
delete reinterpret_cast<SuperVersion*>(sv);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ForwardIterator::SVCleanup() {
|
||||
if (sv_ != nullptr && sv_->Unref()) {
|
||||
// Job id == 0 means that this is not our background process, but rather
|
||||
@@ -153,10 +200,18 @@ void ForwardIterator::SVCleanup() {
|
||||
db_->mutex_.Lock();
|
||||
sv_->Cleanup();
|
||||
db_->FindObsoleteFiles(&job_context, false, true);
|
||||
if (read_options_.background_purge_on_iterator_cleanup) {
|
||||
db_->ScheduleBgLogWriterClose(&job_context);
|
||||
}
|
||||
db_->mutex_.Unlock();
|
||||
delete sv_;
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
pinned_iters_mgr_->PinPtr(sv_, &ReleaseSuperVersionFunc);
|
||||
} else {
|
||||
delete sv_;
|
||||
}
|
||||
if (job_context.HaveSomethingToDelete()) {
|
||||
db_->PurgeObsoleteFiles(job_context);
|
||||
db_->PurgeObsoleteFiles(
|
||||
job_context, read_options_.background_purge_on_iterator_cleanup);
|
||||
}
|
||||
job_context.Clean();
|
||||
}
|
||||
@@ -164,18 +219,21 @@ void ForwardIterator::SVCleanup() {
|
||||
|
||||
void ForwardIterator::Cleanup(bool release_sv) {
|
||||
if (mutable_iter_ != nullptr) {
|
||||
mutable_iter_->~InternalIterator();
|
||||
DeleteIterator(mutable_iter_, true /* is_arena */);
|
||||
}
|
||||
|
||||
for (auto* m : imm_iters_) {
|
||||
m->~InternalIterator();
|
||||
DeleteIterator(m, true /* is_arena */);
|
||||
}
|
||||
imm_iters_.clear();
|
||||
|
||||
for (auto* f : l0_iters_) {
|
||||
delete f;
|
||||
DeleteIterator(f);
|
||||
}
|
||||
l0_iters_.clear();
|
||||
|
||||
for (auto* l : level_iters_) {
|
||||
delete l;
|
||||
DeleteIterator(l);
|
||||
}
|
||||
level_iters_.clear();
|
||||
|
||||
@@ -276,7 +334,7 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
l0[i]->largest.user_key()) > 0) {
|
||||
if (read_options_.iterate_upper_bound != nullptr) {
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
delete l0_iters_[i];
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = nullptr;
|
||||
}
|
||||
continue;
|
||||
@@ -291,7 +349,7 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
immutable_min_heap_.push(l0_iters_[i]);
|
||||
} else {
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
delete l0_iters_[i];
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -368,7 +426,7 @@ void ForwardIterator::SeekInternal(const Slice& internal_key,
|
||||
} else {
|
||||
// Nothing in this level is interesting. Remove.
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
delete level_iters_[level - 1];
|
||||
DeleteIterator(level_iters_[level - 1]);
|
||||
level_iters_[level - 1] = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -481,6 +539,50 @@ Status ForwardIterator::GetProperty(std::string prop_name, std::string* prop) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
|
||||
void ForwardIterator::SetPinnedItersMgr(
|
||||
PinnedIteratorsManager* pinned_iters_mgr) {
|
||||
pinned_iters_mgr_ = pinned_iters_mgr;
|
||||
UpdateChildrenPinnedItersMgr();
|
||||
}
|
||||
|
||||
void ForwardIterator::UpdateChildrenPinnedItersMgr() {
|
||||
// Set PinnedIteratorsManager for mutable memtable iterator.
|
||||
if (mutable_iter_) {
|
||||
mutable_iter_->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
// Set PinnedIteratorsManager for immutable memtable iterators.
|
||||
for (InternalIterator* child_iter : imm_iters_) {
|
||||
if (child_iter) {
|
||||
child_iter->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
}
|
||||
|
||||
// Set PinnedIteratorsManager for L0 files iterators.
|
||||
for (InternalIterator* child_iter : l0_iters_) {
|
||||
if (child_iter) {
|
||||
child_iter->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
}
|
||||
|
||||
// Set PinnedIteratorsManager for L1+ levels iterators.
|
||||
for (LevelIterator* child_iter : level_iters_) {
|
||||
if (child_iter) {
|
||||
child_iter->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ForwardIterator::IsKeyPinned() const {
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
current_->IsKeyPinned();
|
||||
}
|
||||
|
||||
bool ForwardIterator::IsValuePinned() const {
|
||||
return pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled() &&
|
||||
current_->IsValuePinned();
|
||||
}
|
||||
|
||||
void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
// Clean up
|
||||
Cleanup(refresh_sv);
|
||||
@@ -509,6 +611,8 @@ void ForwardIterator::RebuildIterators(bool refresh_sv) {
|
||||
BuildLevelIterators(vstorage);
|
||||
current_ = nullptr;
|
||||
is_prev_set_ = false;
|
||||
|
||||
UpdateChildrenPinnedItersMgr();
|
||||
}
|
||||
|
||||
void ForwardIterator::RenewIterators() {
|
||||
@@ -517,10 +621,10 @@ void ForwardIterator::RenewIterators() {
|
||||
svnew = cfd_->GetReferencedSuperVersion(&(db_->mutex_));
|
||||
|
||||
if (mutable_iter_ != nullptr) {
|
||||
mutable_iter_->~InternalIterator();
|
||||
DeleteIterator(mutable_iter_, true /* is_arena */);
|
||||
}
|
||||
for (auto* m : imm_iters_) {
|
||||
m->~InternalIterator();
|
||||
DeleteIterator(m, true /* is_arena */);
|
||||
}
|
||||
imm_iters_.clear();
|
||||
|
||||
@@ -561,13 +665,13 @@ void ForwardIterator::RenewIterators() {
|
||||
}
|
||||
|
||||
for (auto* f : l0_iters_) {
|
||||
delete f;
|
||||
DeleteIterator(f);
|
||||
}
|
||||
l0_iters_.clear();
|
||||
l0_iters_ = l0_iters_new;
|
||||
|
||||
for (auto* l : level_iters_) {
|
||||
delete l;
|
||||
DeleteIterator(l);
|
||||
}
|
||||
level_iters_.clear();
|
||||
BuildLevelIterators(vstorage_new);
|
||||
@@ -575,6 +679,8 @@ void ForwardIterator::RenewIterators() {
|
||||
is_prev_set_ = false;
|
||||
SVCleanup();
|
||||
sv_ = svnew;
|
||||
|
||||
UpdateChildrenPinnedItersMgr();
|
||||
}
|
||||
|
||||
void ForwardIterator::BuildLevelIterators(const VersionStorageInfo* vstorage) {
|
||||
@@ -604,10 +710,11 @@ void ForwardIterator::ResetIncompleteIterators() {
|
||||
if (!l0_iters_[i] || !l0_iters_[i]->status().IsIncomplete()) {
|
||||
continue;
|
||||
}
|
||||
delete l0_iters_[i];
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = cfd_->table_cache()->NewIterator(
|
||||
read_options_, *cfd_->soptions(), cfd_->internal_comparator(),
|
||||
l0_files[i]->fd);
|
||||
l0_iters_[i]->SetPinnedItersMgr(pinned_iters_mgr_);
|
||||
}
|
||||
|
||||
for (auto* level_iter : level_iters_) {
|
||||
@@ -696,7 +803,7 @@ void ForwardIterator::DeleteCurrentIter() {
|
||||
}
|
||||
if (l0_iters_[i] == current_) {
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
delete l0_iters_[i];
|
||||
DeleteIterator(l0_iters_[i]);
|
||||
l0_iters_[i] = nullptr;
|
||||
return;
|
||||
}
|
||||
@@ -708,7 +815,7 @@ void ForwardIterator::DeleteCurrentIter() {
|
||||
}
|
||||
if (level_iters_[level - 1] == current_) {
|
||||
has_iter_trimmed_for_upper_bound_ = true;
|
||||
delete level_iters_[level - 1];
|
||||
DeleteIterator(level_iters_[level - 1]);
|
||||
level_iters_[level - 1] = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -772,6 +879,22 @@ uint32_t ForwardIterator::FindFileInRange(
|
||||
return right;
|
||||
}
|
||||
|
||||
void ForwardIterator::DeleteIterator(InternalIterator* iter, bool is_arena) {
|
||||
if (iter == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (pinned_iters_mgr_ && pinned_iters_mgr_->PinningEnabled()) {
|
||||
pinned_iters_mgr_->PinIterator(iter, is_arena);
|
||||
} else {
|
||||
if (is_arena) {
|
||||
iter->~InternalIterator();
|
||||
} else {
|
||||
delete iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace rocksdb
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -72,6 +72,10 @@ class ForwardIterator : public InternalIterator {
|
||||
virtual Slice value() const override;
|
||||
virtual Status status() const override;
|
||||
virtual Status GetProperty(std::string prop_name, std::string* prop) override;
|
||||
virtual void SetPinnedItersMgr(
|
||||
PinnedIteratorsManager* pinned_iters_mgr) override;
|
||||
virtual bool IsKeyPinned() const override;
|
||||
virtual bool IsValuePinned() const override;
|
||||
|
||||
bool TEST_CheckDeletedIters(int* deleted_iters, int* num_iters);
|
||||
|
||||
@@ -92,6 +96,14 @@ class ForwardIterator : public InternalIterator {
|
||||
|
||||
bool IsOverUpperBound(const Slice& internal_key) const;
|
||||
|
||||
// Set PinnedIteratorsManager for all children Iterators, this function should
|
||||
// be called whenever we update children Iterators or pinned_iters_mgr_.
|
||||
void UpdateChildrenPinnedItersMgr();
|
||||
|
||||
// A helper function that will release iter in the proper manner, or pass it
|
||||
// to pinned_iters_mgr_ to release it later if pinning is enabled.
|
||||
void DeleteIterator(InternalIterator* iter, bool is_arena = false);
|
||||
|
||||
DBImpl* const db_;
|
||||
const ReadOptions read_options_;
|
||||
ColumnFamilyData* const cfd_;
|
||||
@@ -129,6 +141,7 @@ class ForwardIterator : public InternalIterator {
|
||||
bool is_prev_set_;
|
||||
bool is_prev_inclusive_;
|
||||
|
||||
PinnedIteratorsManager* pinned_iters_mgr_;
|
||||
Arena arena_;
|
||||
};
|
||||
|
||||
|
||||
+88
-52
@@ -101,6 +101,8 @@ std::pair<Slice, Slice> GetPropertyNameAndArg(const Slice& property) {
|
||||
static const std::string rocksdb_prefix = "rocksdb.";
|
||||
|
||||
static const std::string num_files_at_level_prefix = "num-files-at-level";
|
||||
static const std::string compression_ratio_at_level_prefix =
|
||||
"compression-ratio-at-level";
|
||||
static const std::string allstats = "stats";
|
||||
static const std::string sstables = "sstables";
|
||||
static const std::string cfstats = "cfstats";
|
||||
@@ -148,6 +150,8 @@ static const std::string num_running_flushes = "num-running-flushes";
|
||||
|
||||
const std::string DB::Properties::kNumFilesAtLevelPrefix =
|
||||
rocksdb_prefix + num_files_at_level_prefix;
|
||||
const std::string DB::Properties::kCompressionRatioAtLevelPrefix =
|
||||
rocksdb_prefix + compression_ratio_at_level_prefix;
|
||||
const std::string DB::Properties::kStats = rocksdb_prefix + allstats;
|
||||
const std::string DB::Properties::kSSTables = rocksdb_prefix + sstables;
|
||||
const std::string DB::Properties::kCFStats = rocksdb_prefix + cfstats;
|
||||
@@ -211,6 +215,8 @@ const std::unordered_map<std::string,
|
||||
DBPropertyInfo> InternalStats::ppt_name_to_info = {
|
||||
{DB::Properties::kNumFilesAtLevelPrefix,
|
||||
{false, &InternalStats::HandleNumFilesAtLevel, nullptr}},
|
||||
{DB::Properties::kCompressionRatioAtLevelPrefix,
|
||||
{false, &InternalStats::HandleCompressionRatioAtLevelPrefix, nullptr}},
|
||||
{DB::Properties::kLevelStats,
|
||||
{false, &InternalStats::HandleLevelStats, nullptr}},
|
||||
{DB::Properties::kStats, {false, &InternalStats::HandleStats, nullptr}},
|
||||
@@ -324,6 +330,19 @@ bool InternalStats::HandleNumFilesAtLevel(std::string* value, Slice suffix) {
|
||||
}
|
||||
}
|
||||
|
||||
bool InternalStats::HandleCompressionRatioAtLevelPrefix(std::string* value,
|
||||
Slice suffix) {
|
||||
uint64_t level;
|
||||
const auto* vstorage = cfd_->current()->storage_info();
|
||||
bool ok = ConsumeDecimalNumber(&suffix, &level) && suffix.empty();
|
||||
if (!ok || level >= static_cast<uint64_t>(number_levels_)) {
|
||||
return false;
|
||||
}
|
||||
*value = ToString(
|
||||
vstorage->GetEstimatedCompressionRatioAtLevel(static_cast<int>(level)));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InternalStats::HandleLevelStats(std::string* value, Slice suffix) {
|
||||
char buf[1000];
|
||||
const auto* vstorage = cfd_->current()->storage_info();
|
||||
@@ -589,9 +608,6 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
uint64_t wal_synced = GetDBStats(InternalStats::WAL_FILE_SYNCED);
|
||||
uint64_t write_with_wal = GetDBStats(InternalStats::WRITE_WITH_WAL);
|
||||
uint64_t write_stall_micros = GetDBStats(InternalStats::WRITE_STALL_MICROS);
|
||||
uint64_t compact_bytes_read = 0;
|
||||
uint64_t compact_bytes_write = 0;
|
||||
uint64_t compact_micros = 0;
|
||||
|
||||
const int kHumanMicrosLen = 32;
|
||||
char human_micros[kHumanMicrosLen];
|
||||
@@ -599,15 +615,15 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
// Data
|
||||
// writes: total number of write requests.
|
||||
// keys: total number of key updates issued by all the write requests
|
||||
// batches: number of group commits issued to the DB. Each group can contain
|
||||
// one or more writes.
|
||||
// commit groups: number of group commits issued to the DB. Each group can
|
||||
// contain one or more writes.
|
||||
// so writes/keys is the average number of put in multi-put or put
|
||||
// writes/batches is the average group commit size.
|
||||
// writes/groups is the average group commit size.
|
||||
//
|
||||
// The format is the same for interval stats.
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Cumulative writes: %s writes, %s keys, %s batches, "
|
||||
"%.1f writes per batch, ingest: %.2f GB, %.2f MB/s\n",
|
||||
"Cumulative writes: %s writes, %s keys, %s commit groups, "
|
||||
"%.1f writes per commit group, ingest: %.2f GB, %.2f MB/s\n",
|
||||
NumberToHumanString(write_other + write_self).c_str(),
|
||||
NumberToHumanString(num_keys_written).c_str(),
|
||||
NumberToHumanString(write_self).c_str(),
|
||||
@@ -623,20 +639,6 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
write_with_wal / static_cast<double>(wal_synced + 1),
|
||||
wal_bytes / kGB, wal_bytes / kMB / seconds_up);
|
||||
value->append(buf);
|
||||
// Compact
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
compact_bytes_read += comp_stats_[level].bytes_read_output_level +
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
compact_bytes_write += comp_stats_[level].bytes_written;
|
||||
compact_micros += comp_stats_[level].micros;
|
||||
}
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Cumulative compaction: %.2f GB write, %.2f MB/s write, "
|
||||
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
||||
compact_bytes_write / kGB, compact_bytes_write / kMB / seconds_up,
|
||||
compact_bytes_read / kGB, compact_bytes_read / kMB / seconds_up,
|
||||
compact_micros / kMicrosInSec);
|
||||
value->append(buf);
|
||||
// Stall
|
||||
AppendHumanMicros(write_stall_micros, human_micros, kHumanMicrosLen, true);
|
||||
snprintf(buf, sizeof(buf),
|
||||
@@ -652,8 +654,8 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
uint64_t interval_num_keys_written =
|
||||
num_keys_written - db_stats_snapshot_.num_keys_written;
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Interval writes: %s writes, %s keys, %s batches, "
|
||||
"%.1f writes per batch, ingest: %.2f MB, %.2f MB/s\n",
|
||||
"Interval writes: %s writes, %s keys, %s commit groups, "
|
||||
"%.1f writes per commit group, ingest: %.2f MB, %.2f MB/s\n",
|
||||
NumberToHumanString(
|
||||
interval_write_other + interval_write_self).c_str(),
|
||||
NumberToHumanString(interval_num_keys_written).c_str(),
|
||||
@@ -681,25 +683,6 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
interval_wal_bytes / kMB / std::max(interval_seconds_up, 0.001));
|
||||
value->append(buf);
|
||||
|
||||
// Compaction
|
||||
uint64_t interval_compact_bytes_write =
|
||||
compact_bytes_write - db_stats_snapshot_.compact_bytes_write;
|
||||
uint64_t interval_compact_bytes_read =
|
||||
compact_bytes_read - db_stats_snapshot_.compact_bytes_read;
|
||||
uint64_t interval_compact_micros =
|
||||
compact_micros - db_stats_snapshot_.compact_micros;
|
||||
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"Interval compaction: %.2f GB write, %.2f MB/s write, "
|
||||
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
||||
interval_compact_bytes_write / kGB,
|
||||
interval_compact_bytes_write / kMB / std::max(interval_seconds_up, 0.001),
|
||||
interval_compact_bytes_read / kGB,
|
||||
interval_compact_bytes_read / kMB / std::max(interval_seconds_up, 0.001),
|
||||
interval_compact_micros / kMicrosInSec);
|
||||
value->append(buf);
|
||||
|
||||
// Stall
|
||||
AppendHumanMicros(
|
||||
write_stall_micros - db_stats_snapshot_.write_stall_micros,
|
||||
@@ -731,9 +714,6 @@ void InternalStats::DumpDBStats(std::string* value) {
|
||||
db_stats_snapshot_.wal_synced = wal_synced;
|
||||
db_stats_snapshot_.write_with_wal = write_with_wal;
|
||||
db_stats_snapshot_.write_stall_micros = write_stall_micros;
|
||||
db_stats_snapshot_.compact_bytes_write = compact_bytes_write;
|
||||
db_stats_snapshot_.compact_bytes_read = compact_bytes_read;
|
||||
db_stats_snapshot_.compact_micros = compact_micros;
|
||||
}
|
||||
|
||||
void InternalStats::DumpCFStats(std::string* value) {
|
||||
@@ -789,7 +769,10 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
value->append(buf);
|
||||
}
|
||||
}
|
||||
uint64_t curr_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
|
||||
uint64_t flush_ingest = cf_stats_value_[BYTES_FLUSHED];
|
||||
uint64_t add_file_ingest = cf_stats_value_[BYTES_INGESTED_ADD_FILE];
|
||||
uint64_t curr_ingest = flush_ingest + add_file_ingest;
|
||||
// Cumulative summary
|
||||
double w_amp = stats_sum.bytes_written / static_cast<double>(curr_ingest + 1);
|
||||
uint64_t total_stall_count =
|
||||
@@ -804,19 +787,71 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
stats_sum);
|
||||
value->append(buf);
|
||||
// Interval summary
|
||||
uint64_t interval_flush_ingest =
|
||||
flush_ingest - cf_stats_snapshot_.ingest_bytes_flush;
|
||||
uint64_t interval_add_file_inget =
|
||||
add_file_ingest - cf_stats_snapshot_.ingest_bytes_add_file;
|
||||
uint64_t interval_ingest =
|
||||
curr_ingest - cf_stats_snapshot_.ingest_bytes + 1;
|
||||
interval_flush_ingest + interval_add_file_inget + 1;
|
||||
CompactionStats interval_stats(stats_sum);
|
||||
interval_stats.Subtract(cf_stats_snapshot_.comp_stats);
|
||||
w_amp = interval_stats.bytes_written / static_cast<double>(interval_ingest);
|
||||
PrintLevelStats(buf, sizeof(buf), "Int", 0, 0, 0, 0, w_amp, interval_stats);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Flush(GB): cumulative %.3f, interval %.3f\n",
|
||||
curr_ingest / kGB, interval_ingest / kGB);
|
||||
double seconds_up = (env_->NowMicros() - started_at_ + 1) / kMicrosInSec;
|
||||
double interval_seconds_up = seconds_up - cf_stats_snapshot_.seconds_up;
|
||||
snprintf(buf, sizeof(buf), "Uptime(secs): %.1f total, %.1f interval\n",
|
||||
seconds_up, interval_seconds_up);
|
||||
value->append(buf);
|
||||
|
||||
snprintf(buf, sizeof(buf), "Flush(GB): cumulative %.3f, interval %.3f\n",
|
||||
flush_ingest / kGB, interval_flush_ingest / kGB);
|
||||
snprintf(buf, sizeof(buf), "AddFile(GB): cumulative %.3f, interval %.3f\n",
|
||||
add_file_ingest / kGB, interval_add_file_inget / kGB);
|
||||
value->append(buf);
|
||||
|
||||
// Compact
|
||||
uint64_t compact_bytes_read = 0;
|
||||
uint64_t compact_bytes_write = 0;
|
||||
uint64_t compact_micros = 0;
|
||||
for (int level = 0; level < number_levels_; level++) {
|
||||
compact_bytes_read += comp_stats_[level].bytes_read_output_level +
|
||||
comp_stats_[level].bytes_read_non_output_levels;
|
||||
compact_bytes_write += comp_stats_[level].bytes_written;
|
||||
compact_micros += comp_stats_[level].micros;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"Cumulative compaction: %.2f GB write, %.2f MB/s write, "
|
||||
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
||||
compact_bytes_write / kGB, compact_bytes_write / kMB / seconds_up,
|
||||
compact_bytes_read / kGB, compact_bytes_read / kMB / seconds_up,
|
||||
compact_micros / kMicrosInSec);
|
||||
value->append(buf);
|
||||
|
||||
// Compaction interval
|
||||
uint64_t interval_compact_bytes_write =
|
||||
compact_bytes_write - cf_stats_snapshot_.compact_bytes_write;
|
||||
uint64_t interval_compact_bytes_read =
|
||||
compact_bytes_read - cf_stats_snapshot_.compact_bytes_read;
|
||||
uint64_t interval_compact_micros =
|
||||
compact_micros - cf_stats_snapshot_.compact_micros;
|
||||
|
||||
snprintf(
|
||||
buf, sizeof(buf),
|
||||
"Interval compaction: %.2f GB write, %.2f MB/s write, "
|
||||
"%.2f GB read, %.2f MB/s read, %.1f seconds\n",
|
||||
interval_compact_bytes_write / kGB,
|
||||
interval_compact_bytes_write / kMB / std::max(interval_seconds_up, 0.001),
|
||||
interval_compact_bytes_read / kGB,
|
||||
interval_compact_bytes_read / kMB / std::max(interval_seconds_up, 0.001),
|
||||
interval_compact_micros / kMicrosInSec);
|
||||
value->append(buf);
|
||||
cf_stats_snapshot_.compact_bytes_write = compact_bytes_write;
|
||||
cf_stats_snapshot_.compact_bytes_read = compact_bytes_read;
|
||||
cf_stats_snapshot_.compact_micros = compact_micros;
|
||||
|
||||
snprintf(buf, sizeof(buf), "Stalls(count): %" PRIu64
|
||||
" level0_slowdown, "
|
||||
"%" PRIu64
|
||||
@@ -845,7 +880,8 @@ void InternalStats::DumpCFStats(std::string* value) {
|
||||
total_stall_count - cf_stats_snapshot_.stall_count);
|
||||
value->append(buf);
|
||||
|
||||
cf_stats_snapshot_.ingest_bytes = curr_ingest;
|
||||
cf_stats_snapshot_.ingest_bytes_flush = flush_ingest;
|
||||
cf_stats_snapshot_.ingest_bytes_add_file = add_file_ingest;
|
||||
cf_stats_snapshot_.comp_stats = stats_sum;
|
||||
cf_stats_snapshot_.stall_count = total_stall_count;
|
||||
}
|
||||
|
||||
+17
-10
@@ -59,6 +59,7 @@ class InternalStats {
|
||||
HARD_PENDING_COMPACTION_BYTES_LIMIT,
|
||||
WRITE_STALLS_ENUM_MAX,
|
||||
BYTES_FLUSHED,
|
||||
BYTES_INGESTED_ADD_FILE,
|
||||
INTERNAL_CF_STATS_ENUM_MAX,
|
||||
};
|
||||
|
||||
@@ -244,13 +245,24 @@ class InternalStats {
|
||||
struct CFStatsSnapshot {
|
||||
// ColumnFamily-level stats
|
||||
CompactionStats comp_stats;
|
||||
uint64_t ingest_bytes; // Bytes written to L0
|
||||
uint64_t ingest_bytes_flush; // Bytes written to L0 (Flush)
|
||||
uint64_t ingest_bytes_add_file; // Bytes written to L0 (AddFile)
|
||||
uint64_t stall_count; // Stall count
|
||||
// Stats from compaction jobs - bytes written, bytes read, duration.
|
||||
uint64_t compact_bytes_write;
|
||||
uint64_t compact_bytes_read;
|
||||
uint64_t compact_micros;
|
||||
double seconds_up;
|
||||
|
||||
CFStatsSnapshot()
|
||||
: comp_stats(0),
|
||||
ingest_bytes(0),
|
||||
stall_count(0) {}
|
||||
ingest_bytes_flush(0),
|
||||
ingest_bytes_add_file(0),
|
||||
stall_count(0),
|
||||
compact_bytes_write(0),
|
||||
compact_bytes_read(0),
|
||||
compact_micros(0),
|
||||
seconds_up(0) {}
|
||||
} cf_stats_snapshot_;
|
||||
|
||||
struct DBStatsSnapshot {
|
||||
@@ -263,10 +275,6 @@ class InternalStats {
|
||||
// another thread.
|
||||
uint64_t write_other;
|
||||
uint64_t write_self;
|
||||
// Stats from compaction jobs - bytes written, bytes read, duration.
|
||||
uint64_t compact_bytes_write;
|
||||
uint64_t compact_bytes_read;
|
||||
uint64_t compact_micros;
|
||||
// Total number of keys written. write_self and write_other measure number
|
||||
// of write requests written, Each of the write request can contain updates
|
||||
// to multiple keys. num_keys_written is total number of keys updated by all
|
||||
@@ -283,9 +291,6 @@ class InternalStats {
|
||||
write_with_wal(0),
|
||||
write_other(0),
|
||||
write_self(0),
|
||||
compact_bytes_write(0),
|
||||
compact_bytes_read(0),
|
||||
compact_micros(0),
|
||||
num_keys_written(0),
|
||||
write_stall_micros(0),
|
||||
seconds_up(0) {}
|
||||
@@ -294,6 +299,7 @@ class InternalStats {
|
||||
// Handler functions for getting property values. They use "value" as a value-
|
||||
// result argument, and return true upon successfully setting "value".
|
||||
bool HandleNumFilesAtLevel(std::string* value, Slice suffix);
|
||||
bool HandleCompressionRatioAtLevelPrefix(std::string* value, Slice suffix);
|
||||
bool HandleLevelStats(std::string* value, Slice suffix);
|
||||
bool HandleStats(std::string* value, Slice suffix);
|
||||
bool HandleCFStats(std::string* value, Slice suffix);
|
||||
@@ -369,6 +375,7 @@ class InternalStats {
|
||||
HARD_PENDING_COMPACTION_BYTES_LIMIT,
|
||||
WRITE_STALLS_ENUM_MAX,
|
||||
BYTES_FLUSHED,
|
||||
BYTES_INGESTED_ADD_FILE,
|
||||
INTERNAL_CF_STATS_ENUM_MAX,
|
||||
};
|
||||
|
||||
|
||||
+3
-1
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "db/log_writer.h"
|
||||
|
||||
namespace rocksdb {
|
||||
@@ -76,6 +75,9 @@ struct JobContext {
|
||||
uint64_t prev_log_number;
|
||||
|
||||
uint64_t min_pending_output = 0;
|
||||
uint64_t prev_total_log_size = 0;
|
||||
size_t num_alive_log_files = 0;
|
||||
uint64_t size_log_to_delete = 0;
|
||||
|
||||
explicit JobContext(int _job_id, bool create_superversion = false) {
|
||||
job_id = _job_id;
|
||||
|
||||
+178
-2
@@ -484,7 +484,10 @@ TEST_F(EventListenerTest, CompactionReasonLevel) {
|
||||
listener->compaction_reasons_.clear();
|
||||
Reopen(options);
|
||||
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
Put("key", "value");
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, nullptr, nullptr));
|
||||
ASSERT_GT(listener->compaction_reasons_.size(), 0);
|
||||
for (auto compaction_reason : listener->compaction_reasons_) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kManualCompaction);
|
||||
@@ -579,7 +582,180 @@ TEST_F(EventListenerTest, CompactionReasonFIFO) {
|
||||
ASSERT_EQ(compaction_reason, CompactionReason::kFIFOMaxSize);
|
||||
}
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
class TableFileCreationListener : public EventListener {
|
||||
public:
|
||||
class TestEnv : public EnvWrapper {
|
||||
public:
|
||||
TestEnv() : EnvWrapper(Env::Default()) {}
|
||||
|
||||
void SetStatus(Status s) { status_ = s; }
|
||||
|
||||
Status NewWritableFile(const std::string& fname,
|
||||
std::unique_ptr<WritableFile>* result,
|
||||
const EnvOptions& options) {
|
||||
if (fname.size() > 4 && fname.substr(fname.size() - 4) == ".sst") {
|
||||
if (!status_.ok()) {
|
||||
return status_;
|
||||
}
|
||||
}
|
||||
return Env::Default()->NewWritableFile(fname, result, options);
|
||||
}
|
||||
|
||||
private:
|
||||
Status status_;
|
||||
};
|
||||
|
||||
TableFileCreationListener() {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
started_[i] = finished_[i] = failure_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Index(TableFileCreationReason reason) {
|
||||
int idx;
|
||||
switch (reason) {
|
||||
case TableFileCreationReason::kFlush:
|
||||
idx = 0;
|
||||
break;
|
||||
case TableFileCreationReason::kCompaction:
|
||||
idx = 1;
|
||||
break;
|
||||
default:
|
||||
idx = -1;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
void CheckAndResetCounters(int flush_started, int flush_finished,
|
||||
int flush_failure, int compaction_started,
|
||||
int compaction_finished, int compaction_failure) {
|
||||
ASSERT_EQ(started_[0], flush_started);
|
||||
ASSERT_EQ(finished_[0], flush_finished);
|
||||
ASSERT_EQ(failure_[0], flush_failure);
|
||||
ASSERT_EQ(started_[1], compaction_started);
|
||||
ASSERT_EQ(finished_[1], compaction_finished);
|
||||
ASSERT_EQ(failure_[1], compaction_failure);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
started_[i] = finished_[i] = failure_[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void OnTableFileCreationStarted(
|
||||
const TableFileCreationBriefInfo& info) override {
|
||||
int idx = Index(info.reason);
|
||||
if (idx >= 0) {
|
||||
started_[idx]++;
|
||||
}
|
||||
ASSERT_GT(info.db_name.size(), 0U);
|
||||
ASSERT_GT(info.cf_name.size(), 0U);
|
||||
ASSERT_GT(info.file_path.size(), 0U);
|
||||
ASSERT_GT(info.job_id, 0);
|
||||
}
|
||||
|
||||
void OnTableFileCreated(const TableFileCreationInfo& info) override {
|
||||
int idx = Index(info.reason);
|
||||
if (idx >= 0) {
|
||||
finished_[idx]++;
|
||||
}
|
||||
ASSERT_GT(info.db_name.size(), 0U);
|
||||
ASSERT_GT(info.cf_name.size(), 0U);
|
||||
ASSERT_GT(info.file_path.size(), 0U);
|
||||
ASSERT_GT(info.job_id, 0);
|
||||
if (info.status.ok()) {
|
||||
ASSERT_GT(info.table_properties.data_size, 0U);
|
||||
ASSERT_GT(info.table_properties.raw_key_size, 0U);
|
||||
ASSERT_GT(info.table_properties.raw_value_size, 0U);
|
||||
ASSERT_GT(info.table_properties.num_data_blocks, 0U);
|
||||
ASSERT_GT(info.table_properties.num_entries, 0U);
|
||||
} else {
|
||||
if (idx >= 0) {
|
||||
failure_[idx]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TestEnv test_env;
|
||||
int started_[2];
|
||||
int finished_[2];
|
||||
int failure_[2];
|
||||
};
|
||||
|
||||
TEST_F(EventListenerTest, TableFileCreationListenersTest) {
|
||||
auto listener = std::make_shared<TableFileCreationListener>();
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.listeners.push_back(listener);
|
||||
options.env = &listener->test_env;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put("foo", "aaa"));
|
||||
ASSERT_OK(Put("bar", "bbb"));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
|
||||
|
||||
ASSERT_OK(Put("foo", "aaa1"));
|
||||
ASSERT_OK(Put("bar", "bbb1"));
|
||||
listener->test_env.SetStatus(Status::NotSupported("not supported"));
|
||||
ASSERT_NOK(Flush());
|
||||
listener->CheckAndResetCounters(1, 1, 1, 0, 0, 0);
|
||||
listener->test_env.SetStatus(Status::OK());
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("foo", "aaa2"));
|
||||
ASSERT_OK(Put("bar", "bbb2"));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 0, 0, 0);
|
||||
|
||||
const Slice kRangeStart = "a";
|
||||
const Slice kRangeEnd = "z";
|
||||
dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
listener->CheckAndResetCounters(0, 0, 0, 1, 1, 0);
|
||||
|
||||
ASSERT_OK(Put("foo", "aaa3"));
|
||||
ASSERT_OK(Put("bar", "bbb3"));
|
||||
ASSERT_OK(Flush());
|
||||
listener->test_env.SetStatus(Status::NotSupported("not supported"));
|
||||
dbfull()->CompactRange(CompactRangeOptions(), &kRangeStart, &kRangeEnd);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
listener->CheckAndResetCounters(1, 1, 0, 1, 1, 1);
|
||||
}
|
||||
|
||||
class MemTableSealedListener : public EventListener {
|
||||
private:
|
||||
SequenceNumber latest_seq_number_;
|
||||
public:
|
||||
MemTableSealedListener() {}
|
||||
void OnMemTableSealed(const MemTableInfo& info) override {
|
||||
latest_seq_number_ = info.first_seqno;
|
||||
}
|
||||
|
||||
void OnFlushCompleted(DB* /*db*/,
|
||||
const FlushJobInfo& flush_job_info) override {
|
||||
ASSERT_LE(flush_job_info.smallest_seqno, latest_seq_number_);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(EventListenerTest, MemTableSealedListenerTest) {
|
||||
auto listener = std::make_shared<MemTableSealedListener>();
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.listeners.push_back(listener);
|
||||
DestroyAndReopen(options);
|
||||
|
||||
for (unsigned int i = 0; i < 10; i++) {
|
||||
std::string tag = std::to_string(i);
|
||||
ASSERT_OK(Put("foo"+tag, "aaa"));
|
||||
ASSERT_OK(Put("bar"+tag, "bbb"));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
}
|
||||
} // namespace rocksdb
|
||||
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
|
||||
+34
-13
@@ -22,9 +22,8 @@ Reader::Reporter::~Reporter() {
|
||||
}
|
||||
|
||||
Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
unique_ptr<SequentialFileReader>&& _file,
|
||||
Reporter* reporter, bool checksum, uint64_t initial_offset,
|
||||
uint64_t log_num)
|
||||
unique_ptr<SequentialFileReader>&& _file, Reporter* reporter,
|
||||
bool checksum, uint64_t initial_offset, uint64_t log_num)
|
||||
: info_log_(info_log),
|
||||
file_(std::move(_file)),
|
||||
reporter_(reporter),
|
||||
@@ -37,7 +36,8 @@ Reader::Reader(std::shared_ptr<Logger> info_log,
|
||||
last_record_offset_(0),
|
||||
end_of_buffer_offset_(0),
|
||||
initial_offset_(initial_offset),
|
||||
log_number_(log_num) {}
|
||||
log_number_(log_num),
|
||||
recycled_(false) {}
|
||||
|
||||
Reader::~Reader() {
|
||||
delete[] backing_store_;
|
||||
@@ -91,7 +91,7 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
Slice fragment;
|
||||
while (true) {
|
||||
uint64_t physical_record_offset = end_of_buffer_offset_ - buffer_.size();
|
||||
size_t drop_size;
|
||||
size_t drop_size = 0;
|
||||
const unsigned int record_type = ReadPhysicalRecord(&fragment, &drop_size);
|
||||
switch (record_type) {
|
||||
case kFullType:
|
||||
@@ -191,6 +191,26 @@ bool Reader::ReadRecord(Slice* record, std::string* scratch,
|
||||
}
|
||||
break;
|
||||
|
||||
case kBadRecordLen:
|
||||
case kBadRecordChecksum:
|
||||
if (recycled_ &&
|
||||
wal_recovery_mode ==
|
||||
WALRecoveryMode::kTolerateCorruptedTailRecords) {
|
||||
scratch->clear();
|
||||
return false;
|
||||
}
|
||||
if (record_type == kBadRecordLen) {
|
||||
ReportCorruption(drop_size, "bad record length");
|
||||
} else {
|
||||
ReportCorruption(drop_size, "checksum mismatch");
|
||||
}
|
||||
if (in_fragmented_record) {
|
||||
ReportCorruption(scratch->size(), "error in middle of record");
|
||||
in_fragmented_record = false;
|
||||
scratch->clear();
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
char buf[40];
|
||||
snprintf(buf, sizeof(buf), "unknown record type %u", record_type);
|
||||
@@ -324,7 +344,7 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
if (buffer_.size() < (size_t)kHeaderSize) {
|
||||
int r;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
return r;
|
||||
return r;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -337,14 +357,17 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
const uint32_t length = a | (b << 8);
|
||||
int header_size = kHeaderSize;
|
||||
if (type >= kRecyclableFullType && type <= kRecyclableLastType) {
|
||||
if (end_of_buffer_offset_ - buffer_.size() == 0) {
|
||||
recycled_ = true;
|
||||
}
|
||||
header_size = kRecyclableHeaderSize;
|
||||
// We need enough for the larger header
|
||||
if (buffer_.size() < (size_t)kRecyclableHeaderSize) {
|
||||
int r;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
int r;
|
||||
if (!ReadMore(drop_size, &r)) {
|
||||
return r;
|
||||
}
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
const uint32_t log_num = DecodeFixed32(header + 7);
|
||||
if (log_num != log_number_) {
|
||||
@@ -355,8 +378,7 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
*drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
if (!eof_) {
|
||||
ReportCorruption(*drop_size, "bad record length");
|
||||
return kBadRecord;
|
||||
return kBadRecordLen;
|
||||
}
|
||||
// If the end of the file has been reached without reading |length| bytes
|
||||
// of payload, assume the writer died in the middle of writing the record.
|
||||
@@ -388,8 +410,7 @@ unsigned int Reader::ReadPhysicalRecord(Slice* result, size_t* drop_size) {
|
||||
// like a valid log record.
|
||||
*drop_size = buffer_.size();
|
||||
buffer_.clear();
|
||||
ReportCorruption(*drop_size, "checksum mismatch");
|
||||
return kBadRecord;
|
||||
return kBadRecordChecksum;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,9 @@ class Reader {
|
||||
// which log number this is
|
||||
uint64_t const log_number_;
|
||||
|
||||
// Whether this is a recycled log file
|
||||
bool recycled_;
|
||||
|
||||
// Extend record types with the following special values
|
||||
enum {
|
||||
kEof = kMaxRecordType + 1,
|
||||
@@ -126,6 +129,10 @@ class Reader {
|
||||
kBadHeader = kMaxRecordType + 3,
|
||||
// Returned when we read an old record from a previous user of the log.
|
||||
kOldRecord = kMaxRecordType + 4,
|
||||
// Returned when we get a bad record length
|
||||
kBadRecordLen = kMaxRecordType + 5,
|
||||
// Returned when we get a bad record checksum
|
||||
kBadRecordChecksum = kMaxRecordType + 6,
|
||||
};
|
||||
|
||||
// Skips all blocks that are completely before "initial_offset_".
|
||||
|
||||
Loaded 100 of 713 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user