mirror of
https://github.com/facebook/rocksdb.git
synced 2026-11-06 19:12:36 -05:00
Compare commits
455
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1178bf775f | ||
|
|
1ababeb76a | ||
|
|
a64c8ca7a8 | ||
|
|
dfedc74d82 | ||
|
|
312d9c47b4 | ||
|
|
9b53f14a35 | ||
|
|
56810142a5 | ||
|
|
29102641dd | ||
|
|
081722780b | ||
|
|
2b60621f16 | ||
|
|
21f8a57f2a | ||
|
|
67a7b74b7f | ||
|
|
82afa01815 | ||
|
|
f72c834eab | ||
|
|
cfc57f55b5 | ||
|
|
ec9082d698 | ||
|
|
560fe70233 | ||
|
|
a2b9be42b6 | ||
|
|
a5ec5e3ea0 | ||
|
|
230c98f3ce | ||
|
|
b1c27a52d2 | ||
|
|
73e6b89fad | ||
|
|
2b70224f82 | ||
|
|
0e12b1d691 | ||
|
|
01bd86ad35 | ||
|
|
d263505417 | ||
|
|
8e59a1dc9a | ||
|
|
8f4f302316 | ||
|
|
49af999954 | ||
|
|
44d04582cb | ||
|
|
fdf2a0d7eb | ||
|
|
f2d11b3fdc | ||
|
|
92e2399669 | ||
|
|
a7d4bea43a | ||
|
|
f24c39ab3d | ||
|
|
5bf9a7d5ee | ||
|
|
82846f41d3 | ||
|
|
4ec31dc8ac | ||
|
|
dc00e4b120 | ||
|
|
e970248602 | ||
|
|
f72fd58565 | ||
|
|
633f069c29 | ||
|
|
0a73ada7b5 | ||
|
|
0c53b41856 | ||
|
|
4217d1bce7 | ||
|
|
c66b4429ff | ||
|
|
6d93b87588 | ||
|
|
f20b07cebb | ||
|
|
f053851af6 | ||
|
|
b234a3f569 | ||
|
|
0534393fc8 | ||
|
|
8fb3fe8d39 | ||
|
|
8d615a2b1d | ||
|
|
ad5325a736 | ||
|
|
aa21896880 | ||
|
|
f0841d4faf | ||
|
|
314de7e7de | ||
|
|
86cf7266c3 | ||
|
|
53a0ab2bea | ||
|
|
b4326b5273 | ||
|
|
908a999a72 | ||
|
|
9d66d6d13e | ||
|
|
3ffb3baa0b | ||
|
|
678ba5e41c | ||
|
|
ab6755711b | ||
|
|
fbb09cf7aa | ||
|
|
97b30dee5b | ||
|
|
c246c9c6e2 | ||
|
|
140db3c44e | ||
|
|
e5aa7deae1 | ||
|
|
4c277ab201 | ||
|
|
f5526af8ed | ||
|
|
ffc48b6cad | ||
|
|
37418105d0 | ||
|
|
4bfd415e34 | ||
|
|
b4e59a48fd | ||
|
|
7cc52cd8f5 | ||
|
|
22d4dc5066 | ||
|
|
e1139167ae | ||
|
|
1a79839c59 | ||
|
|
3e1bf771a3 | ||
|
|
a282eff3d1 | ||
|
|
ee239df351 | ||
|
|
6c3bf83d6f | ||
|
|
418831cf45 | ||
|
|
bcd049cd2d | ||
|
|
2f1296ef48 | ||
|
|
c0ec58ecb9 | ||
|
|
fcaa7ff638 | ||
|
|
8717c26823 | ||
|
|
b632ed0c67 | ||
|
|
699f45049d | ||
|
|
fe994bbd0b | ||
|
|
53e595d1f3 | ||
|
|
d16ceba687 | ||
|
|
a776406de3 | ||
|
|
787229837e | ||
|
|
2cdaf5ca5b | ||
|
|
84d71f30c4 | ||
|
|
e36b9da57c | ||
|
|
7f08a8503f | ||
|
|
e5bfb91d09 | ||
|
|
1953b63cdd | ||
|
|
532ff334d9 | ||
|
|
2acffecca1 | ||
|
|
61a63ae2f9 | ||
|
|
13ae16c315 | ||
|
|
559943cdc0 | ||
|
|
6b34eb0ebc | ||
|
|
d6bd1a0291 | ||
|
|
7fd68b7c39 | ||
|
|
b88109db19 | ||
|
|
345f4c9462 | ||
|
|
a2f29ce70a | ||
|
|
78afb4d81e | ||
|
|
0774d640c0 | ||
|
|
b59b7570cf | ||
|
|
e0f697d2bd | ||
|
|
b92cef2d1d | ||
|
|
6d424be910 | ||
|
|
791bff5b4e | ||
|
|
0595101c4f | ||
|
|
7c6a7e8fa8 | ||
|
|
b25f2afeff | ||
|
|
be206db351 | ||
|
|
c988e4720b | ||
|
|
58444eadda | ||
|
|
9320067703 | ||
|
|
fcce1f2c7a | ||
|
|
1de588668c | ||
|
|
5268cdc997 | ||
|
|
6924869867 | ||
|
|
d497cdfbb2 | ||
|
|
99fe4c5005 | ||
|
|
1c290c785d | ||
|
|
b512f4bc76 | ||
|
|
ba48ff8303 | ||
|
|
4149d044cd | ||
|
|
65411b8d4e | ||
|
|
272cc77751 | ||
|
|
d6aa8c49f8 | ||
|
|
4f1dd05cec | ||
|
|
b97c53b629 | ||
|
|
64ca0d9b46 | ||
|
|
f4a1d10668 | ||
|
|
2819c7840e | ||
|
|
82e7631de6 | ||
|
|
5c92aa38ea | ||
|
|
7743f033b1 | ||
|
|
8df334342e | ||
|
|
12d798ac06 | ||
|
|
d648cb47b9 | ||
|
|
e10e4162c8 | ||
|
|
34afdc2d3d | ||
|
|
0b6be7eb68 | ||
|
|
a5566d508b | ||
|
|
7bef598440 | ||
|
|
306b779957 | ||
|
|
9bb6cc7e78 | ||
|
|
dafa584fd1 | ||
|
|
2a2b3e03a5 | ||
|
|
5f40b05c98 | ||
|
|
bda8d93ba9 | ||
|
|
dc0dc90cf5 | ||
|
|
12542488ef | ||
|
|
0fb938c448 | ||
|
|
7e78d7c540 | ||
|
|
0aad4ca0ff | ||
|
|
6785135bc2 | ||
|
|
eea566864e | ||
|
|
45175ca2e1 | ||
|
|
dd092c2d11 | ||
|
|
91b95cadee | ||
|
|
0cb0fc6fd3 | ||
|
|
7aa8447728 | ||
|
|
beed86473a | ||
|
|
e40b04e9fa | ||
|
|
cb5b851ff8 | ||
|
|
9308ff366c | ||
|
|
6cca9fab7c | ||
|
|
941543721d | ||
|
|
0ef88538c6 | ||
|
|
4750421ece | ||
|
|
55ef8972fc | ||
|
|
e8eb02145a | ||
|
|
972e3400e1 | ||
|
|
0c942a9c0d | ||
|
|
361895ad79 | ||
|
|
e8a7001159 | ||
|
|
b9e9872819 | ||
|
|
71bbe79abd | ||
|
|
2e03abf9df | ||
|
|
af292cef27 | ||
|
|
ac48e0d589 | ||
|
|
c9cd5d25a8 | ||
|
|
32752551b9 | ||
|
|
f9ffeaed3f | ||
|
|
59eb4b35a6 | ||
|
|
2e09a54c4f | ||
|
|
f756448f28 | ||
|
|
35dd251187 | ||
|
|
240c4126fd | ||
|
|
7b55554605 | ||
|
|
ec9f52ece6 | ||
|
|
13ded69484 | ||
|
|
6c2bd28a61 | ||
|
|
2bc914094d | ||
|
|
7ddc096d7d | ||
|
|
22ecd7edc1 | ||
|
|
ebaa3c8a59 | ||
|
|
4afa24f8ae | ||
|
|
6e63e77af1 | ||
|
|
f484a60d1f | ||
|
|
d8eb824325 | ||
|
|
f235f4b0a3 | ||
|
|
785faf2d07 | ||
|
|
318fe6941a | ||
|
|
229350ef48 | ||
|
|
74cfe7db60 | ||
|
|
c521f22a1e | ||
|
|
f6437ea4d7 | ||
|
|
249b1078c9 | ||
|
|
1a5eb33d91 | ||
|
|
0637c8d36c | ||
|
|
2e062b2227 | ||
|
|
8c9e689790 | ||
|
|
04db764831 | ||
|
|
22161b7547 | ||
|
|
2a383f21f4 | ||
|
|
baf22b4ee6 | ||
|
|
f35042ca40 | ||
|
|
22f2936b35 | ||
|
|
5efec84c60 | ||
|
|
ff8953380f | ||
|
|
9eb002fcf0 | ||
|
|
c625b8d017 | ||
|
|
d10801e983 | ||
|
|
b6269b078a | ||
|
|
2b367fa8cc | ||
|
|
6878cedcc3 | ||
|
|
0729b287e9 | ||
|
|
add68bd28a | ||
|
|
a207c27809 | ||
|
|
5de333fd99 | ||
|
|
9b0a32f802 | ||
|
|
48c468c22e | ||
|
|
c55460c734 | ||
|
|
e51be2c5a1 | ||
|
|
74a652a45f | ||
|
|
f58d276764 | ||
|
|
a53563d86e | ||
|
|
6450e9fc38 | ||
|
|
87e2358736 | ||
|
|
e3a96c4823 | ||
|
|
f63331ebaf | ||
|
|
82b81dc8b5 | ||
|
|
a756fb9c85 | ||
|
|
61f83dfeb7 | ||
|
|
e7c24168d8 | ||
|
|
052c24a668 | ||
|
|
d4b75d295f | ||
|
|
23ffed9cb7 | ||
|
|
87882736ef | ||
|
|
e95c570047 | ||
|
|
543a201b93 | ||
|
|
d56f74a4db | ||
|
|
a7fd1d0881 | ||
|
|
8ca081780b | ||
|
|
d057e8326d | ||
|
|
fd2079938d | ||
|
|
9b25d26dc8 | ||
|
|
b01a428d9b | ||
|
|
a685a701ca | ||
|
|
a074d46a5a | ||
|
|
c268859aaa | ||
|
|
dbe3810c74 | ||
|
|
08af0ae3f0 | ||
|
|
0879c24040 | ||
|
|
8b2f60b668 | ||
|
|
b278152261 | ||
|
|
4811115b3e | ||
|
|
3f7e929865 | ||
|
|
8e91bd90d2 | ||
|
|
8f52972cf9 | ||
|
|
ab7f7c9e49 | ||
|
|
066b51126d | ||
|
|
1d34cd797e | ||
|
|
9501279d5f | ||
|
|
e8f218cb68 | ||
|
|
0804b44fb6 | ||
|
|
a4b8ac9a73 | ||
|
|
e0ff365a76 | ||
|
|
d6006f9c9b | ||
|
|
74b7c0d249 | ||
|
|
e352bd5742 | ||
|
|
eec79b39a6 | ||
|
|
ddf439c561 | ||
|
|
3aee4fbd41 | ||
|
|
9ddb55a8f6 | ||
|
|
4361d6d163 | ||
|
|
daf7e77a6b | ||
|
|
2febf1c45c | ||
|
|
4171e3db9b | ||
|
|
55f7ded80d | ||
|
|
3b27725245 | ||
|
|
c521a9ab2b | ||
|
|
61c9bd49c1 | ||
|
|
2e5388178f | ||
|
|
c4a503f3df | ||
|
|
84eef260de | ||
|
|
9b41082d4a | ||
|
|
42eaa45c1b | ||
|
|
6b4cdacf41 | ||
|
|
87e82a41a9 | ||
|
|
66ca5ac427 | ||
|
|
bbc85a5f22 | ||
|
|
f4529a54bb | ||
|
|
5535d06b9c | ||
|
|
d5f3b77f23 | ||
|
|
9e885939a3 | ||
|
|
c04a86a0e9 | ||
|
|
3455ab0e2b | ||
|
|
39a07c9651 | ||
|
|
a379dae4f7 | ||
|
|
ac37bfded0 | ||
|
|
df5dc73bec | ||
|
|
1e5b631e51 | ||
|
|
0229a88dfe | ||
|
|
42ba60b3ba | ||
|
|
aeb913dd01 | ||
|
|
b678cb1f86 | ||
|
|
206845c057 | ||
|
|
4e4ec16957 | ||
|
|
5ad3227650 | ||
|
|
31193a73a4 | ||
|
|
803a40d412 | ||
|
|
870033291a | ||
|
|
7b9ecd4067 | ||
|
|
e8e911a11c | ||
|
|
a53d6d25e0 | ||
|
|
da90e23998 | ||
|
|
955b80e84f | ||
|
|
0b75b22321 | ||
|
|
bbdc4f2e9a | ||
|
|
c8665611bc | ||
|
|
5afd1e309c | ||
|
|
d1b70b05a6 | ||
|
|
837705ad80 | ||
|
|
bb485e986a | ||
|
|
5dd18a8d8e | ||
|
|
f127d459ad | ||
|
|
ac3f3f3719 | ||
|
|
b1a53db327 | ||
|
|
ed8eb436db | ||
|
|
714ce5041d | ||
|
|
df4197ca6e | ||
|
|
fcd8088333 | ||
|
|
f33611d5e9 | ||
|
|
1ae026c400 | ||
|
|
570248aeff | ||
|
|
9dc887ece0 | ||
|
|
c76778e2bd | ||
|
|
b20737709f | ||
|
|
a95a776d75 | ||
|
|
41d32152ce | ||
|
|
d45b837701 | ||
|
|
ba224b75c7 | ||
|
|
41c4b665f4 | ||
|
|
a0cbb69421 | ||
|
|
93a7389442 | ||
|
|
3503f28982 | ||
|
|
89f66d4484 | ||
|
|
bac399449d | ||
|
|
390c5246d2 | ||
|
|
373e3a154d | ||
|
|
be219089ad | ||
|
|
6f9ed59b1d | ||
|
|
be8199cdb9 | ||
|
|
a904c62d28 | ||
|
|
3d844dff1d | ||
|
|
95d0ee95fa | ||
|
|
66b62a123a | ||
|
|
68d8b28389 | ||
|
|
75741eb0ce | ||
|
|
d44ef2ed4d | ||
|
|
6adc39e1bf | ||
|
|
3f20925dc4 | ||
|
|
3ab0eae860 | ||
|
|
005c046734 | ||
|
|
0a1aed4e71 | ||
|
|
f89423a57a | ||
|
|
b511b042f4 | ||
|
|
cbb3b25915 | ||
|
|
54d73d6429 | ||
|
|
065bea1587 | ||
|
|
a50da404be | ||
|
|
e9b627fd56 | ||
|
|
6f33901899 | ||
|
|
7ed69fbe72 | ||
|
|
19a89267ca | ||
|
|
82a70e1470 | ||
|
|
d96b0127a0 | ||
|
|
8ea0a2c1bd | ||
|
|
e817bc9628 | ||
|
|
5ba1b6e549 | ||
|
|
d53f7ff69a | ||
|
|
865a25101d | ||
|
|
575ea26ec9 | ||
|
|
298edae941 | ||
|
|
d81c2d1e3d | ||
|
|
ecccc63179 | ||
|
|
d5bd0039b9 | ||
|
|
25be1ed66a | ||
|
|
32a4d59477 | ||
|
|
b788e3f497 | ||
|
|
281ac9c89e | ||
|
|
99908db998 | ||
|
|
416dc7bed7 | ||
|
|
3981430f54 | ||
|
|
dcddc1065e | ||
|
|
9047fd7766 | ||
|
|
79407a8a61 | ||
|
|
d5a46c40e5 | ||
|
|
d60ae5b1c7 | ||
|
|
0d0aa578ff | ||
|
|
146263887f | ||
|
|
b3dbeadc34 | ||
|
|
58162835d1 | ||
|
|
a42a342a7a | ||
|
|
6ad0810393 | ||
|
|
3897ce3125 | ||
|
|
d61a449364 | ||
|
|
5a2b4ed671 | ||
|
|
f44e69c64a | ||
|
|
db325a5904 | ||
|
|
2f93a3b809 | ||
|
|
80a59a03a7 | ||
|
|
707f8d168a | ||
|
|
4d5b575563 | ||
|
|
9167ece586 | ||
|
|
799cf37cb1 | ||
|
|
2655477c67 | ||
|
|
eda83eaac0 | ||
|
|
ab718b415f | ||
|
|
d561af487c | ||
|
|
9e701b48e0 | ||
|
|
1c88f66ff8 | ||
|
|
cda7923169 | ||
|
|
b53e3d2adb | ||
|
|
c75ef03e58 | ||
|
|
dfa6b408fe | ||
|
|
886774eabf | ||
|
|
956ce9bde2 | ||
|
|
a607b88240 | ||
|
|
a4405fd981 |
No files matched your search
+108
-45
@@ -5,9 +5,9 @@ orbs:
|
||||
slack: circleci/slack@3.4.2
|
||||
|
||||
aliases:
|
||||
- ¬ify-on-master-failure
|
||||
- ¬ify-on-main-failure
|
||||
fail_only: true
|
||||
only_for_branches: master
|
||||
only_for_branches: main
|
||||
|
||||
commands:
|
||||
install-pyenv-on-macos:
|
||||
@@ -49,6 +49,9 @@ commands:
|
||||
echo "export GTEST_OUTPUT=\"xml:/tmp/test-results/\"" >> $BASH_ENV
|
||||
echo "export SKIP_FORMAT_BUCK_CHECKS=1" >> $BASH_ENV
|
||||
echo "export PRINT_PARALLEL_OUTPUTS=1" >> $BASH_ENV
|
||||
echo "export GTEST_COLOR=1" >> $BASH_ENV
|
||||
echo "export CTEST_OUTPUT_ON_FAILURE=1" >> $BASH_ENV
|
||||
echo "export CTEST_TEST_TIMEOUT=300" >> $BASH_ENV
|
||||
|
||||
pre-steps-macos:
|
||||
steps:
|
||||
@@ -57,7 +60,7 @@ commands:
|
||||
|
||||
post-steps:
|
||||
steps:
|
||||
- slack/status: *notify-on-master-failure
|
||||
- slack/status: *notify-on-main-failure
|
||||
- store_test_results: # store test result if there's any
|
||||
path: /tmp/test-results
|
||||
- store_artifacts: # store LOG for debugging if there's any
|
||||
@@ -88,6 +91,28 @@ commands:
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libgflags-dev
|
||||
|
||||
install-benchmark:
|
||||
steps:
|
||||
- run: # currently doesn't support ubuntu-1604 which doesn't have libbenchmark package, user can still install by building it youself
|
||||
name: Install benchmark
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libbenchmark-dev
|
||||
|
||||
install-librados:
|
||||
steps:
|
||||
- run:
|
||||
name: Install librados
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y librados-dev
|
||||
|
||||
upgrade-cmake:
|
||||
steps:
|
||||
- run:
|
||||
name: Upgrade cmake
|
||||
command: |
|
||||
sudo apt remove --purge cmake
|
||||
sudo snap install cmake --classic
|
||||
|
||||
install-gflags-on-macos:
|
||||
steps:
|
||||
- run:
|
||||
@@ -140,12 +165,12 @@ jobs:
|
||||
- install-cmake-on-macos
|
||||
- install-gflags-on-macos
|
||||
- pre-steps-macos
|
||||
- run: ulimit -S -n 1048576 && (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j32) | .circleci/cat_ignore_eagain
|
||||
- run: ulimit -S -n 1048576 && (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j32 && ctest -j10) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -153,19 +178,20 @@ jobs:
|
||||
- run: make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-mem-env:
|
||||
build-linux-mem-env-librados:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: MEM_ENV=1 make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- install-librados
|
||||
- run: MEM_ENV=1 ROCKSDB_USE_LIBRADOS=1 make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-encrypted-env:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -175,7 +201,7 @@ jobs:
|
||||
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -185,7 +211,7 @@ jobs:
|
||||
|
||||
build-linux-release:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
@@ -212,7 +238,7 @@ jobs:
|
||||
|
||||
build-linux-lite:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -222,7 +248,7 @@ jobs:
|
||||
|
||||
build-linux-lite-release:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
@@ -235,7 +261,7 @@ jobs:
|
||||
|
||||
build-linux-clang-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
@@ -245,7 +271,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-asan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -256,7 +282,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-mini-tsan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -267,7 +293,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-ubsan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -278,7 +304,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-clang-analyze:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -288,16 +314,29 @@ jobs:
|
||||
- run: CC=clang-10 CXX=clang++-10 ROCKSDB_DISABLE_ALIGNED_NEW=1 CLANG_ANALYZER="/usr/bin/clang++-10" CLANG_SCAN_BUILD=scan-build-10 USE_CLANG=1 make V=1 -j32 analyze | .circleci/cat_ignore_eagain # aligned new doesn't work for reason we haven't figured out. For unknown, reason passing "clang++-10" as CLANG_ANALYZER doesn't work, and we need a full path.
|
||||
- post-steps
|
||||
|
||||
build-linux-cmake-no_test_run:
|
||||
build-linux-cmake:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: large
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=0 .. && make V=1 -j8) | .circleci/cat_ignore_eagain
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- upgrade-cmake
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j20 && ctest -j20) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-unity:
|
||||
build-linux-cmake-ubuntu-20:
|
||||
machine:
|
||||
image: ubuntu-2004:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- install-benchmark
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 -DWITH_BENCHMARK=1 .. && make V=1 -j20 && ctest -j20 && make microbench) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-unity-and-headers:
|
||||
docker: # executor type
|
||||
- image: gcc:latest
|
||||
resource_class: large
|
||||
@@ -305,11 +344,12 @@ jobs:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: apt-get update -y && apt-get install -y libgflags-dev
|
||||
- run: TEST_TMPDIR=/dev/shm && make V=1 -j8 unity_test | .circleci/cat_ignore_eagain
|
||||
- run: make V=1 -j8 -k check-headers | .circleci/cat_ignore_eagain # could be moved to a different build
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-4_8-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -347,6 +387,27 @@ jobs:
|
||||
- run: CC=gcc-10 CXX=g++-10 V=1 SKIP_LINK=1 ROCKSDB_CXX_STANDARD=c++20 make -j16 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-11-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-2004:202010-01
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: sudo apt-get update -y && sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test && sudo apt-get install gcc-11 g++-11 libgflags-dev
|
||||
- run: CC=gcc-11 CXX=g++-11 V=1 SKIP_LINK=1 make -j16 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- post-steps
|
||||
|
||||
# This job is only to make sure the microbench tests are able to run, the benchmark result is not meaningful as the CI host is changing.
|
||||
build-linux-microbench:
|
||||
machine:
|
||||
image: ubuntu-2004:202010-01
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-benchmark
|
||||
- run: DEBUG_LEVEL=0 make microbench | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-windows:
|
||||
executor: windows-2xlarge
|
||||
parameters:
|
||||
@@ -417,7 +478,7 @@ jobs:
|
||||
|
||||
build-linux-java:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
@@ -441,7 +502,7 @@ jobs:
|
||||
|
||||
build-linux-java-static:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
@@ -512,7 +573,7 @@ jobs:
|
||||
|
||||
build-examples:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -525,7 +586,7 @@ jobs:
|
||||
|
||||
build-cmake-mingw:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -548,7 +609,7 @@ jobs:
|
||||
|
||||
build-linux-non-shm:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
parameters:
|
||||
start_test:
|
||||
@@ -604,6 +665,7 @@ jobs:
|
||||
JAVA_HOME: /usr/lib/jvm/java-8-openjdk-arm64
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
@@ -616,7 +678,7 @@ jobs:
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=0 -DWITH_GFLAGS=0 -DWITH_BENCHMARK_TOOLS=0 -DWITH_TOOLS=0 -DWITH_CORE_TOOLS=1 ..
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=0 -DWITH_GFLAGS=1 -DWITH_BENCHMARK_TOOLS=0 -DWITH_TOOLS=0 -DWITH_CORE_TOOLS=1 ..
|
||||
make -j4
|
||||
- run:
|
||||
name: "Build Java with cmake"
|
||||
@@ -624,13 +686,13 @@ jobs:
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=0 ..
|
||||
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 ..
|
||||
make -j4 rocksdb rocksdbjni
|
||||
- post-steps
|
||||
|
||||
build-format-compatible:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -650,9 +712,13 @@ workflows:
|
||||
build-linux:
|
||||
jobs:
|
||||
- build-linux
|
||||
build-linux-mem-env:
|
||||
build-linux-cmake:
|
||||
jobs:
|
||||
- build-linux-mem-env
|
||||
- build-linux-cmake
|
||||
- build-linux-cmake-ubuntu-20
|
||||
build-linux-mem-env-librados:
|
||||
jobs:
|
||||
- build-linux-mem-env-librados
|
||||
build-linux-encrypted-env:
|
||||
jobs:
|
||||
- build-linux-encrypted-env
|
||||
@@ -683,9 +749,9 @@ workflows:
|
||||
build-linux-clang10-clang-analyze:
|
||||
jobs:
|
||||
- build-linux-clang10-clang-analyze
|
||||
build-linux-unity:
|
||||
build-linux-unity-and-headers:
|
||||
jobs:
|
||||
- build-linux-unity
|
||||
- build-linux-unity-and-headers
|
||||
build-windows-vs2019:
|
||||
jobs:
|
||||
- build-windows:
|
||||
@@ -701,12 +767,6 @@ workflows:
|
||||
name: "build-windows-vs2017"
|
||||
vs_year: "2017"
|
||||
cmake_generator: "Visual Studio 15 Win64"
|
||||
# build-windows-vs2015:
|
||||
# jobs:
|
||||
# - build-windows:
|
||||
# name: "build-windows-vs2015"
|
||||
# vs_year: "2015"
|
||||
# cmake_generator: "Visual Studio 14 Win64"
|
||||
build-java:
|
||||
jobs:
|
||||
- build-linux-java
|
||||
@@ -733,11 +793,11 @@ workflows:
|
||||
build-linux-compilers-no_test_run:
|
||||
jobs:
|
||||
- build-linux-clang-no_test_run
|
||||
- build-linux-cmake-no_test_run
|
||||
- build-linux-gcc-4_8-no_test_run
|
||||
- build-linux-gcc-8-no_test_run
|
||||
- build-linux-gcc-9-no_test_run
|
||||
- build-linux-gcc-10-cxx20-no_test_run
|
||||
- build-linux-gcc-11-no_test_run
|
||||
- build-linux-arm-cmake-no_test_run
|
||||
build-macos:
|
||||
jobs:
|
||||
@@ -751,6 +811,9 @@ workflows:
|
||||
build-linux-arm:
|
||||
jobs:
|
||||
- build-linux-arm
|
||||
build-microbench:
|
||||
jobs:
|
||||
- build-linux-microbench
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
@@ -758,7 +821,7 @@ workflows:
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
- build-format-compatible
|
||||
- build-linux-arm-test-full
|
||||
@@ -39,3 +39,6 @@ jobs:
|
||||
|
||||
- name: Compare buckify output
|
||||
run: make check-buck-targets
|
||||
|
||||
- name: Simple source code checks
|
||||
run: make check-sources
|
||||
+51
@@ -5,6 +5,7 @@ os:
|
||||
arch:
|
||||
- arm64
|
||||
- ppc64le
|
||||
- s390x
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
@@ -59,6 +60,12 @@ matrix:
|
||||
- os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=make-gcc4.8
|
||||
- os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=make-gcc4.8
|
||||
- os: linux
|
||||
compiler: clang
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
@@ -73,6 +80,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -81,6 +92,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -89,6 +104,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -97,6 +116,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -109,6 +132,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/ AND commit_message !~ /java/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -117,6 +144,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -125,6 +156,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -133,6 +168,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -141,6 +180,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc9
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=cmake-gcc9
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -149,6 +192,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc9-c++20
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=cmake-gcc9-c++20
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -157,6 +204,10 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=status_checked
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: s390x
|
||||
env: JOB_NAME=status_checked
|
||||
|
||||
install:
|
||||
- if [ "${JOB_NAME}" == cmake-gcc8 ]; then
|
||||
|
||||
+57
-19
@@ -32,10 +32,11 @@
|
||||
# 3. cmake ..
|
||||
# 4. make -j
|
||||
|
||||
cmake_minimum_required(VERSION 3.5.1)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules/")
|
||||
include(ReadVersion)
|
||||
include(GoogleTest)
|
||||
get_rocksdb_version(rocksdb_VERSION)
|
||||
project(rocksdb
|
||||
VERSION ${rocksdb_VERSION}
|
||||
@@ -71,6 +72,12 @@ option(WITH_WINDOWS_UTF8_FILENAMES "use UTF8 as characterset for opening files,
|
||||
if (WITH_WINDOWS_UTF8_FILENAMES)
|
||||
add_definitions(-DROCKSDB_WINDOWS_UTF8_FILENAMES)
|
||||
endif()
|
||||
|
||||
if ($ENV{CIRCLECI})
|
||||
message(STATUS "Build for CircieCI env, a few tests may be disabled")
|
||||
add_definitions(-DCIRCLECI)
|
||||
endif()
|
||||
|
||||
# third-party/folly is only validated to work on Linux and Windows for now.
|
||||
# So only turn it on there by default.
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux|Windows")
|
||||
@@ -123,11 +130,11 @@ else()
|
||||
set(GFLAGS_LIB ${GFLAGS_TARGET})
|
||||
else()
|
||||
# Config with GFLAGS_LIBRARIES available since gflags 2.1.0
|
||||
set(GFLAGS_LIB ${GFLAGS_LIBRARIES})
|
||||
set(GFLAGS_LIB ${gflags_LIBRARIES})
|
||||
endif()
|
||||
else()
|
||||
find_package(gflags REQUIRED)
|
||||
set(GFLAGS_LIB gflags::gflags)
|
||||
set(GFLAGS_LIB gflags::gflags)
|
||||
endif()
|
||||
include_directories(${GFLAGS_INCLUDE_DIR})
|
||||
list(APPEND THIRDPARTY_LIBS ${GFLAGS_LIB})
|
||||
@@ -211,7 +218,7 @@ if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /W4 /wd4127 /wd4800 /wd4996 /wd4351 /wd4100 /wd4204 /wd4324")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W -Wextra -Wall -pthread")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare -Wshadow -Wno-unused-parameter -Wno-unused-variable -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers -Wno-strict-aliasing")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wstrict-prototypes")
|
||||
@@ -260,6 +267,13 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|AARCH64")
|
||||
endif(HAS_ARMV8_CRC)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|AARCH64")
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "s390x")
|
||||
CHECK_C_COMPILER_FLAG("-march=native" HAS_S390X_MARCH_NATIVE)
|
||||
if(HAS_S390X_MARCH_NATIVE)
|
||||
message(STATUS " HAS_S390X_MARCH_NATIVE yes")
|
||||
endif(HAS_S390X_MARCH_NATIVE)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "s390x")
|
||||
|
||||
option(PORTABLE "build a portable binary" OFF)
|
||||
option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
|
||||
option(FORCE_AVX "force building with AVX, even when PORTABLE=ON" OFF)
|
||||
@@ -285,12 +299,17 @@ if(PORTABLE)
|
||||
if(FORCE_AVX2)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mavx2 -mbmi -mlzcnt")
|
||||
endif()
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:AVX2")
|
||||
else()
|
||||
if(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64" AND NOT HAS_ARMV8_CRC)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^s390x" AND NOT HAS_S390X_MARCH_NATIVE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=z196")
|
||||
elseif(NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64" AND NOT HAS_ARMV8_CRC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=native")
|
||||
endif()
|
||||
endif()
|
||||
@@ -617,16 +636,19 @@ find_package(Threads REQUIRED)
|
||||
set(SOURCES
|
||||
cache/cache.cc
|
||||
cache/cache_entry_roles.cc
|
||||
cache/cache_reservation_manager.cc
|
||||
cache/clock_cache.cc
|
||||
cache/lru_cache.cc
|
||||
cache/sharded_cache.cc
|
||||
db/arena_wrapped_db_iter.cc
|
||||
db/blob/blob_fetcher.cc
|
||||
db/blob/blob_file_addition.cc
|
||||
db/blob/blob_file_builder.cc
|
||||
db/blob/blob_file_cache.cc
|
||||
db/blob/blob_file_garbage.cc
|
||||
db/blob/blob_file_meta.cc
|
||||
db/blob/blob_file_reader.cc
|
||||
db/blob/blob_garbage_meter.cc
|
||||
db/blob/blob_log_format.cc
|
||||
db/blob/blob_log_sequential_reader.cc
|
||||
db/blob/blob_log_writer.cc
|
||||
@@ -703,6 +725,7 @@ set(SOURCES
|
||||
env/file_system_tracer.cc
|
||||
env/fs_remap.cc
|
||||
env/mock_env.cc
|
||||
env/unique_id_gen.cc
|
||||
file/delete_scheduler.cc
|
||||
file/file_prefetch_buffer.cc
|
||||
file/file_util.cc
|
||||
@@ -796,6 +819,7 @@ set(SOURCES
|
||||
table/table_factory.cc
|
||||
table/table_properties.cc
|
||||
table/two_level_iterator.cc
|
||||
table/unique_id.cc
|
||||
test_util/sync_point.cc
|
||||
test_util/sync_point_impl.cc
|
||||
test_util/testutil.cc
|
||||
@@ -807,9 +831,12 @@ set(SOURCES
|
||||
tools/ldb_tool.cc
|
||||
tools/sst_dump_tool.cc
|
||||
tools/trace_analyzer_tool.cc
|
||||
trace_replay/trace_replay.cc
|
||||
trace_replay/block_cache_tracer.cc
|
||||
trace_replay/io_tracer.cc
|
||||
trace_replay/trace_record_handler.cc
|
||||
trace_replay/trace_record_result.cc
|
||||
trace_replay/trace_record.cc
|
||||
trace_replay/trace_replay.cc
|
||||
util/coding.cc
|
||||
util/compaction_job_stats_impl.cc
|
||||
util/comparator.cc
|
||||
@@ -822,6 +849,7 @@ set(SOURCES
|
||||
util/random.cc
|
||||
util/rate_limiter.cc
|
||||
util/ribbon_config.cc
|
||||
util/regex.cc
|
||||
util/slice.cc
|
||||
util/file_checksum_helper.cc
|
||||
util/status.cc
|
||||
@@ -836,10 +864,13 @@ set(SOURCES
|
||||
utilities/blob_db/blob_db_impl_filesnapshot.cc
|
||||
utilities/blob_db/blob_dump_tool.cc
|
||||
utilities/blob_db/blob_file.cc
|
||||
utilities/cache_dump_load.cc
|
||||
utilities/cache_dump_load_impl.cc
|
||||
utilities/cassandra/cassandra_compaction_filter.cc
|
||||
utilities/cassandra/format.cc
|
||||
utilities/cassandra/merge_operator.cc
|
||||
utilities/checkpoint/checkpoint_impl.cc
|
||||
utilities/compaction_filters.cc
|
||||
utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc
|
||||
utilities/debug.cc
|
||||
utilities/env_mirror.cc
|
||||
@@ -848,6 +879,7 @@ set(SOURCES
|
||||
utilities/fault_injection_fs.cc
|
||||
utilities/leveldb_options/leveldb_options.cc
|
||||
utilities/memory/memory_util.cc
|
||||
utilities/merge_operators.cc
|
||||
utilities/merge_operators/bytesxor.cc
|
||||
utilities/merge_operators/max.cc
|
||||
utilities/merge_operators/put.cc
|
||||
@@ -867,6 +899,7 @@ set(SOURCES
|
||||
utilities/simulator_cache/sim_cache.cc
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector.cc
|
||||
utilities/trace/file_trace_reader_writer.cc
|
||||
utilities/trace/replayer_impl.cc
|
||||
utilities/transactions/lock/lock_manager.cc
|
||||
utilities/transactions/lock/point/point_lock_tracker.cc
|
||||
utilities/transactions/lock/point/point_lock_manager.cc
|
||||
@@ -885,6 +918,7 @@ set(SOURCES
|
||||
utilities/transactions/write_unprepared_txn.cc
|
||||
utilities/transactions/write_unprepared_txn_db.cc
|
||||
utilities/ttl/db_ttl_impl.cc
|
||||
utilities/wal_filter.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_internal.cc)
|
||||
|
||||
@@ -925,13 +959,8 @@ if(WIN32)
|
||||
port/win/env_win.cc
|
||||
port/win/env_default.cc
|
||||
port/win/port_win.cc
|
||||
port/win/win_logger.cc)
|
||||
if(NOT MINGW)
|
||||
# Mingw only supports std::thread when using
|
||||
# posix threads.
|
||||
list(APPEND SOURCES
|
||||
port/win/win_thread.cc)
|
||||
endif()
|
||||
port/win/win_logger.cc
|
||||
port/win/win_thread.cc)
|
||||
if(WITH_XPRESS)
|
||||
list(APPEND SOURCES
|
||||
port/win/xpress_win.cc)
|
||||
@@ -1113,19 +1142,23 @@ if(WITH_TESTS)
|
||||
)
|
||||
if(WITH_ALL_TESTS)
|
||||
list(APPEND TESTS
|
||||
cache/cache_reservation_manager_test.cc
|
||||
cache/cache_test.cc
|
||||
cache/lru_cache_test.cc
|
||||
db/blob/blob_counting_iterator_test.cc
|
||||
db/blob/blob_file_addition_test.cc
|
||||
db/blob/blob_file_builder_test.cc
|
||||
db/blob/blob_file_cache_test.cc
|
||||
db/blob/blob_file_garbage_test.cc
|
||||
db/blob/blob_file_reader_test.cc
|
||||
db/blob/blob_garbage_meter_test.cc
|
||||
db/blob/db_blob_basic_test.cc
|
||||
db/blob/db_blob_compaction_test.cc
|
||||
db/blob/db_blob_corruption_test.cc
|
||||
db/blob/db_blob_index_test.cc
|
||||
db/column_family_test.cc
|
||||
db/compact_files_test.cc
|
||||
db/compaction/clipping_iterator_test.cc
|
||||
db/compaction/compaction_job_stats_test.cc
|
||||
db/compaction/compaction_job_test.cc
|
||||
db/compaction/compaction_iterator_test.cc
|
||||
@@ -1321,21 +1354,21 @@ if(WITH_TESTS)
|
||||
|
||||
foreach(sourcefile ${TESTS})
|
||||
get_filename_component(exename ${sourcefile} NAME_WE)
|
||||
add_executable(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} ${sourcefile})
|
||||
set_target_properties(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX}
|
||||
add_executable(${exename}${ARTIFACT_SUFFIX} ${sourcefile})
|
||||
set_target_properties(${exename}${ARTIFACT_SUFFIX}
|
||||
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_RELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_MINRELEASE 1
|
||||
EXCLUDE_FROM_DEFAULT_BUILD_RELWITHDEBINFO 1
|
||||
OUTPUT_NAME ${exename}${ARTIFACT_SUFFIX}
|
||||
)
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} testharness gtest ${THIRDPARTY_LIBS} ${ROCKSDB_LIB})
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} testutillib${ARTIFACT_SUFFIX} testharness gtest ${THIRDPARTY_LIBS} ${ROCKSDB_LIB})
|
||||
if(NOT "${exename}" MATCHES "db_sanity_test")
|
||||
add_test(NAME ${exename} COMMAND ${exename}${ARTIFACT_SUFFIX})
|
||||
add_dependencies(check ${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX})
|
||||
gtest_discover_tests(${exename} DISCOVERY_TIMEOUT 120)
|
||||
add_dependencies(check ${exename}${ARTIFACT_SUFFIX})
|
||||
endif()
|
||||
if("${exename}" MATCHES "env_librados_test")
|
||||
# env_librados_test.cc uses librados directly
|
||||
target_link_libraries(${CMAKE_PROJECT_NAME}_${exename}${ARTIFACT_SUFFIX} rados)
|
||||
target_link_libraries(${exename}${ARTIFACT_SUFFIX} rados)
|
||||
endif()
|
||||
endforeach(sourcefile ${TESTS})
|
||||
|
||||
@@ -1415,3 +1448,8 @@ option(WITH_EXAMPLES "build with examples" OFF)
|
||||
if(WITH_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
option(WITH_BENCHMARK "build benchmark tests" OFF)
|
||||
if(WITH_BENCHMARK)
|
||||
add_subdirectory(${PROJECT_SOURCE_DIR}/microbench/)
|
||||
endif()
|
||||
+196
@@ -1,5 +1,201 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
### New Features
|
||||
* Added new ChecksumType kXXH3 which is faster than kCRC32c on almost all x86\_64 hardware.
|
||||
* Added a new online consistency check for BlobDB which validates that the number/total size of garbage blobs does not exceed the number/total size of all blobs in any given blob file.
|
||||
|
||||
### Bug Fixes
|
||||
* Prevent a `CompactRange()` with `CompactRangeOptions::change_level == true` from possibly causing corruption to the LSM state (overlapping files within a level) when run in parallel with another manual compaction. Note that setting `force_consistency_checks == true` (the default) would cause the DB to enter read-only mode in this scenario and return `Status::Corruption`, rather than committing any corruption.
|
||||
* Fixed a bug in CompactionIterator when write-prepared transaction is used. A released earliest write conflict snapshot may cause assertion failure in dbg mode and unexpected key in opt mode.
|
||||
* Fix ticker WRITE_WITH_WAL("rocksdb.write.wal"), this bug is caused by a bad extra `RecordTick(stats_, WRITE_WITH_WAL)` (at 2 place), this fix remove the extra `RecordTick`s and fix the corresponding test case.
|
||||
* EventListener::OnTableFileCreated was previously called with OK status and file_size==0 in cases of no SST file contents written (because there was no content to add) and the empty file deleted before calling the listener. Now the status is Aborted.
|
||||
* Fixed a bug in CompactionIterator when write-preared transaction is used. Releasing earliest_snapshot during compaction may cause a SingleDelete to be output after a PUT of the same user key whose seq has been zeroed.
|
||||
* Added input sanitization on negative bytes passed into `GenericRateLimiter::Request`.
|
||||
|
||||
### Behavior Changes
|
||||
* `NUM_FILES_IN_SINGLE_COMPACTION` was only counting the first input level files, now it's including all input files.
|
||||
|
||||
### Public Interface Change
|
||||
* When options.ttl is used with leveled compaction with compactinon priority kMinOverlappingRatio, files exceeding half of TTL value will be prioritized more, so that by the time TTL is reached, fewer extra compactions will be scheduled to clear them up. At the same time, when compacting files with data older than half of TTL, output files may be cut off based on those files' boundaries, in order for the early TTL compaction to work properly.
|
||||
|
||||
### Public API change
|
||||
* Made FileSystem extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* Clarified in API comments that RocksDB is not exception safe for callbacks and custom extensions. An exception propagating into RocksDB can lead to undefined behavior, including data loss, unreported corruption, deadlocks, and more.
|
||||
* Marked `WriteBufferManager` as `final` because it is not intended for extension.
|
||||
* Add API `FSDirectory::FsyncWithDirOptions()`, which provides extra information like directory fsync reason in `DirFsyncOptions`. File system like btrfs is using that to skip directory fsync for creating a new file, or when renaming a file, fsync the target file instead of the directory, which improves the `DB::Open()` speed by ~20%.
|
||||
* `DB::Open()` is not going be blocked by obsolete file purge if `DBOptions::avoid_unnecessary_blocking_io` is set to true.
|
||||
|
||||
### Performance Improvements
|
||||
* Released some memory related to filter construction earlier in `BlockBasedTableBuilder` for `FullFilter` and `PartitionedFilter` case (#9070)
|
||||
|
||||
## 6.26.0 (2021-10-20)
|
||||
### Bug Fixes
|
||||
* Fixes a bug in directed IO mode when calling MultiGet() for blobs in the same blob file. The bug is caused by not sorting the blob read requests by file offsets.
|
||||
* Fix the incorrect disabling of SST rate limited deletion when the WAL and DB are in different directories. Only WAL rate limited deletion should be disabled if its in a different directory.
|
||||
* Fix `DisableManualCompaction()` to cancel compactions even when they are waiting on automatic compactions to drain due to `CompactRangeOptions::exclusive_manual_compactions == true`.
|
||||
* Fix contract of `Env::ReopenWritableFile()` and `FileSystem::ReopenWritableFile()` to specify any existing file must not be deleted or truncated.
|
||||
* Fixed bug in calls to `IngestExternalFiles()` with files for multiple column families. The bug could have introduced a delay in ingested file keys becoming visible after `IngestExternalFiles()` returned. Furthermore, mutations to ingested file keys while they were invisible could have been dropped (not necessarily immediately).
|
||||
* Fixed a possible race condition impacting users of `WriteBufferManager` who constructed it with `allow_stall == true`. The race condition led to undefined behavior (in our experience, typically a process crash).
|
||||
* Fixed a bug where stalled writes would remain stalled forever after the user calls `WriteBufferManager::SetBufferSize()` with `new_size == 0` to dynamically disable memory limiting.
|
||||
* Make `DB::close()` thread-safe.
|
||||
* Fix a bug in atomic flush where one bg flush thread will wait forever for a preceding bg flush thread to commit its result to MANIFEST but encounters an error which is mapped to a soft error (DB not stopped).
|
||||
|
||||
### New Features
|
||||
* Print information about blob files when using "ldb list_live_files_metadata"
|
||||
* Provided support for SingleDelete with user defined timestamp.
|
||||
* Experimental new function DB::GetLiveFilesStorageInfo offers essentially a unified version of other functions like GetLiveFiles, GetLiveFilesChecksumInfo, and GetSortedWalFiles. Checkpoints and backups could show small behavioral changes and/or improved performance as they now use this new API.
|
||||
* Add remote compaction read/write bytes statistics: `REMOTE_COMPACT_READ_BYTES`, `REMOTE_COMPACT_WRITE_BYTES`.
|
||||
* Introduce an experimental feature to dump out the blocks from block cache and insert them to the secondary cache to reduce the cache warmup time (e.g., used while migrating DB instance). More information are in `class CacheDumper` and `CacheDumpedLoader` at `rocksdb/utilities/cache_dump_load.h` Note that, this feature is subject to the potential change in the future, it is still experimental.
|
||||
* Introduced a new BlobDB configuration option `blob_garbage_collection_force_threshold`, which can be used to trigger compactions targeting the SST files which reference the oldest blob files when the ratio of garbage in those blob files meets or exceeds the specified threshold. This can reduce space amplification with skewed workloads where the affected SST files might not otherwise get picked up for compaction.
|
||||
* Added EXPERIMENTAL support for table file (SST) unique identifiers that are stable and universally unique, available with new function `GetUniqueIdFromTableProperties`. Only SST files from RocksDB >= 6.24 support unique IDs.
|
||||
* Added `GetMapProperty()` support for "rocksdb.dbstats" (`DB::Properties::kDBStats`). As a map property, it includes DB-level internal stats accumulated over the DB's lifetime, such as user write related stats and uptime.
|
||||
|
||||
### Public API change
|
||||
* Made SystemClock extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* Made SliceTransform extend the Customizable class and added a CreateFromString method. Implementations need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method. The Capped and Prefixed transform classes return a short name (no length); use GetId for the fully qualified name.
|
||||
* Made FileChecksumGenFactory, SstPartitionerFactory, TablePropertiesCollectorFactory, and WalFilter extend the Customizable class and added a CreateFromString method.
|
||||
* Some fields of SstFileMetaData are deprecated for compatibility with new base class FileStorageInfo.
|
||||
* Add `file_temperature` to `IngestExternalFileArg` such that when ingesting SST files, we are able to indicate the temperature of the this batch of files.
|
||||
* If `DB::Close()` failed with a non aborted status, calling `DB::Close()` again will return the original status instead of Status::OK.
|
||||
* Add CacheTier to advanced_options.h to describe the cache tier we used. Add a `lowest_used_cache_tier` option to `DBOptions` (immutable) and pass it to BlockBasedTableReader. By default it is `CacheTier::kNonVolatileBlockTier`, which means, we always use both block cache (kVolatileTier) and secondary cache (kNonVolatileBlockTier). By set it to `CacheTier::kVolatileTier`, the DB will not use the secondary cache.
|
||||
* Even when options.max_compaction_bytes is hit, compaction output files are only cut when it aligns with grandparent files' boundaries. options.max_compaction_bytes could be slightly violated with the change, but the violation is no more than one target SST file size, which is usually much smaller.
|
||||
|
||||
### Performance Improvements
|
||||
* Improved CPU efficiency of building block-based table (SST) files (#9039 and #9040).
|
||||
|
||||
### Java API Changes
|
||||
* Add Java API bindings for new integrated BlobDB options
|
||||
* `keyMayExist()` supports ByteBuffer.
|
||||
* Fix multiget throwing Null Pointer Exception for num of keys > 70k (https://github.com/facebook/rocksdb/issues/8039).
|
||||
|
||||
## 6.25.0 (2021-09-20)
|
||||
### Bug Fixes
|
||||
* Allow secondary instance to refresh iterator. Assign read seq after referencing SuperVersion.
|
||||
* Fixed a bug of secondary instance's last_sequence going backward, and reads on the secondary fail to see recent updates from the primary.
|
||||
* Fixed a bug that could lead to duplicate DB ID or DB session ID in POSIX environments without /proc/sys/kernel/random/uuid.
|
||||
* Fix a race in DumpStats() with column family destruction due to not taking a Ref on each entry while iterating the ColumnFamilySet.
|
||||
* Fix a race in item ref counting in LRUCache when promoting an item from the SecondaryCache.
|
||||
* Fix a race in BackupEngine if RateLimiter is reconfigured during concurrent Restore operations.
|
||||
* Fix a bug on POSIX in which failure to create a lock file (e.g. out of space) can prevent future LockFile attempts in the same process on the same file from succeeding.
|
||||
* Fix a bug that backup_rate_limiter and restore_rate_limiter in BackupEngine could not limit read rates.
|
||||
* Fix the implementation of `prepopulate_block_cache = kFlushOnly` to only apply to flushes rather than to all generated files.
|
||||
* Fix WAL log data corruption when using DBOptions.manual_wal_flush(true) and WriteOptions.sync(true) together. The sync WAL should work with locked log_write_mutex_.
|
||||
* Add checks for validity of the IO uring completion queue entries, and fail the BlockBasedTableReader MultiGet sub-batch if there's an invalid completion
|
||||
* Add an interface RocksDbIOUringEnable() that, if defined by the user, will allow them to enable/disable the use of IO uring by RocksDB
|
||||
* Fix the bug that when direct I/O is used and MultiRead() returns a short result, RandomAccessFileReader::MultiRead() still returns full size buffer, with returned short value together with some data in original buffer. This bug is unlikely cause incorrect results, because (1) since FileSystem layer is expected to retry on short result, returning short results is only possible when asking more bytes in the end of the file, which RocksDB doesn't do when using MultiRead(); (2) checksum is unlikely to match.
|
||||
|
||||
### New Features
|
||||
* RemoteCompaction's interface now includes `db_name`, `db_id`, `session_id`, which could help the user uniquely identify compaction job between db instances and sessions.
|
||||
* Added a ticker statistic, "rocksdb.verify_checksum.read.bytes", reporting how many bytes were read from file to serve `VerifyChecksum()` and `VerifyFileChecksums()` queries.
|
||||
* Added ticker statistics, "rocksdb.backup.read.bytes" and "rocksdb.backup.write.bytes", reporting how many bytes were read and written during backup.
|
||||
* Added properties for BlobDB: `rocksdb.num-blob-files`, `rocksdb.blob-stats`, `rocksdb.total-blob-file-size`, and `rocksdb.live-blob-file-size`. The existing property `rocksdb.estimate_live-data-size` was also extended to include live bytes residing in blob files.
|
||||
* Added two new RateLimiter IOPriorities: `Env::IO_USER`,`Env::IO_MID`. `Env::IO_USER` will have superior priority over all other RateLimiter IOPriorities without being subject to fair scheduling constraint.
|
||||
* `SstFileWriter` now supports `Put`s and `Delete`s with user-defined timestamps. Note that the ingestion logic itself is not timestamp-aware yet.
|
||||
* Allow a single write batch to include keys from multiple column families whose timestamps' formats can differ. For example, some column families may disable timestamp, while others enable timestamp.
|
||||
* Add compaction priority information in RemoteCompaction, which can be used to schedule high priority job first.
|
||||
* Added new callback APIs `OnBlobFileCreationStarted`,`OnBlobFileCreated`and `OnBlobFileDeleted` in `EventListener` class of listener.h. It notifies listeners during creation/deletion of individual blob files in Integrated BlobDB. It also log blob file creation finished event and deletion event in LOG file.
|
||||
* Batch blob read requests for `DB::MultiGet` using `MultiRead`.
|
||||
* Add support for fallback to local compaction, the user can return `CompactionServiceJobStatus::kUseLocal` to instruct RocksDB to run the compaction locally instead of waiting for the remote compaction result.
|
||||
* Add built-in rate limiter's implementation of `RateLimiter::GetTotalPendingRequest(int64_t* total_pending_requests, const Env::IOPriority pri)` for the total number of requests that are pending for bytes in the rate limiter.
|
||||
* Charge memory usage during data buffering, from which training samples are gathered for dictionary compression, to block cache. Unbuffering data can now be triggered if the block cache becomes full and `strict_capacity_limit=true` for the block cache, in addition to existing conditions that can trigger unbuffering.
|
||||
|
||||
### Public API change
|
||||
* Remove obsolete implementation details FullKey and ParseFullKey from public API
|
||||
* Change `SstFileMetaData::size` from `size_t` to `uint64_t`.
|
||||
* Made Statistics extend the Customizable class and added a CreateFromString method. Implementations of Statistics need to be registered with the ObjectRegistry and to implement a Name() method in order to be created via this method.
|
||||
* Extended `FlushJobInfo` and `CompactionJobInfo` in listener.h to provide information about the blob files generated by a flush/compaction and garbage collected during compaction in Integrated BlobDB. Added struct members `blob_file_addition_infos` and `blob_file_garbage_infos` that contain this information.
|
||||
* Extended parameter `output_file_names` of `CompactFiles` API to also include paths of the blob files generated by the compaction in Integrated BlobDB.
|
||||
* Most `BackupEngine` functions now return `IOStatus` instead of `Status`. Most existing code should be compatible with this change but some calls might need to be updated.
|
||||
* Add a new field `level_at_creation` in `TablePropertiesCollectorFactory::Context` to capture the level at creating the SST file (i.e, table), of which the properties are being collected.
|
||||
|
||||
### Miscellaneous
|
||||
* Add a paranoid check where in case FileSystem layer doesn't fill the buffer but returns succeed, checksum is unlikely to match even if buffer contains a previous block. The byte modified is not useful anyway, so it isn't expected to change any behavior when FileSystem is satisfying its contract.
|
||||
|
||||
## 6.24.0 (2021-08-20)
|
||||
### Bug Fixes
|
||||
* If the primary's CURRENT file is missing or inaccessible, the secondary instance should not hang repeatedly trying to switch to a new MANIFEST. It should instead return the error code encountered while accessing the file.
|
||||
* Restoring backups with BackupEngine is now a logically atomic operation, so that if a restore operation is interrupted, DB::Open on it will fail. Using BackupEngineOptions::sync (default) ensures atomicity even in case of power loss or OS crash.
|
||||
* Fixed a race related to the destruction of `ColumnFamilyData` objects. The earlier logic unlocked the DB mutex before destroying the thread-local `SuperVersion` pointers, which could result in a process crash if another thread managed to get a reference to the `ColumnFamilyData` object.
|
||||
* Removed a call to `RenameFile()` on a non-existent info log file ("LOG") when opening a new DB. Such a call was guaranteed to fail though did not impact applications since we swallowed the error. Now we also stopped swallowing errors in renaming "LOG" file.
|
||||
* Fixed an issue where `OnFlushCompleted` was not called for atomic flush.
|
||||
* Fixed a bug affecting the batched `MultiGet` API when used with keys spanning multiple column families and `sorted_input == false`.
|
||||
* Fixed a potential incorrect result in opt mode and assertion failures caused by releasing snapshot(s) during compaction.
|
||||
* Fixed passing of BlobFileCompletionCallback to Compaction job and Atomic flush job which was default paramter (nullptr). BlobFileCompletitionCallback is internal callback that manages addition of blob files to SSTFileManager.
|
||||
* Fixed MultiGet not updating the block_read_count and block_read_byte PerfContext counters.
|
||||
|
||||
### New Features
|
||||
* Made the EventListener extend the Customizable class.
|
||||
* EventListeners that have a non-empty Name() and that are registered with the ObjectRegistry can now be serialized to/from the OPTIONS file.
|
||||
* Insert warm blocks (data blocks, uncompressed dict blocks, index and filter blocks) in Block cache during flush under option BlockBasedTableOptions.prepopulate_block_cache. Previously it was enabled for only data blocks.
|
||||
* BlockBasedTableOptions.prepopulate_block_cache can be dynamically configured using DB::SetOptions.
|
||||
* Add CompactionOptionsFIFO.age_for_warm, which allows RocksDB to move old files to warm tier in FIFO compactions. Note that file temperature is still an experimental feature.
|
||||
* Add a comment to suggest btrfs user to disable file preallocation by setting `options.allow_fallocate=false`.
|
||||
* Fast forward option in Trace replay changed to double type to allow replaying at a lower speed, by settings the value between 0 and 1. This option can be set via `ReplayOptions` in `Replayer::Replay()`, or via `--trace_replay_fast_forward` in db_bench.
|
||||
* Add property `LiveSstFilesSizeAtTemperature` to retrieve sst file size at different temperature.
|
||||
* Added a stat rocksdb.secondary.cache.hits.
|
||||
* Added a PerfContext counter secondary_cache_hit_count.
|
||||
* The integrated BlobDB implementation now supports the tickers `BLOB_DB_BLOB_FILE_BYTES_READ`, `BLOB_DB_GC_NUM_KEYS_RELOCATED`, and `BLOB_DB_GC_BYTES_RELOCATED`, as well as the histograms `BLOB_DB_COMPRESSION_MICROS` and `BLOB_DB_DECOMPRESSION_MICROS`.
|
||||
* Added hybrid configuration of Ribbon filter and Bloom filter where some LSM levels use Ribbon for memory space efficiency and some use Bloom for speed. See NewRibbonFilterPolicy. This also changes the default behavior of NewRibbonFilterPolicy to use Bloom for flushes under Leveled and Universal compaction and Ribbon otherwise. The C API function `rocksdb_filterpolicy_create_ribbon` is unchanged but adds new `rocksdb_filterpolicy_create_ribbon_hybrid`.
|
||||
|
||||
### Public API change
|
||||
* Added APIs to decode and replay trace file via Replayer class. Added `DB::NewDefaultReplayer()` to create a default Replayer instance. Added `TraceReader::Reset()` to restart reading a trace file. Created trace_record.h, trace_record_result.h and utilities/replayer.h files to access the decoded Trace records, replay them, and query the actual operation results.
|
||||
* Added Configurable::GetOptionsMap to the public API for use in creating new Customizable classes.
|
||||
* Generalized bits_per_key parameters in C API from int to double for greater configurability. Although this is a compatible change for existing C source code, anything depending on C API signatures, such as foreign function interfaces, will need to be updated.
|
||||
|
||||
### Performance Improvements
|
||||
* Try to avoid updating DBOptions if `SetDBOptions()` does not change any option value.
|
||||
|
||||
### Behavior Changes
|
||||
* `StringAppendOperator` additionally accepts a string as the delimiter.
|
||||
* BackupEngineOptions::sync (default true) now applies to restoring backups in addition to creating backups. This could slow down restores, but ensures they are fully persisted before returning OK. (Consider increasing max_background_operations to improve performance.)
|
||||
|
||||
## 6.23.0 (2021-07-16)
|
||||
### Behavior Changes
|
||||
* Obsolete keys in the bottommost level that were preserved for a snapshot will now be cleaned upon snapshot release in all cases. This form of compaction (snapshot release triggered compaction) previously had an artificial limitation that multiple tombstones needed to be present.
|
||||
### Bug Fixes
|
||||
* Blob file checksums are now printed in hexadecimal format when using the `manifest_dump` `ldb` command.
|
||||
* `GetLiveFilesMetaData()` now populates the `temperature`, `oldest_ancester_time`, and `file_creation_time` fields of its `LiveFileMetaData` results when the information is available. Previously these fields always contained zero indicating unknown.
|
||||
* Fix mismatches of OnCompaction{Begin,Completed} in case of DisableManualCompaction().
|
||||
* Fix continuous logging of an existing background error on every user write
|
||||
* Fix a bug that `Get()` return Status::OK() and an empty value for non-existent key when `read_options.read_tier = kBlockCacheTier`.
|
||||
* Fix a bug that stat in `get_context` didn't accumulate to statistics when query is failed.
|
||||
* Fixed handling of DBOptions::wal_dir with LoadLatestOptions() or ldb --try_load_options on a copied or moved DB. Previously, when the WAL directory is same as DB directory (default), a copied or moved DB would reference the old path of the DB as the WAL directory, potentially corrupting both copies. Under this change, the wal_dir from DB::GetOptions() or LoadLatestOptions() may now be empty, indicating that the current DB directory is used for WALs. This is also a subtle API change.
|
||||
|
||||
### New Features
|
||||
* ldb has a new feature, `list_live_files_metadata`, that shows the live SST files, as well as their LSM storage level and the column family they belong to.
|
||||
* The new BlobDB implementation now tracks the amount of garbage in each blob file in the MANIFEST.
|
||||
* Integrated BlobDB now supports Merge with base values (Put/Delete etc.).
|
||||
* RemoteCompaction supports sub-compaction, the job_id in the user interface is changed from `int` to `uint64_t` to support sub-compaction id.
|
||||
* Expose statistics option in RemoteCompaction worker.
|
||||
|
||||
### Public API change
|
||||
* Added APIs to the Customizable class to allow developers to create their own Customizable classes. Created the utilities/customizable_util.h file to contain helper methods for developing new Customizable classes.
|
||||
* Change signature of SecondaryCache::Name(). Make SecondaryCache customizable and add SecondaryCache::CreateFromString method.
|
||||
|
||||
## 6.22.0 (2021-06-18)
|
||||
### Behavior Changes
|
||||
* Added two additional tickers, MEMTABLE_PAYLOAD_BYTES_AT_FLUSH and MEMTABLE_GARBAGE_BYTES_AT_FLUSH. These stats can be used to estimate the ratio of "garbage" (outdated) bytes in the memtable that are discarded at flush time.
|
||||
* Added API comments clarifying safe usage of Disable/EnableManualCompaction and EventListener callbacks for compaction.
|
||||
### Bug Fixes
|
||||
* fs_posix.cc GetFreeSpace() always report disk space available to root even when running as non-root. Linux defaults often have disk mounts with 5 to 10 percent of total space reserved only for root. Out of space could result for non-root users.
|
||||
* Subcompactions are now disabled when user-defined timestamps are used, since the subcompaction boundary picking logic is currently not timestamp-aware, which could lead to incorrect results when different subcompactions process keys that only differ by timestamp.
|
||||
* Fix an issue that `DeleteFilesInRange()` may cause ongoing compaction reports corruption exception, or ASSERT for debug build. There's no actual data loss or corruption that we find.
|
||||
* Fixed confusingly duplicated output in LOG for periodic stats ("DUMPING STATS"), including "Compaction Stats" and "File Read Latency Histogram By Level".
|
||||
* Fixed performance bugs in background gathering of block cache entry statistics, that could consume a lot of CPU when there are many column families with a shared block cache.
|
||||
|
||||
### New Features
|
||||
* Marked the Ribbon filter and optimize_filters_for_memory features as production-ready, each enabling memory savings for Bloom-like filters. Use `NewRibbonFilterPolicy` in place of `NewBloomFilterPolicy` to use Ribbon filters instead of Bloom, or `ribbonfilter` in place of `bloomfilter` in configuration string.
|
||||
* Allow `DBWithTTL` to use `DeleteRange` api just like other DBs. `DeleteRangeCF()` which executes `WriteBatchInternal::DeleteRange()` has been added to the handler in `DBWithTTLImpl::Write()` to implement it.
|
||||
* Add BlockBasedTableOptions.prepopulate_block_cache. If enabled, it prepopulate warm/hot data blocks which are already in memory into block cache at the time of flush. On a flush, the data block that is in memory (in memtables) get flushed to the device. If using Direct IO, additional IO is incurred to read this data back into memory again, which is avoided by enabling this option and it also helps with Distributed FileSystem. More details in include/rocksdb/table.h.
|
||||
* Added a `cancel` field to `CompactRangeOptions`, allowing individual in-process manual range compactions to be cancelled.
|
||||
|
||||
### New Features
|
||||
* Added BlobMetaData to the ColumnFamilyMetaData to return information about blob files
|
||||
|
||||
### Public API change
|
||||
* Added GetAllColumnFamilyMetaData API to retrieve the ColumnFamilyMetaData about all column families.
|
||||
|
||||
## 6.21.0 (2021-05-21)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in handling file rename error in distributed/network file systems when the server succeeds but client returns error. The bug can cause CURRENT file to point to non-existing MANIFEST file, thus DB cannot be opened.
|
||||
* Fixed a bug where ingested files were written with incorrect boundary key metadata. In rare cases this could have led to a level's files being wrongly ordered and queries for the boundary keys returning wrong results.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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
|
||||
* Java - https://github.com/facebook/rocksdb/tree/main/java
|
||||
* Python
|
||||
* http://python-rocksdb.readthedocs.io/en/latest/
|
||||
* http://pyrocksdb.readthedocs.org/en/latest/ (unmaintained)
|
||||
|
||||
@@ -222,6 +222,7 @@ am__v_AR_1 =
|
||||
|
||||
ifdef ROCKSDB_USE_LIBRADOS
|
||||
LIB_SOURCES += utilities/env_librados.cc
|
||||
TEST_MAIN_SOURCES += utilities/env_librados_test.cc
|
||||
LDFLAGS += -lrados
|
||||
endif
|
||||
|
||||
@@ -344,6 +345,12 @@ ifdef ROCKSDB_VALGRIND_RUN
|
||||
PLATFORM_CCFLAGS += -DROCKSDB_VALGRIND_RUN
|
||||
PLATFORM_CXXFLAGS += -DROCKSDB_VALGRIND_RUN
|
||||
endif
|
||||
ifdef ROCKSDB_FULL_VALGRIND_RUN
|
||||
# Some tests are slow when run under valgrind and are only run when
|
||||
# explicitly requested via the ROCKSDB_FULL_VALGRIND_RUN compiler flag.
|
||||
PLATFORM_CCFLAGS += -DROCKSDB_VALGRIND_RUN -DROCKSDB_FULL_VALGRIND_RUN
|
||||
PLATFORM_CXXFLAGS += -DROCKSDB_VALGRIND_RUN -DROCKSDB_FULL_VALGRIND_RUN
|
||||
endif
|
||||
|
||||
ifndef DISABLE_JEMALLOC
|
||||
ifdef JEMALLOC
|
||||
@@ -488,6 +495,7 @@ VALGRIND_ERROR = 2
|
||||
VALGRIND_VER := $(join $(VALGRIND_VER),valgrind)
|
||||
|
||||
VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
# Not yet supported: --show-leak-kinds=definite,possible,reachable --errors-for-leak-kinds=definite,possible,reachable
|
||||
|
||||
TEST_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES)) $(GTEST)
|
||||
BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(BENCH_LIB_SOURCES))
|
||||
@@ -509,6 +517,29 @@ ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
ALL_SOURCES += third-party/folly/folly/synchronization/test/DistributedMutexTest.cc
|
||||
endif
|
||||
|
||||
# `make check-headers` to very that each header file includes its own
|
||||
# dependencies
|
||||
ifneq ($(filter check-headers, $(MAKECMDGOALS)),)
|
||||
# TODO: add/support JNI headers
|
||||
DEV_HEADER_DIRS := $(sort include/ hdfs/ $(dir $(ALL_SOURCES)))
|
||||
# Some headers like in port/ are platform-specific
|
||||
DEV_HEADERS := $(shell $(FIND) $(DEV_HEADER_DIRS) -type f -name '*.h' | egrep -v 'port/|plugin/|lua/|range_tree/|tools/rdb/db_wrapper.h|include/rocksdb/utilities/env_librados.h')
|
||||
else
|
||||
DEV_HEADERS :=
|
||||
endif
|
||||
HEADER_OK_FILES = $(patsubst %.h, %.h.ok, $(DEV_HEADERS))
|
||||
|
||||
AM_V_CCH = $(am__v_CCH_$(V))
|
||||
am__v_CCH_ = $(am__v_CCH_$(AM_DEFAULT_VERBOSITY))
|
||||
am__v_CCH_0 = @echo " CC.h " $<;
|
||||
am__v_CCH_1 =
|
||||
|
||||
%.h.ok: %.h # .h.ok not actually created, so re-checked on each invocation
|
||||
# -DROCKSDB_NAMESPACE=42 ensures the namespace header is included
|
||||
$(AM_V_CCH) echo '#include "$<"' | $(CXX) $(CXXFLAGS) -DROCKSDB_NAMESPACE=42 -x c++ -c - -o /dev/null
|
||||
|
||||
check-headers: $(HEADER_OK_FILES)
|
||||
|
||||
# options_settable_test doesn't pass with UBSAN as we use hack in the test
|
||||
ifdef COMPILE_WITH_UBSAN
|
||||
TESTS := $(shell echo $(TESTS) | sed 's/\boptions_settable_test\b//g')
|
||||
@@ -516,8 +547,9 @@ endif
|
||||
ifdef ASSERT_STATUS_CHECKED
|
||||
# TODO: finish fixing all tests to pass this check
|
||||
TESTS_FAILING_ASC = \
|
||||
c_test \
|
||||
db_test \
|
||||
db_test2 \
|
||||
env_test \
|
||||
range_locking_test \
|
||||
testutil_test \
|
||||
|
||||
@@ -593,6 +625,8 @@ TEST_LIBS = \
|
||||
# TODO: add back forward_iterator_bench, after making it build in all environemnts.
|
||||
BENCHMARKS = $(patsubst %.cc, %, $(notdir $(BENCH_MAIN_SOURCES)))
|
||||
|
||||
MICROBENCHS = $(patsubst %.cc, %, $(notdir $(MICROBENCH_SOURCES)))
|
||||
|
||||
# if user didn't config LIBNAME, set the default
|
||||
ifeq ($(LIBNAME),)
|
||||
LIBNAME=librocksdb
|
||||
@@ -706,7 +740,7 @@ endif # PLATFORM_SHARED_EXT
|
||||
.PHONY: blackbox_crash_test check clean coverage crash_test ldb_tests package \
|
||||
release tags tags0 valgrind_check whitebox_crash_test format static_lib shared_lib all \
|
||||
dbg rocksdbjavastatic rocksdbjava gen-pc install install-static install-shared uninstall \
|
||||
analyze tools tools_lib \
|
||||
analyze tools tools_lib check-headers \
|
||||
blackbox_crash_test_with_atomic_flush whitebox_crash_test_with_atomic_flush \
|
||||
blackbox_crash_test_with_txn whitebox_crash_test_with_txn \
|
||||
blackbox_crash_test_with_best_efforts_recovery \
|
||||
@@ -731,6 +765,9 @@ test_libs: $(TEST_LIBS)
|
||||
|
||||
benchmarks: $(BENCHMARKS)
|
||||
|
||||
microbench: $(MICROBENCHS)
|
||||
for t in $(MICROBENCHS); do echo "===== Running benchmark $$t (`date`)"; ./$$t || exit 1; done;
|
||||
|
||||
dbg: $(LIBRARY) $(BENCHMARKS) tools $(TESTS)
|
||||
|
||||
# creates library and programs
|
||||
@@ -930,6 +967,7 @@ endif
|
||||
ifndef SKIP_FORMAT_BUCK_CHECKS
|
||||
$(MAKE) check-format
|
||||
$(MAKE) check-buck-targets
|
||||
$(MAKE) check-sources
|
||||
endif
|
||||
|
||||
# TODO add ldb_tests
|
||||
@@ -1044,6 +1082,12 @@ ubsan_crash_test_with_best_efforts_recovery: clean
|
||||
COMPILE_WITH_UBSAN=1 $(MAKE) crash_test_with_best_efforts_recovery
|
||||
$(MAKE) clean
|
||||
|
||||
full_valgrind_test:
|
||||
ROCKSDB_FULL_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
|
||||
full_valgrind_test_some:
|
||||
ROCKSDB_FULL_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check_some
|
||||
|
||||
valgrind_test:
|
||||
ROCKSDB_VALGRIND_RUN=1 DISABLE_JEMALLOC=1 $(MAKE) valgrind_check
|
||||
|
||||
@@ -1162,7 +1206,7 @@ clean-not-downloaded: clean-ext-libraries-bin clean-rocks clean-not-downloaded-r
|
||||
clean-rocks:
|
||||
echo shared=$(ALL_SHARED_LIBS)
|
||||
echo static=$(ALL_STATIC_LIBS)
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(ALL_STATIC_LIBS) $(ALL_SHARED_LIBS)
|
||||
rm -f $(BENCHMARKS) $(TOOLS) $(TESTS) $(PARALLEL_TEST) $(ALL_STATIC_LIBS) $(ALL_SHARED_LIBS) $(MICROBENCHS)
|
||||
rm -rf $(CLEAN_FILES) ios-x86 ios-arm scan_build_report
|
||||
$(FIND) . -name "*.[oda]" -exec rm -f {} \;
|
||||
$(FIND) . -type f -regex ".*\.\(\(gcda\)\|\(gcno\)\)" -exec rm -f {} \;
|
||||
@@ -1201,6 +1245,9 @@ check-format:
|
||||
check-buck-targets:
|
||||
buckifier/check_buck_targets.sh
|
||||
|
||||
check-sources:
|
||||
build_tools/check-sources.sh
|
||||
|
||||
package:
|
||||
bash build_tools/make_package.sh $(SHARED_MAJOR).$(SHARED_MINOR)
|
||||
|
||||
@@ -1825,6 +1872,9 @@ block_cache_trace_analyzer_test: $(OBJ_DIR)/tools/block_cache_analyzer/block_cac
|
||||
defer_test: $(OBJ_DIR)/util/defer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_counting_iterator_test: $(OBJ_DIR)/db/blob/blob_counting_iterator_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_file_addition_test: $(OBJ_DIR)/db/blob/blob_file_addition_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1840,6 +1890,9 @@ blob_file_garbage_test: $(OBJ_DIR)/db/blob/blob_file_garbage_test.o $(TEST_LIBRA
|
||||
blob_file_reader_test: $(OBJ_DIR)/db/blob/blob_file_reader_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_garbage_meter_test: $(OBJ_DIR)/db/blob/blob_garbage_meter_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
timer_test: $(OBJ_DIR)/util/timer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1866,6 +1919,18 @@ db_blob_corruption_test: $(OBJ_DIR)/db/blob/db_blob_corruption_test.o $(TEST_LIB
|
||||
|
||||
db_write_buffer_manager_test: $(OBJ_DIR)/db/db_write_buffer_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
clipping_iterator_test: $(OBJ_DIR)/db/compaction/clipping_iterator_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
ribbon_bench: $(OBJ_DIR)/microbench/ribbon_bench.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_basic_bench: $(OBJ_DIR)/microbench/db_basic_bench.o $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
cache_reservation_manager_test: $(OBJ_DIR)/cache/cache_reservation_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
@@ -1936,7 +2001,7 @@ JAVA_INCLUDE = -I$(JAVA_HOME)/include/ -I$(JAVA_HOME)/include/linux
|
||||
ifeq ($(PLATFORM), OS_SOLARIS)
|
||||
ARCH := $(shell isainfo -b)
|
||||
else ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
ifneq (,$(filter amd64 ppc64 ppc64le arm64 aarch64 sparc64, $(MACHINE)))
|
||||
ifneq (,$(filter amd64 ppc64 ppc64le s390x arm64 aarch64 sparc64, $(MACHINE)))
|
||||
ARCH := 64
|
||||
else
|
||||
ARCH := 32
|
||||
@@ -1956,7 +2021,7 @@ ifneq ($(origin JNI_LIBC), undefined)
|
||||
JNI_LIBC_POSTFIX = -$(JNI_LIBC)
|
||||
endif
|
||||
|
||||
ifneq (,$(filter ppc% arm64 aarch64 sparc64, $(MACHINE)))
|
||||
ifneq (,$(filter ppc% s390x arm64 aarch64 sparc64, $(MACHINE)))
|
||||
ROCKSDBJNILIB = librocksdbjni-linux-$(MACHINE)$(JNI_LIBC_POSTFIX).so
|
||||
else
|
||||
ROCKSDBJNILIB = librocksdbjni-linux$(ARCH)$(JNI_LIBC_POSTFIX).so
|
||||
@@ -1973,7 +2038,7 @@ ZLIB_SHA256 ?= c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
|
||||
ZLIB_DOWNLOAD_BASE ?= http://zlib.net
|
||||
BZIP2_VER ?= 1.0.8
|
||||
BZIP2_SHA256 ?= ab5a03176ee106d3f0fa90e381da478ddae405918153cca248e682cd0c4a2269
|
||||
BZIP2_DOWNLOAD_BASE ?= https://sourceware.org/pub/bzip2
|
||||
BZIP2_DOWNLOAD_BASE ?= http://sourceware.org/pub/bzip2
|
||||
SNAPPY_VER ?= 1.1.8
|
||||
SNAPPY_SHA256 ?= 16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
@@ -2330,7 +2395,7 @@ build_subset_tests: $(ROCKSDBTESTS_SUBSET)
|
||||
|
||||
# Remove the rules for which dependencies should not be generated and see if any are left.
|
||||
#If so, include the dependencies; if not, do not include the dependency files
|
||||
ROCKS_DEP_RULES=$(filter-out clean format check-format check-buck-targets jclean jtest package analyze tags rocksdbjavastatic% unity.% unity_test, $(MAKECMDGOALS))
|
||||
ROCKS_DEP_RULES=$(filter-out clean format check-format check-buck-targets check-headers check-sources jclean jtest package analyze tags rocksdbjavastatic% unity.% unity_test, $(MAKECMDGOALS))
|
||||
ifneq ("$(ROCKS_DEP_RULES)", "")
|
||||
-include $(DEPFILES)
|
||||
endif
|
||||
@@ -1,8 +1,8 @@
|
||||
## RocksDB: A Persistent Key-Value Store for Flash and RAM Storage
|
||||
|
||||
[](https://circleci.com/gh/facebook/rocksdb)
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](https://travis-ci.com/github/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/main)
|
||||
[](http://140-211-168-68-openstack.osuosl.org:8080/job/rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
@@ -17,7 +17,7 @@ and Space-Amplification-Factor (SAF). It has multi-threaded compactions,
|
||||
making it especially suitable for storing multiple terabytes of data in a
|
||||
single database.
|
||||
|
||||
Start with example usage here: https://github.com/facebook/rocksdb/tree/master/examples
|
||||
Start with example usage here: https://github.com/facebook/rocksdb/tree/main/examples
|
||||
|
||||
See the [github wiki](https://github.com/facebook/rocksdb/wiki) for more explanation.
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ load(":defs.bzl", "test_binary")
|
||||
|
||||
REPO_PATH = package_name() + "/"
|
||||
|
||||
ROCKSDB_COMPILER_FLAGS = [
|
||||
ROCKSDB_COMPILER_FLAGS_0 = [
|
||||
"-fno-builtin-memcmp",
|
||||
# Needed to compile in fbcode
|
||||
"-Wno-expansion-to-defined",
|
||||
@@ -28,10 +28,14 @@ ROCKSDB_EXTERNAL_DEPS = [
|
||||
("zstd", None, "zstd"),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_DEPS = [
|
||||
ROCKSDB_OS_DEPS_0 = [
|
||||
(
|
||||
"linux",
|
||||
["third-party//numa:numa", "third-party//liburing:uring", "third-party//tbb:tbb"],
|
||||
[
|
||||
"third-party//numa:numa",
|
||||
"third-party//liburing:uring",
|
||||
"third-party//tbb:tbb",
|
||||
],
|
||||
),
|
||||
(
|
||||
"macos",
|
||||
@@ -39,7 +43,7 @@ ROCKSDB_OS_DEPS = [
|
||||
),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS = [
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS_0 = [
|
||||
(
|
||||
"linux",
|
||||
[
|
||||
@@ -69,7 +73,13 @@ ROCKSDB_OS_PREPROCESSOR_FLAGS = [
|
||||
),
|
||||
(
|
||||
"windows",
|
||||
[ "-DOS_WIN", "-DWIN32", "-D_MBCS", "-DWIN64", "-DNOMINMAX" ]
|
||||
[
|
||||
"-DOS_WIN",
|
||||
"-DWIN32",
|
||||
"-D_MBCS",
|
||||
"-DWIN64",
|
||||
"-DNOMINMAX",
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
@@ -107,18 +117,18 @@ is_opt_mode = build_mode.startswith("opt")
|
||||
|
||||
# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice
|
||||
# doesn't harm and avoid forgetting to add it.
|
||||
ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else [])
|
||||
ROCKSDB_COMPILER_FLAGS = ROCKSDB_COMPILER_FLAGS_0 + (["-DNDEBUG"] if is_opt_mode else [])
|
||||
|
||||
sanitizer = read_config("fbcode", "sanitizer")
|
||||
|
||||
# Do not enable jemalloc if sanitizer presents. RocksDB will further detect
|
||||
# whether the binary is linked with jemalloc at runtime.
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS += ([(
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS = ROCKSDB_OS_PREPROCESSOR_FLAGS_0 + ([(
|
||||
"linux",
|
||||
["-DROCKSDB_JEMALLOC"],
|
||||
)] if sanitizer == "" else [])
|
||||
|
||||
ROCKSDB_OS_DEPS += ([(
|
||||
ROCKSDB_OS_DEPS = ROCKSDB_OS_DEPS_0 + ([(
|
||||
"linux",
|
||||
["third-party//jemalloc:headers"],
|
||||
)] if sanitizer == "" else [])
|
||||
@@ -133,16 +143,19 @@ cpp_library(
|
||||
srcs = [
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_fetcher.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_builder.cc",
|
||||
"db/blob/blob_file_cache.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_file_reader.cc",
|
||||
"db/blob/blob_garbage_meter.cc",
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_sequential_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
@@ -222,6 +235,7 @@ cpp_library(
|
||||
"env/fs_remap.cc",
|
||||
"env/io_posix.cc",
|
||||
"env/mock_env.cc",
|
||||
"env/unique_id_gen.cc",
|
||||
"file/delete_scheduler.cc",
|
||||
"file/file_prefetch_buffer.cc",
|
||||
"file/file_util.cc",
|
||||
@@ -323,6 +337,7 @@ cpp_library(
|
||||
"table/table_factory.cc",
|
||||
"table/table_properties.cc",
|
||||
"table/two_level_iterator.cc",
|
||||
"table/unique_id.cc",
|
||||
"test_util/sync_point.cc",
|
||||
"test_util/sync_point_impl.cc",
|
||||
"test_util/transaction_test_util.cc",
|
||||
@@ -333,6 +348,9 @@ cpp_library(
|
||||
"tools/sst_dump_tool.cc",
|
||||
"trace_replay/block_cache_tracer.cc",
|
||||
"trace_replay/io_tracer.cc",
|
||||
"trace_replay/trace_record.cc",
|
||||
"trace_replay/trace_record_handler.cc",
|
||||
"trace_replay/trace_record_result.cc",
|
||||
"trace_replay/trace_replay.cc",
|
||||
"util/build_version.cc",
|
||||
"util/coding.cc",
|
||||
@@ -348,6 +366,7 @@ cpp_library(
|
||||
"util/murmurhash.cc",
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
"util/regex.cc",
|
||||
"util/ribbon_config.cc",
|
||||
"util/slice.cc",
|
||||
"util/status.cc",
|
||||
@@ -362,10 +381,13 @@ cpp_library(
|
||||
"utilities/blob_db/blob_db_impl_filesnapshot.cc",
|
||||
"utilities/blob_db/blob_dump_tool.cc",
|
||||
"utilities/blob_db/blob_file.cc",
|
||||
"utilities/cache_dump_load.cc",
|
||||
"utilities/cache_dump_load_impl.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
"utilities/checkpoint/checkpoint_impl.cc",
|
||||
"utilities/compaction_filters.cc",
|
||||
"utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc",
|
||||
"utilities/convenience/info_log_finder.cc",
|
||||
"utilities/debug.cc",
|
||||
@@ -375,6 +397,7 @@ cpp_library(
|
||||
"utilities/fault_injection_fs.cc",
|
||||
"utilities/leveldb_options/leveldb_options.cc",
|
||||
"utilities/memory/memory_util.cc",
|
||||
"utilities/merge_operators.cc",
|
||||
"utilities/merge_operators/bytesxor.cc",
|
||||
"utilities/merge_operators/max.cc",
|
||||
"utilities/merge_operators/put.cc",
|
||||
@@ -394,6 +417,7 @@ cpp_library(
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/table_properties_collectors/compact_on_deletion_collector.cc",
|
||||
"utilities/trace/file_trace_reader_writer.cc",
|
||||
"utilities/trace/replayer_impl.cc",
|
||||
"utilities/transactions/lock/lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_tracker.cc",
|
||||
@@ -424,19 +448,21 @@ cpp_library(
|
||||
"utilities/transactions/write_unprepared_txn.cc",
|
||||
"utilities/transactions/write_unprepared_txn_db.cc",
|
||||
"utilities/ttl/db_ttl_impl.cc",
|
||||
"utilities/wal_filter.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
],
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
link_whole = False,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
link_whole = False,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -444,16 +470,19 @@ cpp_library(
|
||||
srcs = [
|
||||
"cache/cache.cc",
|
||||
"cache/cache_entry_roles.cc",
|
||||
"cache/cache_reservation_manager.cc",
|
||||
"cache/clock_cache.cc",
|
||||
"cache/lru_cache.cc",
|
||||
"cache/sharded_cache.cc",
|
||||
"db/arena_wrapped_db_iter.cc",
|
||||
"db/blob/blob_fetcher.cc",
|
||||
"db/blob/blob_file_addition.cc",
|
||||
"db/blob/blob_file_builder.cc",
|
||||
"db/blob/blob_file_cache.cc",
|
||||
"db/blob/blob_file_garbage.cc",
|
||||
"db/blob/blob_file_meta.cc",
|
||||
"db/blob/blob_file_reader.cc",
|
||||
"db/blob/blob_garbage_meter.cc",
|
||||
"db/blob/blob_log_format.cc",
|
||||
"db/blob/blob_log_sequential_reader.cc",
|
||||
"db/blob/blob_log_writer.cc",
|
||||
@@ -533,6 +562,7 @@ cpp_library(
|
||||
"env/fs_remap.cc",
|
||||
"env/io_posix.cc",
|
||||
"env/mock_env.cc",
|
||||
"env/unique_id_gen.cc",
|
||||
"file/delete_scheduler.cc",
|
||||
"file/file_prefetch_buffer.cc",
|
||||
"file/file_util.cc",
|
||||
@@ -634,6 +664,7 @@ cpp_library(
|
||||
"table/table_factory.cc",
|
||||
"table/table_properties.cc",
|
||||
"table/two_level_iterator.cc",
|
||||
"table/unique_id.cc",
|
||||
"test_util/sync_point.cc",
|
||||
"test_util/sync_point_impl.cc",
|
||||
"test_util/transaction_test_util.cc",
|
||||
@@ -644,6 +675,9 @@ cpp_library(
|
||||
"tools/sst_dump_tool.cc",
|
||||
"trace_replay/block_cache_tracer.cc",
|
||||
"trace_replay/io_tracer.cc",
|
||||
"trace_replay/trace_record.cc",
|
||||
"trace_replay/trace_record_handler.cc",
|
||||
"trace_replay/trace_record_result.cc",
|
||||
"trace_replay/trace_replay.cc",
|
||||
"util/build_version.cc",
|
||||
"util/coding.cc",
|
||||
@@ -659,6 +693,7 @@ cpp_library(
|
||||
"util/murmurhash.cc",
|
||||
"util/random.cc",
|
||||
"util/rate_limiter.cc",
|
||||
"util/regex.cc",
|
||||
"util/ribbon_config.cc",
|
||||
"util/slice.cc",
|
||||
"util/status.cc",
|
||||
@@ -673,10 +708,13 @@ cpp_library(
|
||||
"utilities/blob_db/blob_db_impl_filesnapshot.cc",
|
||||
"utilities/blob_db/blob_dump_tool.cc",
|
||||
"utilities/blob_db/blob_file.cc",
|
||||
"utilities/cache_dump_load.cc",
|
||||
"utilities/cache_dump_load_impl.cc",
|
||||
"utilities/cassandra/cassandra_compaction_filter.cc",
|
||||
"utilities/cassandra/format.cc",
|
||||
"utilities/cassandra/merge_operator.cc",
|
||||
"utilities/checkpoint/checkpoint_impl.cc",
|
||||
"utilities/compaction_filters.cc",
|
||||
"utilities/compaction_filters/remove_emptyvalue_compactionfilter.cc",
|
||||
"utilities/convenience/info_log_finder.cc",
|
||||
"utilities/debug.cc",
|
||||
@@ -686,6 +724,7 @@ cpp_library(
|
||||
"utilities/fault_injection_fs.cc",
|
||||
"utilities/leveldb_options/leveldb_options.cc",
|
||||
"utilities/memory/memory_util.cc",
|
||||
"utilities/merge_operators.cc",
|
||||
"utilities/merge_operators/bytesxor.cc",
|
||||
"utilities/merge_operators/max.cc",
|
||||
"utilities/merge_operators/put.cc",
|
||||
@@ -705,6 +744,7 @@ cpp_library(
|
||||
"utilities/simulator_cache/sim_cache.cc",
|
||||
"utilities/table_properties_collectors/compact_on_deletion_collector.cc",
|
||||
"utilities/trace/file_trace_reader_writer.cc",
|
||||
"utilities/trace/replayer_impl.cc",
|
||||
"utilities/transactions/lock/lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_manager.cc",
|
||||
"utilities/transactions/lock/point/point_lock_tracker.cc",
|
||||
@@ -735,19 +775,21 @@ cpp_library(
|
||||
"utilities/transactions/write_unprepared_txn.cc",
|
||||
"utilities/transactions/write_unprepared_txn_db.cc",
|
||||
"utilities/ttl/db_ttl_impl.cc",
|
||||
"utilities/wal_filter.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index.cc",
|
||||
"utilities/write_batch_with_index/write_batch_with_index_internal.cc",
|
||||
],
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
link_whole = True,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
link_whole = True,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -765,15 +807,16 @@ cpp_library(
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
link_whole = False,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
],
|
||||
link_whole = False,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -788,13 +831,14 @@ cpp_library(
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
link_whole = False,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
link_whole = False,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -803,13 +847,14 @@ cpp_library(
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
link_whole = False,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [":rocksdb_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
link_whole = False,
|
||||
)
|
||||
|
||||
cpp_library(
|
||||
@@ -820,9 +865,11 @@ cpp_library(
|
||||
"db_stress_tool/db_stress_common.cc",
|
||||
"db_stress_tool/db_stress_driver.cc",
|
||||
"db_stress_tool/db_stress_gflags.cc",
|
||||
"db_stress_tool/db_stress_listener.cc",
|
||||
"db_stress_tool/db_stress_shared_state.cc",
|
||||
"db_stress_tool/db_stress_test_base.cc",
|
||||
"db_stress_tool/db_stress_tool.cc",
|
||||
"db_stress_tool/expected_state.cc",
|
||||
"db_stress_tool/no_batched_ops_stress.cc",
|
||||
"test_util/testutil.cc",
|
||||
"tools/block_cache_analyzer/block_cache_trace_analyzer.cc",
|
||||
@@ -831,10 +878,11 @@ cpp_library(
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = ROCKSDB_LIB_DEPS,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
@@ -843,10 +891,10 @@ cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
) if not is_opt_mode else None
|
||||
|
||||
@@ -865,13 +913,14 @@ cpp_library(
|
||||
auto_headers = AutoHeaders.RECURSIVE_GLOB,
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
link_whole = False,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
link_whole = False,
|
||||
)
|
||||
|
||||
# [test_name, test_src, test_type, extra_deps, extra_compiler_flags]
|
||||
@@ -904,6 +953,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_counting_iterator_test",
|
||||
"db/blob/blob_counting_iterator_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_db_test",
|
||||
"utilities/blob_db/blob_db_test.cc",
|
||||
@@ -946,6 +1002,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"blob_garbage_meter_test",
|
||||
"db/blob/blob_garbage_meter_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"block_based_filter_block_test",
|
||||
"table/block_based/block_based_filter_block_test.cc",
|
||||
@@ -995,6 +1058,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"cache_reservation_manager_test",
|
||||
"cache/cache_reservation_manager_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"cache_simulator_test",
|
||||
"utilities/simulator_cache/cache_simulator_test.cc",
|
||||
@@ -1051,6 +1121,13 @@ ROCKS_TESTS = [
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"clipping_iterator_test",
|
||||
"db/compaction/clipping_iterator_test.cc",
|
||||
"parallel",
|
||||
[],
|
||||
[],
|
||||
],
|
||||
[
|
||||
"coding_test",
|
||||
"util/coding_test.cc",
|
||||
@@ -2160,10 +2237,10 @@ ROCKS_TESTS = [
|
||||
name = test_name,
|
||||
srcs = [test_cc],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS + extra_compiler_flags,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"] + extra_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
|
||||
@@ -113,5 +113,14 @@ LzLabs is using RocksDB as a storage engine in their multi-database distributed
|
||||
[YugabyteDB](https://www.yugabyte.com/) is an open source, high performance, distributed SQL database that uses RocksDB as its storage layer. For more information, please see https://github.com/yugabyte/yugabyte-db/.
|
||||
|
||||
## ArangoDB
|
||||
[ArangoDB](https://www.arangodb.com/) is a native multi-model database with flexible data models for documents, graphs, and key-values, for building high performance applications using a convenient SQL-like query language or JavaScript extensions. It uses RocksDB as its sotrage engine.
|
||||
[ArangoDB](https://www.arangodb.com/) is a native multi-model database with flexible data models for documents, graphs, and key-values, for building high performance applications using a convenient SQL-like query language or JavaScript extensions. It uses RocksDB as its storage engine.
|
||||
|
||||
## Milvus
|
||||
[Milvus](https://milvus.io/) is an open source vector database for unstructured data. It uses RocksDB not only as one of the supported kv storage engines, but also as a message queue.
|
||||
|
||||
## Kafka
|
||||
[Kafka](https://kafka.apache.org/) is an open-source distributed event streaming platform, it uses RocksDB to store state in Kafka Streams: https://www.confluent.io/blog/how-to-tune-rocksdb-kafka-streams-state-stores-performance/.
|
||||
|
||||
## Others
|
||||
More databases using RocksDB can be found at [dbdb.io](https://dbdb.io/browse?embeds=rocksdb).
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ We strive to achieve the following goals:
|
||||
* make all unit test pass both in debug and release builds.
|
||||
* Note: latest introduction of SyncPoint seems to disable running db_test in Release.
|
||||
* make performance on par with published benchmarks accounting for HW differences
|
||||
* we would like to keep the port code inline with the master branch with no forking
|
||||
* we would like to keep the port code inline with the main branch with no forking
|
||||
|
||||
## Build system
|
||||
We have chosen CMake as a widely accepted build system to build the Windows port. It is very fast and convenient.
|
||||
@@ -66,7 +66,7 @@ We endeavored to make it functionally on par with posix_env. This means we repli
|
||||
Even though Windows provides its own efficient thread-pool implementation we chose to replicate posix logic using `std::thread` primitives. This allows anyone to quickly detect any changes within the posix source code and replicate them within windows env. This has proven to work very well. At the same time for anyone who wishes to replace the built-in thread-pool can do so using RocksDB stackable environments.
|
||||
|
||||
For disk access we implemented all of the functionality present within the posix_env which includes memory mapped files, random access, rate-limiter support etc.
|
||||
The `use_os_buffer` flag on Posix platforms currently denotes disabling read-ahead log via `fadvise` mechanism. Windows does not have `fadvise` system call. What is more, it implements disk cache in a way that differs from Linux greatly. It’s not an uncommon practice on Windows to perform un-buffered disk access to gain control of the memory consumption. We think that in our use case this may also be a good configuration option at the expense of disk throughput. To compensate one may increase the configured in-memory cache size instead. Thus we have chosen `use_os_buffer=false` to disable OS disk buffering for `WinWritableFile` and `WinRandomAccessFile`. The OS imposes restrictions on the alignment of the disk offsets, buffers used and the amount of data that is read/written when accessing files in un-buffered mode. When the option is true, the classes behave in a standard way. This allows to perform writes and reads in cases when un-buffered access does not make sense such as WAL and MANIFEST.
|
||||
The `use_os_buffer` flag on Posix platforms currently denotes disabling read-ahead log via `fadvise` mechanism. Windows does not have `fadvise` system call. What is more, it implements disk cache in a way that differs from Linux greatly. It's not an uncommon practice on Windows to perform un-buffered disk access to gain control of the memory consumption. We think that in our use case this may also be a good configuration option at the expense of disk throughput. To compensate one may increase the configured in-memory cache size instead. Thus we have chosen `use_os_buffer=false` to disable OS disk buffering for `WinWritableFile` and `WinRandomAccessFile`. The OS imposes restrictions on the alignment of the disk offsets, buffers used and the amount of data that is read/written when accessing files in un-buffered mode. When the option is true, the classes behave in a standard way. This allows to perform writes and reads in cases when un-buffered access does not make sense such as WAL and MANIFEST.
|
||||
|
||||
We have replaced `pread/pwrite` with `WriteFile/ReadFile` with `OVERLAPPED` structure so we can atomically seek to the position of the disk operation but still perform the operation synchronously. Thus we able to emulate that functionality of `pread/pwrite` reasonably well. The only difference is that the file pointer is not returned to its original position but that hardly matters given the random nature of access.
|
||||
|
||||
|
||||
@@ -84,10 +84,10 @@ cpp_binary(
|
||||
name = "c_test_bin",
|
||||
srcs = ["db/c_test.c"],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
) if not is_opt_mode else None
|
||||
|
||||
|
||||
+25
-13
@@ -17,7 +17,7 @@ load(":defs.bzl", "test_binary")
|
||||
|
||||
REPO_PATH = package_name() + "/"
|
||||
|
||||
ROCKSDB_COMPILER_FLAGS = [
|
||||
ROCKSDB_COMPILER_FLAGS_0 = [
|
||||
"-fno-builtin-memcmp",
|
||||
# Needed to compile in fbcode
|
||||
"-Wno-expansion-to-defined",
|
||||
@@ -35,10 +35,14 @@ ROCKSDB_EXTERNAL_DEPS = [
|
||||
("zstd", None, "zstd"),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_DEPS = [
|
||||
ROCKSDB_OS_DEPS_0 = [
|
||||
(
|
||||
"linux",
|
||||
["third-party//numa:numa", "third-party//liburing:uring", "third-party//tbb:tbb"],
|
||||
[
|
||||
"third-party//numa:numa",
|
||||
"third-party//liburing:uring",
|
||||
"third-party//tbb:tbb",
|
||||
],
|
||||
),
|
||||
(
|
||||
"macos",
|
||||
@@ -46,7 +50,7 @@ ROCKSDB_OS_DEPS = [
|
||||
),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS = [
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS_0 = [
|
||||
(
|
||||
"linux",
|
||||
[
|
||||
@@ -76,7 +80,13 @@ ROCKSDB_OS_PREPROCESSOR_FLAGS = [
|
||||
),
|
||||
(
|
||||
"windows",
|
||||
[ "-DOS_WIN", "-DWIN32", "-D_MBCS", "-DWIN64", "-DNOMINMAX" ]
|
||||
[
|
||||
"-DOS_WIN",
|
||||
"-DWIN32",
|
||||
"-D_MBCS",
|
||||
"-DWIN64",
|
||||
"-DNOMINMAX",
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
@@ -114,18 +124,18 @@ is_opt_mode = build_mode.startswith("opt")
|
||||
|
||||
# -DNDEBUG is added by default in opt mode in fbcode. But adding it twice
|
||||
# doesn't harm and avoid forgetting to add it.
|
||||
ROCKSDB_COMPILER_FLAGS += (["-DNDEBUG"] if is_opt_mode else [])
|
||||
ROCKSDB_COMPILER_FLAGS = ROCKSDB_COMPILER_FLAGS_0 + (["-DNDEBUG"] if is_opt_mode else [])
|
||||
|
||||
sanitizer = read_config("fbcode", "sanitizer")
|
||||
|
||||
# Do not enable jemalloc if sanitizer presents. RocksDB will further detect
|
||||
# whether the binary is linked with jemalloc at runtime.
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS += ([(
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS = ROCKSDB_OS_PREPROCESSOR_FLAGS_0 + ([(
|
||||
"linux",
|
||||
["-DROCKSDB_JEMALLOC"],
|
||||
)] if sanitizer == "" else [])
|
||||
|
||||
ROCKSDB_OS_DEPS += ([(
|
||||
ROCKSDB_OS_DEPS = ROCKSDB_OS_DEPS_0 + ([(
|
||||
"linux",
|
||||
["third-party//jemalloc:headers"],
|
||||
)] if sanitizer == "" else [])
|
||||
@@ -144,13 +154,14 @@ cpp_library(
|
||||
{headers_attr_prefix}headers = {headers},
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
link_whole = {link_whole},
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS{extra_external_deps},
|
||||
link_whole = {link_whole},
|
||||
)
|
||||
"""
|
||||
|
||||
@@ -161,10 +172,11 @@ cpp_library(
|
||||
{headers_attr_prefix}headers = {headers},
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
unexported_deps_by_default = False,
|
||||
deps = ROCKSDB_LIB_DEPS,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
@@ -205,10 +217,10 @@ ROCKS_TESTS = [
|
||||
name = test_name,
|
||||
srcs = [test_cc],
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS + extra_compiler_flags,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
deps = [":rocksdb_test_lib"] + extra_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
|
||||
@@ -69,10 +69,6 @@ if [ -z "$ROCKSDB_NO_FBCODE" -a -d /mnt/gvfs/third-party ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
elif [ -n "$ROCKSDB_FBCODE_BUILD_WITH_PLATFORM009" ]; then
|
||||
source "$PWD/build_tools/fbcode_config_platform009.sh"
|
||||
elif [ -z "$USE_CLANG" ]; then
|
||||
# Still use platform007 for gcc by default for build break on
|
||||
# some hosts.
|
||||
source "$PWD/build_tools/fbcode_config_platform007.sh"
|
||||
else
|
||||
source "$PWD/build_tools/fbcode_config_platform009.sh"
|
||||
fi
|
||||
@@ -193,9 +189,6 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_IOURING_PRESENT"
|
||||
fi
|
||||
fi
|
||||
if test -z "$USE_FOLLY_DISTRIBUTED_MUTEX"; then
|
||||
USE_FOLLY_DISTRIBUTED_MUTEX=1
|
||||
fi
|
||||
# PORT_FILES=port/linux/linux_specific.cc
|
||||
;;
|
||||
SunOS)
|
||||
@@ -600,11 +593,21 @@ EOF
|
||||
PLATFORM_CXXFLAGS="$PLATFORM_CXXFLAGS -faligned-new -DHAVE_ALIGNED_NEW"
|
||||
fi
|
||||
fi
|
||||
if ! test $ROCKSDB_DISABLE_BENCHMARK; then
|
||||
# Test whether google benchmark is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -lbenchmark 2>/dev/null <<EOF
|
||||
#include <benchmark/benchmark.h>
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lbenchmark"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# TODO(tec): Fix -Wshorten-64-to-32 errors on FreeBSD and enable the warning.
|
||||
# -Wshorten-64-to-32 breaks compilation on FreeBSD i386
|
||||
if ! [ "$TARGET_OS" = FreeBSD -a "$TARGET_ARCHITECTURE" = i386 ]; then
|
||||
# -Wshorten-64-to-32 breaks compilation on FreeBSD aarch64 and i386
|
||||
if ! { [ "$TARGET_OS" = FreeBSD ] && [ "$TARGET_ARCHITECTURE" = arm64 -o "$TARGET_ARCHITECTURE" = i386 ]; }; then
|
||||
# Test whether -Wshorten-64-to-32 is available
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -Wshorten-64-to-32 2>/dev/null <<EOF
|
||||
int main() {}
|
||||
@@ -635,13 +638,19 @@ if test "0$PORTABLE" -eq 0; then
|
||||
# Tune for this POWER processor, treating '+' models as base models
|
||||
POWER=`LD_SHOW_AUXV=1 /bin/true | grep AT_PLATFORM | grep -E -o power[0-9]+`
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mcpu=$POWER -mtune=$POWER "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z10 "
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep -e^arm -e^aarch64`"; then
|
||||
# TODO: Handle this with approprite options.
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^aarch64`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ \
|
||||
-fsyntax-only -march=native - -o /dev/null 2>/dev/null; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=native "
|
||||
else
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z196 "
|
||||
fi
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif [ "$TARGET_OS" == "IOS" ]; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS"
|
||||
elif [ "$TARGET_OS" == "AIX" ] || [ "$TARGET_OS" == "SunOS" ]; then
|
||||
@@ -658,6 +667,10 @@ else
|
||||
TRY_SSE_ETC="1"
|
||||
fi
|
||||
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^s390x`"; then
|
||||
COMMON_FLAGS="$COMMON_FLAGS -march=z196 "
|
||||
fi
|
||||
|
||||
if [[ "${PLATFORM}" == "OS_MACOSX" ]]; then
|
||||
# For portability compile for macOS 10.12 (2016) or newer
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.12"
|
||||
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
|
||||
#
|
||||
# Check for some simple mistakes that should prevent commit or push
|
||||
|
||||
BAD=""
|
||||
|
||||
git grep 'namespace rocksdb' -- '*.[ch]*'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ Do not hardcode namespace rocksdb. Use ROCKSDB_NAMESPACE"
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep -i 'nocommit' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo "^^^^^ Code was not intended to be committed"
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
git grep '<rocksdb/' -- ':!build_tools/check-sources.sh'
|
||||
if [ "$?" != "1" ]; then
|
||||
echo '^^^^^ Use double-quotes as in #include "rocksdb/something.h"'
|
||||
BAD=1
|
||||
fi
|
||||
|
||||
if [ "$BAD" ]; then
|
||||
exit 1
|
||||
fi
|
||||
@@ -18,3 +18,4 @@ KERNEL_HEADERS_BASE=/mnt/gvfs/third-party2/kernel-headers/32b8a2407b634df3f8f948
|
||||
BINUTILS_BASE=/mnt/gvfs/third-party2/binutils/08634589372fa5f237bfd374e8c644a8364e78c1/2.32/platform009/ba86d1f/
|
||||
VALGRIND_BASE=/mnt/gvfs/third-party2/valgrind/6ae525939ad02e5e676855082fbbc7828dbafeac/3.15.0/platform009/7f3b187
|
||||
LUA_BASE=/mnt/gvfs/third-party2/lua/162efd9561a3d21f6869f4814011e9cf1b3ff4dc/5.3.4/platform009/a6271c4
|
||||
BENCHMARK_BASE=/mnt/gvfs/third-party2/benchmark/bce8d9564eaf161700aa3a20b1051564acf555fb/1.5.5/platform009/7f3b187
|
||||
@@ -167,6 +167,4 @@ VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
LUA_PATH=
|
||||
LUA_LIB=
|
||||
|
||||
USE_FOLLY_DISTRIBUTED_MUTEX=1
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -63,6 +63,13 @@ else
|
||||
fi
|
||||
CFLAGS+=" -DGFLAGS=gflags"
|
||||
|
||||
BENCHMARK_INCLUDE=" -I $BENCHMARK_BASE/include/"
|
||||
if test -z $PIC_BUILD; then
|
||||
BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark.a"
|
||||
else
|
||||
BENCHMARK_LIBS=" $BENCHMARK_BASE/lib/libbenchmark_pic.a"
|
||||
fi
|
||||
|
||||
# location of jemalloc
|
||||
JEMALLOC_INCLUDE=" -I $JEMALLOC_BASE/include/"
|
||||
JEMALLOC_LIB=" $JEMALLOC_BASE/lib/libjemalloc.a"
|
||||
@@ -102,8 +109,9 @@ export PORTABLE
|
||||
|
||||
BINUTILS="$BINUTILS_BASE/bin"
|
||||
AR="$BINUTILS/ar"
|
||||
AS="$BINUTILS/as"
|
||||
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE"
|
||||
DEPS_INCLUDE="$SNAPPY_INCLUDE $ZLIB_INCLUDE $BZIP_INCLUDE $LZ4_INCLUDE $ZSTD_INCLUDE $GFLAGS_INCLUDE $NUMA_INCLUDE $TBB_INCLUDE $LIBURING_INCLUDE $BENCHMARK_INCLUDE"
|
||||
|
||||
STDLIBS="-L $GCC_BASE/lib64"
|
||||
|
||||
@@ -120,7 +128,7 @@ if [ -z "$USE_CLANG" ]; then
|
||||
CXX="$GCC_BASE/bin/g++"
|
||||
AR="$GCC_BASE/bin/gcc-ar"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold"
|
||||
CFLAGS+=" -B$BINUTILS"
|
||||
CFLAGS+=" -isystem $LIBGCC_INCLUDE"
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
JEMALLOC=1
|
||||
@@ -133,7 +141,7 @@ else
|
||||
|
||||
KERNEL_HEADERS_INCLUDE="$KERNEL_HEADERS_BASE/include"
|
||||
|
||||
CFLAGS+=" -B$BINUTILS/gold -nostdinc -nostdlib"
|
||||
CFLAGS+=" -B$BINUTILS -nostdinc -nostdlib"
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/9.x "
|
||||
CFLAGS+=" -isystem $LIBGCC_BASE/include/c++/9.x/x86_64-facebook-linux "
|
||||
CFLAGS+=" -isystem $GLIBC_INCLUDE"
|
||||
@@ -149,17 +157,18 @@ CFLAGS+=" $DEPS_INCLUDE"
|
||||
CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PRESENT -DROCKSDB_MALLOC_USABLE_SIZE -DROCKSDB_RANGESYNC_PRESENT -DROCKSDB_SCHED_GETCPU_PRESENT -DROCKSDB_SUPPORT_THREAD_LOCAL -DHAVE_SSE42 -DROCKSDB_IOURING_PRESENT"
|
||||
CXXFLAGS+=" $CFLAGS"
|
||||
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS"
|
||||
EXEC_LDFLAGS+=" -B$BINUTILS/gold"
|
||||
EXEC_LDFLAGS=" $SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $NUMA_LIB $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
EXEC_LDFLAGS+=" -Wl,--dynamic-linker,/usr/local/fbcode/platform009/lib/ld.so"
|
||||
EXEC_LDFLAGS+=" $LIBUNWIND"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=/usr/local/fbcode/platform009/lib"
|
||||
EXEC_LDFLAGS+=" -Wl,-rpath=$GCC_BASE/lib64"
|
||||
# required by libtbb
|
||||
EXEC_LDFLAGS+=" -ldl"
|
||||
|
||||
PLATFORM_LDFLAGS="$LIBGCC_LIBS $GLIBC_LIBS $STDLIBS -lgcc -lstdc++"
|
||||
PLATFORM_LDFLAGS+=" -B$BINUTILS"
|
||||
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_LIBS"
|
||||
EXEC_LDFLAGS_SHARED="$SNAPPY_LIBS $ZLIB_LIBS $BZIP_LIBS $LZ4_LIBS $ZSTD_LIBS $GFLAGS_LIBS $TBB_LIBS $LIBURING_LIBS $BENCHMARK_LIBS"
|
||||
|
||||
VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
|
||||
@@ -167,6 +176,4 @@ VALGRIND_VER="$VALGRIND_BASE/bin/"
|
||||
LUA_PATH=
|
||||
LUA_LIB=
|
||||
|
||||
USE_FOLLY_DISTRIBUTED_MUTEX=1
|
||||
|
||||
export CC CXX AR CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
export CC CXX AR AS CFLAGS CXXFLAGS EXEC_LDFLAGS EXEC_LDFLAGS_SHARED VALGRIND_VER JEMALLOC_LIB JEMALLOC_INCLUDE CLANG_ANALYZER CLANG_SCAN_BUILD LUA_PATH LUA_LIB
|
||||
@@ -38,7 +38,9 @@ if [ "$CLANG_FORMAT_DIFF" ]; then
|
||||
fi
|
||||
else
|
||||
# First try directly executing the possibilities
|
||||
if clang-format-diff.py --help &> /dev/null < /dev/null; then
|
||||
if clang-format-diff --help &> /dev/null < /dev/null; then
|
||||
CLANG_FORMAT_DIFF=clang-format-diff
|
||||
elif clang-format-diff.py --help &> /dev/null < /dev/null; then
|
||||
CLANG_FORMAT_DIFF=clang-format-diff.py
|
||||
elif $REPO_ROOT/clang-format-diff.py --help &> /dev/null < /dev/null; then
|
||||
CLANG_FORMAT_DIFF=$REPO_ROOT/clang-format-diff.py
|
||||
@@ -120,16 +122,16 @@ uncommitted_code=`git diff HEAD`
|
||||
|
||||
# If there's no uncommitted changes, we assume user are doing post-commit
|
||||
# format check, in which case we'll try to check the modified lines vs. the
|
||||
# facebook/rocksdb.git master branch. Otherwise, we'll check format of the
|
||||
# facebook/rocksdb.git main branch. Otherwise, we'll check format of the
|
||||
# uncommitted code only.
|
||||
if [ -z "$uncommitted_code" ]
|
||||
then
|
||||
# Attempt to get name of facebook/rocksdb.git remote.
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(git remote -v | grep 'facebook/rocksdb.git' | head -n 1 | cut -f 1)"
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE="$(LC_ALL=POSIX LANG=POSIX git remote -v | grep 'facebook/rocksdb.git' | head -n 1 | cut -f 1)"
|
||||
# Fall back on 'origin' if that fails
|
||||
[ "$FORMAT_REMOTE" ] || FORMAT_REMOTE=origin
|
||||
# Use master branch from that remote
|
||||
[ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/master"
|
||||
# Use main branch from that remote
|
||||
[ "$FORMAT_UPSTREAM" ] || FORMAT_UPSTREAM="$FORMAT_REMOTE/$(LC_ALL=POSIX LANG=POSIX git remote show $FORMAT_REMOTE | sed -n '/HEAD branch/s/.*: //p')"
|
||||
# Get the common ancestor with that remote branch. Everything after that
|
||||
# common ancestor would be considered the contents of a pull request, so
|
||||
# should be relevant for formatting fixes.
|
||||
|
||||
@@ -20,26 +20,11 @@ STAT_FILE=${STAT_FILE:-$(mktemp -t -u rocksdb_test_stats_XXXX)}
|
||||
|
||||
function cleanup {
|
||||
rm -rf $DATA_DIR
|
||||
rm -f $STAT_FILE.fillseq
|
||||
rm -f $STAT_FILE.readrandom
|
||||
rm -f $STAT_FILE.overwrite
|
||||
rm -f $STAT_FILE.memtablefillreadrandom
|
||||
rm -f $STAT_FILE.*
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
if [ -z $GIT_BRANCH ]; then
|
||||
git_br=`git rev-parse --abbrev-ref HEAD`
|
||||
else
|
||||
git_br=$(basename $GIT_BRANCH)
|
||||
fi
|
||||
|
||||
if [ $git_br == "master" ]; then
|
||||
git_br=""
|
||||
else
|
||||
git_br="."$git_br
|
||||
fi
|
||||
|
||||
make release
|
||||
|
||||
# measure fillseq + fill up the DB for overwrite benchmark
|
||||
@@ -286,12 +271,10 @@ common_in_mem_args="--db=/dev/shm/rocksdb \
|
||||
--sync=0 \
|
||||
--verify_checksum=1 \
|
||||
--delete_obsolete_files_period_micros=314572800 \
|
||||
--max_grandparent_overlap_factor=10 \
|
||||
--use_plain_table=1 \
|
||||
--open_files=-1 \
|
||||
--mmap_read=1 \
|
||||
--mmap_write=0 \
|
||||
--memtablerep=prefix_hash \
|
||||
--bloom_bits=10 \
|
||||
--bloom_locality=1 \
|
||||
--perf_level=0"
|
||||
@@ -378,7 +361,7 @@ function send_to_ods {
|
||||
echo >&2 "ERROR: Key $key doesn't have a value."
|
||||
return
|
||||
fi
|
||||
curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build$git_br&key=$key&value=$value" \
|
||||
curl --silent "https://www.intern.facebook.com/intern/agent/ods_set.php?entity=rocksdb_build&key=$key&value=$value" \
|
||||
--connect-timeout 60
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ if [ ! -z $EMAIL ]; then
|
||||
'type':'email',
|
||||
'triggers': [ '$TRIGGER' ],
|
||||
'emails':['$EMAIL']
|
||||
},"
|
||||
}"
|
||||
fi
|
||||
|
||||
CREATE_TASK=
|
||||
@@ -38,8 +38,8 @@ if [ ! -z $ONCALL ]; then
|
||||
'triggers':[ 'fail' ],
|
||||
'priority':0,
|
||||
'subscribers':[ '$SUBSCRIBER' ],
|
||||
'tags':[ 'rocksdb', 'ci' ],
|
||||
},"
|
||||
'tags':[ 'rocksdb', 'ci' ]
|
||||
}"
|
||||
fi
|
||||
|
||||
# For now, create the tasks using only the dedicated task creation tool.
|
||||
@@ -48,7 +48,7 @@ CREATE_TASK=
|
||||
REPORT=
|
||||
if [[ ! -z $REPORT_EMAIL || ! -z $CREATE_TASK ]]; then
|
||||
REPORT="'report': [
|
||||
$REPORT_EMAIL
|
||||
$REPORT_EMAIL,
|
||||
$CREATE_TASK
|
||||
]"
|
||||
fi
|
||||
@@ -79,9 +79,9 @@ UPLOAD_DB_DIR="
|
||||
{
|
||||
'name':'rocksdb_db_dir',
|
||||
'paths': ['rocksdb_db.tar.gz'],
|
||||
'bundle': false,
|
||||
},
|
||||
],
|
||||
'bundle': false
|
||||
}
|
||||
]
|
||||
}"
|
||||
|
||||
# We will eventually set the RATIO to 1, but we want do this
|
||||
@@ -106,16 +106,14 @@ NON_SHM="TMPD=/tmp/rocksdb_test_tmp"
|
||||
GCC_481="ROCKSDB_FBCODE_BUILD_WITH_481=1"
|
||||
ASAN="COMPILE_WITH_ASAN=1"
|
||||
CLANG="USE_CLANG=1"
|
||||
# in gcc-5 there are known problems with TSAN like https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71090.
|
||||
# using platform007 gives us gcc-8 or higher which has that bug fixed.
|
||||
TSAN="ROCKSDB_FBCODE_BUILD_WITH_PLATFORM007=1 COMPILE_WITH_TSAN=1"
|
||||
TSAN="COMPILE_WITH_TSAN=1"
|
||||
UBSAN="COMPILE_WITH_UBSAN=1"
|
||||
TSAN_CRASH='CRASH_TEST_EXT_ARGS="--compression_type=zstd --log2_keys_per_lock=22"'
|
||||
NON_TSAN_CRASH="CRASH_TEST_EXT_ARGS=--compression_type=zstd"
|
||||
DISABLE_JEMALLOC="DISABLE_JEMALLOC=1"
|
||||
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-8u60-64/; export PATH=\$JAVA_HOME/bin:\$PATH"
|
||||
PARSER="'parser':'python build_tools/error_filter.py $1'"
|
||||
PARSER="'parser':'/usr/bin/env python3 build_tools/error_filter.py $1'"
|
||||
|
||||
CONTRUN_NAME="ROCKSDB_CONTRUN_NAME"
|
||||
SKIP_FORMAT_CHECKS="SKIP_FORMAT_BUCK_CHECKS=1"
|
||||
@@ -143,8 +141,8 @@ DISABLE_COMMANDS="[
|
||||
'name':'Job disabled. Please contact test owner',
|
||||
'shell':'exit 1',
|
||||
'user':'root'
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
]"
|
||||
|
||||
@@ -163,7 +161,7 @@ UNIT_TEST_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM check || $CONTRUN_NAME=check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -186,7 +184,7 @@ UNIT_TEST_NON_SHM_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $NON_SHM $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM check || $CONTRUN_NAME=non_shm_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -207,7 +205,7 @@ RELEASE_BUILD_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; make $PARALLEL_j release || $CONTRUN_NAME=release $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -228,7 +226,7 @@ UNIT_TEST_COMMANDS_481="[
|
||||
'shell':'cd $WORKING_DIR; $SHM $GCC_481 $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM check || $CONTRUN_NAME=unit_gcc_481_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -249,7 +247,7 @@ RELEASE_BUILD_COMMANDS_481="[
|
||||
'shell':'cd $WORKING_DIR; $GCC_481 make $PARALLEL_j release || $CONTRUN_NAME=release_gcc481 $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -270,7 +268,7 @@ CLANG_UNIT_TEST_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $CLANG $SHM $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM check || $CONTRUN_NAME=clang_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -291,7 +289,7 @@ CLANG_RELEASE_BUILD_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $CLANG make $PARALLEL_j release|| $CONTRUN_NAME=clang_release $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -312,7 +310,7 @@ CLANG_ANALYZE_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $CLANG $SHM $DEBUG make $PARALLEL_j analyze || $CONTRUN_NAME=clang_analyze $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -333,7 +331,7 @@ CODE_COV_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM coverage || $CONTRUN_NAME=coverage $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -354,7 +352,7 @@ UNITY_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG V=1 make J=1 unity_test || $CONTRUN_NAME=unity_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -375,29 +373,12 @@ LITE_BUILD_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $SKIP_FORMAT_CHECKS make J=1 LITE=1 all check || $CONTRUN_NAME=lite $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# Report RocksDB lite binary size to scuba
|
||||
REPORT_LITE_BINARY_SIZE_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Lite Binary Size',
|
||||
'oncall':'$ONCALL',
|
||||
'executeLocal': 'true',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Report RocksDB Lite binary size to scuba',
|
||||
'shell':'cd $WORKING_DIR; tools/report_lite_binary_size.sh',
|
||||
'user':'root',
|
||||
},
|
||||
],
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB stress/crash test
|
||||
#
|
||||
@@ -422,7 +403,7 @@ STRESS_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -452,7 +433,7 @@ BLACKBOX_STRESS_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -482,7 +463,7 @@ WHITEBOX_STRESS_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -512,7 +493,7 @@ STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -542,7 +523,7 @@ STRESS_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -572,7 +553,7 @@ STRESS_CRASH_TEST_WITH_TS_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -590,7 +571,7 @@ WRITE_STRESS_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB write stress tests',
|
||||
'shell':'cd $WORKING_DIR; make write_stress && python tools/write_stress_runner.py --runtime_sec=3600 --db=/tmp/rocksdb_write_stress || $CONTRUN_NAME=write_stress $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; make write_stress && /usr/bin/env python3 tools/write_stress_runner.py --runtime_sec=3600 --db=/tmp/rocksdb_write_stress || $CONTRUN_NAME=write_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -613,7 +594,7 @@ ASAN_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Test RocksDB debug under ASAN',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && ($SHM $ASAN $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM asan_check || $CONTRUN_NAME=asan_check $TASK_CREATION_TOOL) |& /usr/facebook/ops/scripts/asan_symbolize.py -d',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $ASAN $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM asan_check || $CONTRUN_NAME=asan_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -640,7 +621,7 @@ ASAN_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -664,7 +645,7 @@ ASAN_BLACKBOX_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -688,7 +669,7 @@ ASAN_WHITEBOX_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -712,7 +693,7 @@ ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -736,7 +717,7 @@ ASAN_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -781,7 +762,7 @@ UBSAN_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -805,7 +786,7 @@ UBSAN_BLACKBOX_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -829,7 +810,7 @@ UBSAN_WHITEBOX_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -853,7 +834,7 @@ UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -877,7 +858,7 @@ UBSAN_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -900,7 +881,7 @@ VALGRIND_TEST_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG make $PARALLELISM valgrind_test || $CONTRUN_NAME=valgrind_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -923,7 +904,7 @@ TSAN_UNIT_TEST_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $DEBUG $TSAN $SKIP_FORMAT_CHECKS make $PARALLELISM check || $CONTRUN_NAME=tsan_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -947,7 +928,7 @@ TSAN_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -971,7 +952,7 @@ TSAN_BLACKBOX_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -995,7 +976,7 @@ TSAN_WHITEBOX_CRASH_TEST_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -1019,7 +1000,7 @@ TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -1043,7 +1024,7 @@ TSAN_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
$UPLOAD_DB_DIR
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -1060,7 +1041,7 @@ run_format_compatible()
|
||||
mkdir /dev/shm/rocksdb
|
||||
|
||||
export https_proxy="fwdproxy:8080"
|
||||
|
||||
|
||||
tools/check_format_compatible.sh
|
||||
}
|
||||
|
||||
@@ -1076,7 +1057,7 @@ FORMAT_COMPATIBLE_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; build_tools/rocksdb-lego-determinator run_format_compatible || $CONTRUN_NAME=run_format_compatible $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -1111,7 +1092,7 @@ NO_COMPRESSION_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; build_tools/rocksdb-lego-determinator run_no_compression || $CONTRUN_NAME=run_no_compression $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -1122,7 +1103,7 @@ NO_COMPRESSION_COMMANDS="[
|
||||
#
|
||||
run_regression()
|
||||
{
|
||||
time -v bash -vx ./build_tools/regression_build_test.sh $(mktemp -d $WORKSPACE/leveldb.XXXX) $(mktemp leveldb_test_stats.XXXX)
|
||||
time bash -vx ./build_tools/regression_build_test.sh $(mktemp -d $WORKING_DIR/rocksdb.XXXX) $(mktemp rocksdb_test_stats.XXXX)
|
||||
|
||||
# ======= report size to ODS ========
|
||||
|
||||
@@ -1163,6 +1144,7 @@ REGRESSION_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb regression commands',
|
||||
'oncall':'$ONCALL',
|
||||
'executeLocal': 'true',
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
@@ -1170,7 +1152,7 @@ REGRESSION_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; build_tools/rocksdb-lego-determinator run_regression || $CONTRUN_NAME=run_regression $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -1191,7 +1173,41 @@ JAVA_BUILD_TEST_COMMANDS="[
|
||||
'shell':'cd $WORKING_DIR; $SETUP_JAVA_ENV; $SHM make rocksdbjava || $CONTRUN_NAME=rocksdbjava $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB fbcode stress/crash test
|
||||
#
|
||||
FBCODE_STRESS_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Fbcode Stress and Crash Test',
|
||||
'oncall':'$ONCALL',
|
||||
'executeLocal': 'true',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
{
|
||||
'name':'Copy RocksDB code to fbcode repo',
|
||||
'shell':'cd internal_repo_rocksdb/repo && git init -b main && git add * && git commit -a -m \"Make internal_repo_rocksdb/repo a git repo\" && cd ../.. && echo Y | python3 rocks/release_script/release_to_fbcode.py -u internal_repo_rocksdb/repo main || $CONTRUN_NAME=db_stress_fbcode $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build RocksDB fbcode stress tests',
|
||||
'shell':'cd $WORKING_DIR; buck build @mode/dbg rocks/tools:rocks_db_stress || $CONTRUN_NAME=db_stress_fbcode $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Run RocksDB whitebox crash tests',
|
||||
'timeout': 86400,
|
||||
'shell':'cd $WORKING_DIR; mkdir /dev/shm/rocksdb_fbcode_crash_test && TEST_TMPDIR=\$(mktemp -d --tmpdir=/dev/shm/rocksdb_fbcode_crash_test) python3 rocksdb/src/tools/db_crashtest.py --stress_cmd=buck-out/dbg/gen/rocks/tools/rocks_db_stress -secondary_cache_uri=\"$SECONDARY_CACHE_URI\" --env_uri=$ENV_URI $EXTRA_DB_STRESS_ARGS -logtostderr=false $TEST_TYPE || $CONTRUN_NAME=db_stress_fbcode $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
@@ -1232,9 +1248,6 @@ case $1 in
|
||||
lite)
|
||||
echo $LITE_BUILD_COMMANDS
|
||||
;;
|
||||
report_lite_binary_size)
|
||||
echo $REPORT_LITE_BINARY_SIZE_COMMANDS
|
||||
;;
|
||||
stress_crash)
|
||||
echo $STRESS_CRASH_TEST_COMMANDS
|
||||
;;
|
||||
@@ -1329,11 +1342,18 @@ case $1 in
|
||||
echo $REGRESSION_COMMANDS
|
||||
;;
|
||||
run_regression)
|
||||
set -e
|
||||
run_regression
|
||||
set +e
|
||||
;;
|
||||
java_build)
|
||||
echo $JAVA_BUILD_TEST_COMMANDS
|
||||
;;
|
||||
fbcode_stress_crash)
|
||||
set -f
|
||||
echo $FBCODE_STRESS_CRASH_TEST_COMMANDS
|
||||
set +f
|
||||
;;
|
||||
*)
|
||||
echo "Invalid determinator command"
|
||||
exit 1
|
||||
|
||||
Vendored
+9
@@ -10,6 +10,8 @@
|
||||
#include "rocksdb/cache.h"
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/utilities/customizable_util.h"
|
||||
#include "rocksdb/utilities/options_type.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
@@ -34,6 +36,13 @@ static std::unordered_map<std::string, OptionTypeInfo>
|
||||
};
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status SecondaryCache::CreateFromString(
|
||||
const ConfigOptions& config_options, const std::string& value,
|
||||
std::shared_ptr<SecondaryCache>* result) {
|
||||
return LoadSharedObject<SecondaryCache>(config_options, value, nullptr,
|
||||
result);
|
||||
}
|
||||
|
||||
Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
const std::string& value,
|
||||
std::shared_ptr<Cache>* result) {
|
||||
|
||||
Vendored
+1
-1
@@ -13,7 +13,7 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
#include <rocksdb/cache_bench_tool.h>
|
||||
#include "rocksdb/cache_bench_tool.h"
|
||||
int main(int argc, char** argv) {
|
||||
return ROCKSDB_NAMESPACE::cache_bench_tool(argc, argv);
|
||||
}
|
||||
|
||||
Vendored
+3
-4
@@ -13,11 +13,11 @@
|
||||
#include "monitoring/histogram.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "table/block_based/cachable_entry.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/gflags_compat.h"
|
||||
@@ -233,9 +233,8 @@ class CacheBench {
|
||||
LRUCacheOptions opts(FLAGS_cache_size, FLAGS_num_shard_bits, false, 0.5);
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (!FLAGS_secondary_cache_uri.empty()) {
|
||||
Status s =
|
||||
ObjectRegistry::NewInstance()->NewSharedObject<SecondaryCache>(
|
||||
FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
Status s = SecondaryCache::CreateFromString(
|
||||
ConfigOptions(), FLAGS_secondary_cache_uri, &secondary_cache);
|
||||
if (secondary_cache == nullptr) {
|
||||
fprintf(
|
||||
stderr,
|
||||
|
||||
Vendored
+2
@@ -19,6 +19,7 @@ std::array<const char*, kNumCacheEntryRoles> kCacheEntryRoleToCamelString{{
|
||||
"IndexBlock",
|
||||
"OtherBlock",
|
||||
"WriteBuffer",
|
||||
"CompressionDictionaryBuildingBuffer",
|
||||
"Misc",
|
||||
}};
|
||||
|
||||
@@ -30,6 +31,7 @@ std::array<const char*, kNumCacheEntryRoles> kCacheEntryRoleToHyphenString{{
|
||||
"index-block",
|
||||
"other-block",
|
||||
"write-buffer",
|
||||
"compression-dictionary-building-buffer",
|
||||
"misc",
|
||||
}};
|
||||
|
||||
|
||||
Vendored
+6
-1
@@ -14,6 +14,8 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Classifications of block cache entries, for reporting statistics
|
||||
// Adding new enum to this class requires corresponding updates to
|
||||
// kCacheEntryRoleToCamelString and kCacheEntryRoleToHyphenString
|
||||
enum class CacheEntryRole {
|
||||
// Block-based table data block
|
||||
kDataBlock,
|
||||
@@ -27,8 +29,11 @@ enum class CacheEntryRole {
|
||||
kIndexBlock,
|
||||
// Other kinds of block-based table block
|
||||
kOtherBlock,
|
||||
// WriteBufferManager resevations to account for memtable usage
|
||||
// WriteBufferManager reservations to account for memtable usage
|
||||
kWriteBuffer,
|
||||
// BlockBasedTableBuilder reservations to account for
|
||||
// compression dictionary building buffer's memory usage
|
||||
kCompressionDictionaryBuildingBuffer,
|
||||
// Default bucket, for miscellaneous cache entries. Do not use for
|
||||
// entries that could potentially add up to large usage.
|
||||
kMisc,
|
||||
|
||||
Vendored
+47
-18
@@ -15,6 +15,7 @@
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/coding_lean.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -51,35 +52,56 @@ namespace ROCKSDB_NAMESPACE {
|
||||
template <class Stats>
|
||||
class CacheEntryStatsCollector {
|
||||
public:
|
||||
// Gathers stats and saves results into `stats`
|
||||
void GetStats(Stats *stats, int maximum_age_in_seconds = 180) {
|
||||
// Gather and save stats if saved stats are too old. (Use GetStats() to
|
||||
// read saved stats.)
|
||||
//
|
||||
// Maximum allowed age for a "hit" on saved results is determined by the
|
||||
// two interval parameters. Both set to 0 forces a re-scan. For example
|
||||
// with min_interval_seconds=300 and min_interval_factor=100, if the last
|
||||
// scan took 10s, we would only rescan ("miss") if the age in seconds of
|
||||
// the saved results is > max(300, 100*10).
|
||||
// Justification: scans can vary wildly in duration, e.g. from 0.02 sec
|
||||
// to as much as 20 seconds, so we want to be able to cap the absolute
|
||||
// and relative frequency of scans.
|
||||
void CollectStats(int min_interval_seconds, int min_interval_factor) {
|
||||
// Waits for any pending reader or writer (collector)
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::lock_guard<std::mutex> lock(working_mutex_);
|
||||
|
||||
// Maximum allowed age is nominally given by the parameter
|
||||
uint64_t max_age_micros =
|
||||
static_cast<uint64_t>(std::min(maximum_age_in_seconds, 0)) * 1000000U;
|
||||
// But we will re-scan more frequently if it means scanning < 1%
|
||||
// of the time and no more than once per second.
|
||||
max_age_micros = std::min(
|
||||
max_age_micros,
|
||||
std::max(uint64_t{1000000},
|
||||
100U * (last_end_time_micros_ - last_start_time_micros_)));
|
||||
static_cast<uint64_t>(std::max(min_interval_seconds, 0)) * 1000000U;
|
||||
|
||||
if (last_end_time_micros_ > last_start_time_micros_ &&
|
||||
min_interval_factor > 0) {
|
||||
max_age_micros = std::max(
|
||||
max_age_micros, min_interval_factor * (last_end_time_micros_ -
|
||||
last_start_time_micros_));
|
||||
}
|
||||
|
||||
uint64_t start_time_micros = clock_->NowMicros();
|
||||
if ((start_time_micros - last_end_time_micros_) > max_age_micros) {
|
||||
last_start_time_micros_ = start_time_micros;
|
||||
saved_stats_.BeginCollection(cache_, clock_, start_time_micros);
|
||||
working_stats_.BeginCollection(cache_, clock_, start_time_micros);
|
||||
|
||||
cache_->ApplyToAllEntries(saved_stats_.GetEntryCallback(), {});
|
||||
cache_->ApplyToAllEntries(working_stats_.GetEntryCallback(), {});
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CacheEntryStatsCollector::GetStats:AfterApplyToAllEntries", nullptr);
|
||||
|
||||
uint64_t end_time_micros = clock_->NowMicros();
|
||||
last_end_time_micros_ = end_time_micros;
|
||||
saved_stats_.EndCollection(cache_, clock_, end_time_micros);
|
||||
working_stats_.EndCollection(cache_, clock_, end_time_micros);
|
||||
} else {
|
||||
saved_stats_.SkippedCollection();
|
||||
working_stats_.SkippedCollection();
|
||||
}
|
||||
// Copy to caller
|
||||
|
||||
// Save so that we don't need to wait for an outstanding collection in
|
||||
// order to make of copy of the last saved stats
|
||||
std::lock_guard<std::mutex> lock2(saved_mutex_);
|
||||
saved_stats_ = working_stats_;
|
||||
}
|
||||
|
||||
// Gets saved stats, regardless of age
|
||||
void GetStats(Stats *stats) {
|
||||
std::lock_guard<std::mutex> lock(saved_mutex_);
|
||||
*stats = saved_stats_;
|
||||
}
|
||||
|
||||
@@ -113,9 +135,11 @@ class CacheEntryStatsCollector {
|
||||
// usage to go flaky. Fix the problem somehow so we can use an
|
||||
// accurate charge.
|
||||
size_t charge = 0;
|
||||
Status s = cache->Insert(cache_key, new_ptr, charge, Deleter, &h);
|
||||
Status s = cache->Insert(cache_key, new_ptr, charge, Deleter, &h,
|
||||
Cache::Priority::HIGH);
|
||||
if (!s.ok()) {
|
||||
assert(h == nullptr);
|
||||
delete new_ptr;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
@@ -132,6 +156,7 @@ class CacheEntryStatsCollector {
|
||||
private:
|
||||
explicit CacheEntryStatsCollector(Cache *cache, SystemClock *clock)
|
||||
: saved_stats_(),
|
||||
working_stats_(),
|
||||
last_start_time_micros_(0),
|
||||
last_end_time_micros_(/*pessimistic*/ 10000000),
|
||||
cache_(cache),
|
||||
@@ -141,10 +166,14 @@ class CacheEntryStatsCollector {
|
||||
delete static_cast<CacheEntryStatsCollector *>(value);
|
||||
}
|
||||
|
||||
std::mutex mutex_;
|
||||
std::mutex saved_mutex_;
|
||||
Stats saved_stats_;
|
||||
|
||||
std::mutex working_mutex_;
|
||||
Stats working_stats_;
|
||||
uint64_t last_start_time_micros_;
|
||||
uint64_t last_end_time_micros_;
|
||||
|
||||
Cache *const cache_;
|
||||
SystemClock *const clock_;
|
||||
};
|
||||
|
||||
Vendored
+131
@@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
CacheReservationManager::CacheReservationManager(std::shared_ptr<Cache> cache,
|
||||
bool delayed_decrease)
|
||||
: delayed_decrease_(delayed_decrease), cache_allocated_size_(0) {
|
||||
assert(cache != nullptr);
|
||||
cache_ = cache;
|
||||
std::memset(cache_key_, 0, kCacheKeyPrefixSize + kMaxVarint64Length);
|
||||
EncodeVarint64(cache_key_, cache_->NewId());
|
||||
}
|
||||
|
||||
CacheReservationManager::~CacheReservationManager() {
|
||||
for (auto* handle : dummy_handles_) {
|
||||
cache_->Release(handle, true);
|
||||
}
|
||||
}
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManager::UpdateCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
std::size_t cur_cache_allocated_size =
|
||||
cache_allocated_size_.load(std::memory_order_relaxed);
|
||||
if (new_mem_used == cur_cache_allocated_size) {
|
||||
return Status::OK();
|
||||
} else if (new_mem_used > cur_cache_allocated_size) {
|
||||
Status s = IncreaseCacheReservation<R>(new_mem_used);
|
||||
return s;
|
||||
} else {
|
||||
// In delayed decrease mode, we don't decrease cache reservation
|
||||
// untill the memory usage is less than 3/4 of what we reserve
|
||||
// in the cache.
|
||||
// We do this because
|
||||
// (1) Dummy entry insertion is expensive in block cache
|
||||
// (2) Delayed releasing previously inserted dummy entries can save such
|
||||
// expensive dummy entry insertion on memory increase in the near future,
|
||||
// which is likely to happen when the memory usage is greater than or equal
|
||||
// to 3/4 of what we reserve
|
||||
if (delayed_decrease_ && new_mem_used >= cur_cache_allocated_size / 4 * 3) {
|
||||
return Status::OK();
|
||||
} else {
|
||||
Status s = DecreaseCacheReservation(new_mem_used);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Explicitly instantiate templates for "CacheEntryRole" values we use.
|
||||
// This makes it possible to keep the template definitions in the .cc file.
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kWriteBuffer>(std::size_t new_mem_used);
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kCompressionDictionaryBuildingBuffer>(
|
||||
std::size_t new_mem_used);
|
||||
// For cache reservation manager unit tests
|
||||
template Status CacheReservationManager::UpdateCacheReservation<
|
||||
CacheEntryRole::kMisc>(std::size_t new_mem_used);
|
||||
|
||||
template <CacheEntryRole R>
|
||||
Status CacheReservationManager::IncreaseCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
Status return_status = Status::OK();
|
||||
while (new_mem_used > cache_allocated_size_.load(std::memory_order_relaxed)) {
|
||||
Cache::Handle* handle = nullptr;
|
||||
return_status = cache_->Insert(GetNextCacheKey(), nullptr, kSizeDummyEntry,
|
||||
GetNoopDeleterForRole<R>(), &handle);
|
||||
|
||||
if (return_status != Status::OK()) {
|
||||
return return_status;
|
||||
}
|
||||
|
||||
dummy_handles_.push_back(handle);
|
||||
cache_allocated_size_ += kSizeDummyEntry;
|
||||
}
|
||||
return return_status;
|
||||
}
|
||||
|
||||
Status CacheReservationManager::DecreaseCacheReservation(
|
||||
std::size_t new_mem_used) {
|
||||
Status return_status = Status::OK();
|
||||
|
||||
// Decrease to the smallest multiple of kSizeDummyEntry that is greater than
|
||||
// or equal to new_mem_used We do addition instead of new_mem_used <=
|
||||
// cache_allocated_size_.load(std::memory_order_relaxed) - kSizeDummyEntry to
|
||||
// avoid underflow of size_t when cache_allocated_size_ = 0
|
||||
while (new_mem_used + kSizeDummyEntry <=
|
||||
cache_allocated_size_.load(std::memory_order_relaxed)) {
|
||||
assert(!dummy_handles_.empty());
|
||||
auto* handle = dummy_handles_.back();
|
||||
cache_->Release(handle, true);
|
||||
dummy_handles_.pop_back();
|
||||
cache_allocated_size_ -= kSizeDummyEntry;
|
||||
}
|
||||
return return_status;
|
||||
}
|
||||
|
||||
std::size_t CacheReservationManager::GetTotalReservedCacheSize() {
|
||||
return cache_allocated_size_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Slice CacheReservationManager::GetNextCacheKey() {
|
||||
// Calling this function will have the side-effect of changing the
|
||||
// underlying cache_key_ that is shared among other keys generated from this
|
||||
// fucntion. Therefore please make sure the previous keys are saved/copied
|
||||
// before calling this function.
|
||||
std::memset(cache_key_ + kCacheKeyPrefixSize, 0, kMaxVarint64Length);
|
||||
char* end =
|
||||
EncodeVarint64(cache_key_ + kCacheKeyPrefixSize, next_cache_key_id_++);
|
||||
return Slice(cache_key_, static_cast<std::size_t>(end - cache_key_));
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+100
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// CacheReservationManager is for reserving cache space for the memory used
|
||||
// through inserting/releasing dummy entries in the cache.
|
||||
// This class is not thread-safe.
|
||||
class CacheReservationManager {
|
||||
public:
|
||||
// Construct a CacheReservationManager
|
||||
// @param cache The cache where dummy entries are inserted and released for
|
||||
// reserving cache space
|
||||
// @param delayed_decrease If set true, then dummy entries won't be released
|
||||
// immediately when memory usage decreases.
|
||||
// Instead, it will be released when the memory usage
|
||||
// decreases to 3/4 of what we have reserved so far.
|
||||
// This is for saving some future dummy entry
|
||||
// insertion when memory usage increases are likely to
|
||||
// happen in the near future.
|
||||
explicit CacheReservationManager(std::shared_ptr<Cache> cache,
|
||||
bool delayed_decrease = false);
|
||||
|
||||
// no copy constructor, copy assignment, move constructor, move assignment
|
||||
CacheReservationManager(const CacheReservationManager &) = delete;
|
||||
CacheReservationManager &operator=(const CacheReservationManager &) = delete;
|
||||
CacheReservationManager(CacheReservationManager &&) = delete;
|
||||
CacheReservationManager &operator=(CacheReservationManager &&) = delete;
|
||||
|
||||
~CacheReservationManager();
|
||||
|
||||
template <CacheEntryRole R>
|
||||
|
||||
// Insert and release dummy entries in the cache to
|
||||
// match the size of total dummy entries with the smallest multiple of
|
||||
// kSizeDummyEntry that is greater than or equal to new_mem_used
|
||||
//
|
||||
// Insert dummy entries if new_memory_used > cache_allocated_size_;
|
||||
//
|
||||
// Release dummy entries if new_memory_used < cache_allocated_size_
|
||||
// (and new_memory_used < cache_allocated_size_ * 3/4
|
||||
// when delayed_decrease is set true);
|
||||
//
|
||||
// Keey dummy entries the same if (1) new_memory_used == cache_allocated_size_
|
||||
// or (2) new_memory_used is in the interval of
|
||||
// [cache_allocated_size_ * 3/4, cache_allocated_size) when delayed_decrease
|
||||
// is set true.
|
||||
//
|
||||
// @param new_memory_used The number of bytes used by new memory
|
||||
// @return On inserting dummy entries, it returns Status::OK() if all dummy
|
||||
// entry insertions succeed. Otherwise, it returns the first non-ok status;
|
||||
// On releasing dummy entries, it always returns Status::OK().
|
||||
// On keeping dummy entries the same, it always returns Status::OK().
|
||||
Status UpdateCacheReservation(std::size_t new_memory_used);
|
||||
std::size_t GetTotalReservedCacheSize();
|
||||
|
||||
static constexpr std::size_t GetDummyEntrySize() { return kSizeDummyEntry; }
|
||||
|
||||
private:
|
||||
static constexpr std::size_t kSizeDummyEntry = 256 * 1024;
|
||||
// The key will be longer than keys for blocks in SST files so they won't
|
||||
// conflict.
|
||||
static const std::size_t kCacheKeyPrefixSize =
|
||||
BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length;
|
||||
|
||||
Slice GetNextCacheKey();
|
||||
template <CacheEntryRole R>
|
||||
Status IncreaseCacheReservation(std::size_t new_mem_used);
|
||||
Status DecreaseCacheReservation(std::size_t new_mem_used);
|
||||
|
||||
std::shared_ptr<Cache> cache_;
|
||||
bool delayed_decrease_;
|
||||
std::atomic<std::size_t> cache_allocated_size_;
|
||||
std::vector<Cache::Handle *> dummy_handles_;
|
||||
std::uint64_t next_cache_key_id_ = 0;
|
||||
// The non-prefix part will be updated according to the ID to use.
|
||||
char cache_key_[kCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+415
@@ -0,0 +1,415 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
// Copyright (c) 2011 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#include "cache/cache_reservation_manager.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "table/block_based/block_based_table_reader.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/coding.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class CacheReservationManagerTest : public ::testing::Test {
|
||||
protected:
|
||||
static constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
static constexpr int kNumShardBits = 0; // 2^0 shard
|
||||
|
||||
static constexpr std::size_t kSizeDummyEntry =
|
||||
CacheReservationManager::GetDummyEntrySize();
|
||||
static const std::size_t kCacheKeyPrefixSize =
|
||||
BlockBasedTable::kMaxCacheKeyPrefixSize + kMaxVarint64Length;
|
||||
static constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(kOneGigabyte, kNumShardBits);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng;
|
||||
|
||||
CacheReservationManagerTest() {
|
||||
test_cache_rev_mng.reset(new CacheReservationManager(cache));
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CacheReservationManagerTest, GenerateCacheKey) {
|
||||
// The first cache reservation manager owning the cache will have
|
||||
// cache->NewId() = 1
|
||||
constexpr std::size_t kCacheNewId = 1;
|
||||
// The first key generated inside of cache reservation manager will have
|
||||
// next_cache_key_id = 0
|
||||
constexpr std::size_t kCacheKeyId = 0;
|
||||
|
||||
char expected_cache_key[kCacheKeyPrefixSize + kMaxVarint64Length];
|
||||
std::memset(expected_cache_key, 0, kCacheKeyPrefixSize + kMaxVarint64Length);
|
||||
|
||||
EncodeVarint64(expected_cache_key, kCacheNewId);
|
||||
char* end =
|
||||
EncodeVarint64(expected_cache_key + kCacheKeyPrefixSize, kCacheKeyId);
|
||||
Slice expected_cache_key_slice(
|
||||
expected_cache_key, static_cast<std::size_t>(end - expected_cache_key));
|
||||
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
Cache::Handle* handle = cache->Lookup(expected_cache_key_slice);
|
||||
EXPECT_NE(handle, nullptr)
|
||||
<< "Failed to generate the cache key for the dummy entry correctly";
|
||||
// Clean up the returned handle from Lookup() to prevent memory leak
|
||||
cache->Release(handle);
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest, KeepCacheReservationTheSame) {
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry);
|
||||
std::size_t initial_pinned_usage = cache->GetPinnedUsage();
|
||||
ASSERT_GE(initial_pinned_usage, 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(initial_pinned_usage,
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to keep cache reservation the same when new_mem_used equals "
|
||||
"to current cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when new_mem_used equals to current "
|
||||
"cache reservation";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to keep underlying dummy entries the same when new_mem_used "
|
||||
"equals to current cache reservation";
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
IncreaseCacheReservationByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
IncreaseCacheReservationNotByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry + kSizeDummyEntry / 2;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
3 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 3 * kSizeDummyEntry)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
3 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to increase underlying dummy entries in cache correctly";
|
||||
}
|
||||
|
||||
TEST(CacheReservationManagerIncreaseReservcationOnFullCacheTest,
|
||||
IncreaseCacheReservationOnFullCache) {
|
||||
constexpr std::size_t kOneMegabyte = 1024 * 1024;
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry =
|
||||
CacheReservationManager::GetDummyEntrySize();
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kOneMegabyte;
|
||||
lo.num_shard_bits = 0; // 2^0 shard
|
||||
lo.strict_capacity_limit = true;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache));
|
||||
|
||||
std::size_t new_mem_used = kOneMegabyte + 1;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::Incomplete())
|
||||
<< "Failed to return status to indicate failure of dummy entry insertion "
|
||||
"during cache reservation on full cache";
|
||||
EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly before cache resevation failure happens "
|
||||
"due to full cache";
|
||||
EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), kOneMegabyte)
|
||||
<< "Failed to bookkeep correctly (i.e, bookkeep only successful dummy "
|
||||
"entry insertions) when encountering cache resevation failure due to "
|
||||
"full cache";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
EXPECT_LE(cache->GetPinnedUsage(), kOneMegabyte)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
|
||||
new_mem_used = kOneMegabyte / 2; // 2 dummy entries
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation after encountering cache "
|
||||
"reservation failure due to full cache";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly after "
|
||||
"encountering cache reservation due to full cache";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry)
|
||||
<< "Failed to release underlying dummy entries correctly on cache "
|
||||
"reservation decrease after encountering cache resevation failure due "
|
||||
"to full cache";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to release underlying dummy entries correctly on cache "
|
||||
"reservation decrease after encountering cache resevation failure due "
|
||||
"to full cache";
|
||||
|
||||
// Create cache full again for subsequent tests
|
||||
new_mem_used = kOneMegabyte + 1;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::Incomplete())
|
||||
<< "Failed to return status to indicate failure of dummy entry insertion "
|
||||
"during cache reservation on full cache";
|
||||
EXPECT_GE(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly before cache resevation failure happens "
|
||||
"due to full cache";
|
||||
EXPECT_LE(test_cache_rev_mng->GetTotalReservedCacheSize(), kOneMegabyte)
|
||||
<< "Failed to bookkeep correctly (i.e, bookkeep only successful dummy "
|
||||
"entry insertions) when encountering cache resevation failure due to "
|
||||
"full cache";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
EXPECT_LE(cache->GetPinnedUsage(), kOneMegabyte)
|
||||
<< "Failed to insert underlying dummy entries correctly when "
|
||||
"encountering cache resevation failure due to full cache";
|
||||
|
||||
// Increase cache capacity so the previously failed insertion can fully
|
||||
// succeed
|
||||
cache->SetCapacity(kOneGigabyte);
|
||||
new_mem_used = kOneMegabyte + 1;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to increase cache reservation after increasing cache capacity "
|
||||
"and mitigating cache full error";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
5 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation increase correctly after "
|
||||
"increasing cache capacity and mitigating cache full error";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 5 * kSizeDummyEntry)
|
||||
<< "Failed to insert underlying dummy entries correctly after increasing "
|
||||
"cache capacity and mitigating cache full error";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
5 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to insert underlying dummy entries correctly after increasing "
|
||||
"cache capacity and mitigating cache full error";
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
DecreaseCacheReservationByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry);
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = 1 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
}
|
||||
|
||||
TEST_F(CacheReservationManagerTest,
|
||||
DecreaseCacheReservationNotByMultiplesOfDummyEntrySize) {
|
||||
std::size_t new_mem_used = 2 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
2 * kSizeDummyEntry);
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 2 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
2 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = kSizeDummyEntry / 2;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
1 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep cache reservation decrease correctly";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to decrease underlying dummy entries in cache correctly";
|
||||
}
|
||||
|
||||
TEST(CacheReservationManagerWithDelayedDecreaseTest,
|
||||
DecreaseCacheReservationWithDelayedDecrease) {
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry =
|
||||
CacheReservationManager::GetDummyEntrySize();
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kOneGigabyte;
|
||||
lo.num_shard_bits = 0;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache, true /* delayed_decrease */));
|
||||
|
||||
std::size_t new_mem_used = 8 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry);
|
||||
std::size_t initial_pinned_usage = cache->GetPinnedUsage();
|
||||
ASSERT_GE(initial_pinned_usage, 8 * kSizeDummyEntry);
|
||||
ASSERT_LT(initial_pinned_usage,
|
||||
8 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
|
||||
new_mem_used = 6 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK()) << "Failed to delay decreasing cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when delaying cache reservation "
|
||||
"decrease";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to delay decreasing underlying dummy entries in cache";
|
||||
|
||||
new_mem_used = 7 * kSizeDummyEntry;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK()) << "Failed to delay decreasing cache reservation";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
8 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when delaying cache reservation "
|
||||
"decrease";
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), initial_pinned_usage)
|
||||
<< "Failed to delay decreasing underlying dummy entries in cache";
|
||||
|
||||
new_mem_used = 6 * kSizeDummyEntry - 1;
|
||||
s = test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
EXPECT_EQ(s, Status::OK())
|
||||
<< "Failed to decrease cache reservation correctly when new_mem_used < "
|
||||
"GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode";
|
||||
EXPECT_EQ(test_cache_rev_mng->GetTotalReservedCacheSize(),
|
||||
6 * kSizeDummyEntry)
|
||||
<< "Failed to bookkeep correctly when new_mem_used < "
|
||||
"GetTotalReservedCacheSize() * 3 / 4 on delayed decrease mode";
|
||||
EXPECT_GE(cache->GetPinnedUsage(), 6 * kSizeDummyEntry)
|
||||
<< "Failed to decrease underlying dummy entries in cache when "
|
||||
"new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed "
|
||||
"decrease mode";
|
||||
EXPECT_LT(cache->GetPinnedUsage(),
|
||||
6 * kSizeDummyEntry + kMetaDataChargeOverhead)
|
||||
<< "Failed to decrease underlying dummy entries in cache when "
|
||||
"new_mem_used < GetTotalReservedCacheSize() * 3 / 4 on delayed "
|
||||
"decrease mode";
|
||||
}
|
||||
|
||||
TEST(CacheReservationManagerDestructorTest,
|
||||
ReleaseRemainingDummyEntriesOnDestruction) {
|
||||
constexpr std::size_t kOneGigabyte = 1024 * 1024 * 1024;
|
||||
constexpr std::size_t kSizeDummyEntry =
|
||||
CacheReservationManager::GetDummyEntrySize();
|
||||
constexpr std::size_t kMetaDataChargeOverhead = 10000;
|
||||
|
||||
LRUCacheOptions lo;
|
||||
lo.capacity = kOneGigabyte;
|
||||
lo.num_shard_bits = 0;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(lo);
|
||||
{
|
||||
std::unique_ptr<CacheReservationManager> test_cache_rev_mng(
|
||||
new CacheReservationManager(cache));
|
||||
std::size_t new_mem_used = 1 * kSizeDummyEntry;
|
||||
Status s =
|
||||
test_cache_rev_mng
|
||||
->UpdateCacheReservation<ROCKSDB_NAMESPACE::CacheEntryRole::kMisc>(
|
||||
new_mem_used);
|
||||
ASSERT_EQ(s, Status::OK());
|
||||
ASSERT_GE(cache->GetPinnedUsage(), 1 * kSizeDummyEntry);
|
||||
ASSERT_LT(cache->GetPinnedUsage(),
|
||||
1 * kSizeDummyEntry + kMetaDataChargeOverhead);
|
||||
}
|
||||
EXPECT_EQ(cache->GetPinnedUsage(), 0 * kSizeDummyEntry)
|
||||
<< "Failed to release remaining underlying dummy entries in cache in "
|
||||
"CacheReservationManager's destructor";
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
Vendored
+3
-2
@@ -260,7 +260,7 @@ struct CleanupContext {
|
||||
class ClockCacheShard final : public CacheShard {
|
||||
public:
|
||||
// Hash map type.
|
||||
typedef tbb::concurrent_hash_map<CacheKey, CacheHandle*, CacheKey> HashTable;
|
||||
using HashTable = tbb::concurrent_hash_map<CacheKey, CacheHandle*, CacheKey>;
|
||||
|
||||
ClockCacheShard();
|
||||
~ClockCacheShard() override;
|
||||
@@ -280,7 +280,8 @@ class ClockCacheShard final : public CacheShard {
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
const Cache::CreateCallback& /*create_cb*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/) override {
|
||||
Cache::Priority /*priority*/, bool /*wait*/,
|
||||
Statistics* /*stats*/) override {
|
||||
return Lookup(key, hash);
|
||||
}
|
||||
bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
|
||||
Vendored
+123
-28
@@ -13,6 +13,8 @@
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include "monitoring/perf_context_imp.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -309,7 +311,8 @@ void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle,
|
||||
bool free_handle_on_fail) {
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
@@ -323,14 +326,16 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
e->SetInCache(false);
|
||||
if (handle == nullptr) {
|
||||
// Don't insert the entry but still return ok, as if the entry inserted
|
||||
// into cache and get evicted immediately.
|
||||
e->SetInCache(false);
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
*handle = nullptr;
|
||||
if (free_handle_on_fail) {
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
*handle = nullptr;
|
||||
}
|
||||
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
@@ -355,7 +360,10 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(e);
|
||||
} else {
|
||||
e->Ref();
|
||||
// If caller already holds a ref, no need to take one here
|
||||
if (!e->HasRefs()) {
|
||||
e->Ref();
|
||||
}
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
}
|
||||
@@ -375,11 +383,43 @@ Status LRUCacheShard::InsertItem(LRUHandle* e, Cache::Handle** handle) {
|
||||
return s;
|
||||
}
|
||||
|
||||
void LRUCacheShard::Promote(LRUHandle* e) {
|
||||
SecondaryCacheResultHandle* secondary_handle = e->sec_handle;
|
||||
|
||||
assert(secondary_handle->IsReady());
|
||||
e->SetIncomplete(false);
|
||||
e->SetInCache(true);
|
||||
e->SetPromoted(true);
|
||||
e->value = secondary_handle->Value();
|
||||
e->charge = secondary_handle->Size();
|
||||
delete secondary_handle;
|
||||
|
||||
// This call could fail if the cache is over capacity and
|
||||
// strict_capacity_limit_ is true. In such a case, we don't want
|
||||
// InsertItem() to free the handle, since the item is already in memory
|
||||
// and the caller will most likely just read from disk if we erase it here.
|
||||
if (e->value) {
|
||||
Cache::Handle* handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
Status s = InsertItem(e, &handle, /*free_handle_on_fail=*/false);
|
||||
if (!s.ok()) {
|
||||
// Item is in memory, but not accounted against the cache capacity.
|
||||
// When the handle is released, the item should get deleted
|
||||
assert(!e->InCache());
|
||||
}
|
||||
} else {
|
||||
// Since the secondary cache lookup failed, mark the item as not in cache
|
||||
// Don't charge the cache as its only metadata that'll shortly be released
|
||||
MutexLock l(&mutex_);
|
||||
e->charge = 0;
|
||||
e->SetInCache(false);
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCacheShard::Lookup(
|
||||
const Slice& key, uint32_t hash,
|
||||
const ShardedCache::CacheItemHelper* helper,
|
||||
const ShardedCache::CreateCallback& create_cb, Cache::Priority priority,
|
||||
bool wait) {
|
||||
bool wait, Statistics* stats) {
|
||||
LRUHandle* e = nullptr;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
@@ -399,47 +439,51 @@ Cache::Handle* LRUCacheShard::Lookup(
|
||||
// mutex if we're going to lookup in the secondary cache
|
||||
// Only support synchronous for now
|
||||
// TODO: Support asynchronous lookup in secondary cache
|
||||
if (!e && secondary_cache_ && helper && helper->saveto_cb && wait) {
|
||||
if (!e && secondary_cache_ && helper && helper->saveto_cb) {
|
||||
// For objects from the secondary cache, we expect the caller to provide
|
||||
// a way to create/delete the primary cache object. The only case where
|
||||
// a deleter would not be required is for dummy entries inserted for
|
||||
// accounting purposes, which we won't demote to the secondary cache
|
||||
// anyway.
|
||||
assert(create_cb && helper->del_cb);
|
||||
std::unique_ptr<SecondaryCacheHandle> secondary_handle =
|
||||
std::unique_ptr<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(key, create_cb, wait);
|
||||
if (secondary_handle != nullptr) {
|
||||
void* value = nullptr;
|
||||
e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
|
||||
e->flags = 0;
|
||||
e->SetPromoted(true);
|
||||
e->SetSecondaryCacheCompatible(true);
|
||||
e->info_.helper = helper;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetInCache(true);
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->value = nullptr;
|
||||
e->sec_handle = secondary_handle.release();
|
||||
e->Ref();
|
||||
|
||||
value = secondary_handle->Value();
|
||||
e->value = value;
|
||||
e->charge = secondary_handle->Size();
|
||||
|
||||
// This call could nullify e if the cache is over capacity and
|
||||
// strict_capacity_limit_ is true. In such a case, the caller will try
|
||||
// to insert later, which might again fail, but its ok as this should
|
||||
// not be common
|
||||
// Being conservative here since there could be lookups that are
|
||||
// actually ok to fail rather than succeed and bloat up the memory
|
||||
// usage (preloading partitioned index blocks, for example).
|
||||
Status s = InsertItem(e, reinterpret_cast<Cache::Handle**>(&e));
|
||||
if (!s.ok()) {
|
||||
assert(e == nullptr);
|
||||
(*helper->del_cb)(key, value);
|
||||
if (wait) {
|
||||
Promote(e);
|
||||
if (!e->value) {
|
||||
// The secondary cache returned a handle, but the lookup failed
|
||||
e->Unref();
|
||||
e->Free();
|
||||
e = nullptr;
|
||||
} else {
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
}
|
||||
} else {
|
||||
// If wait is false, we always return a handle and let the caller
|
||||
// release the handle after checking for success or failure
|
||||
e->SetIncomplete(true);
|
||||
// This may be slightly inaccurate, if the lookup eventually fails.
|
||||
// But the probability is very low.
|
||||
PERF_COUNTER_ADD(secondary_cache_hit_count, 1);
|
||||
RecordTick(stats, SECONDARY_CACHE_HITS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -485,7 +529,12 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
|
||||
last_reference = false;
|
||||
}
|
||||
}
|
||||
if (last_reference) {
|
||||
// If it was the last reference, and the entry is either not secondary
|
||||
// cache compatible (i.e a dummy entry for accounting), or is secondary
|
||||
// cache compatible and has a non-null value, then decrement the cache
|
||||
// usage. If value is null in the latter case, taht means the lookup
|
||||
// failed and we didn't charge the cache.
|
||||
if (last_reference && (!e->IsSecondaryCacheCompatible() || e->value)) {
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= total_charge);
|
||||
usage_ -= total_charge;
|
||||
@@ -516,6 +565,9 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->SetSecondaryCacheCompatible(true);
|
||||
e->info_.helper = helper;
|
||||
} else {
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
e->is_secondary_cache_compatible_for_tsan = false;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
e->info_.deleter = deleter;
|
||||
}
|
||||
e->charge = charge;
|
||||
@@ -527,7 +579,7 @@ Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
|
||||
return InsertItem(e, handle);
|
||||
return InsertItem(e, handle, /* free_handle_on_fail */ true);
|
||||
}
|
||||
|
||||
void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
@@ -557,6 +609,18 @@ void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
}
|
||||
}
|
||||
|
||||
bool LRUCacheShard::IsReady(Cache::Handle* handle) {
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(handle);
|
||||
MutexLock l(&mutex_);
|
||||
bool ready = true;
|
||||
if (e->IsPending()) {
|
||||
assert(secondary_cache_);
|
||||
assert(e->sec_handle);
|
||||
ready = e->sec_handle->IsReady();
|
||||
}
|
||||
return ready;
|
||||
}
|
||||
|
||||
size_t LRUCacheShard::GetUsage() const {
|
||||
MutexLock l(&mutex_);
|
||||
return usage_;
|
||||
@@ -597,6 +661,7 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
use_adaptive_mutex, metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - num_shard_bits, secondary_cache);
|
||||
}
|
||||
secondary_cache_ = secondary_cache;
|
||||
}
|
||||
|
||||
LRUCache::~LRUCache() {
|
||||
@@ -662,6 +727,36 @@ double LRUCache::GetHighPriPoolRatio() {
|
||||
return result;
|
||||
}
|
||||
|
||||
void LRUCache::WaitAll(std::vector<Handle*>& handles) {
|
||||
if (secondary_cache_) {
|
||||
std::vector<SecondaryCacheResultHandle*> sec_handles;
|
||||
sec_handles.reserve(handles.size());
|
||||
for (Handle* handle : handles) {
|
||||
if (!handle) {
|
||||
continue;
|
||||
}
|
||||
LRUHandle* lru_handle = reinterpret_cast<LRUHandle*>(handle);
|
||||
if (!lru_handle->IsPending()) {
|
||||
continue;
|
||||
}
|
||||
sec_handles.emplace_back(lru_handle->sec_handle);
|
||||
}
|
||||
secondary_cache_->WaitAll(sec_handles);
|
||||
for (Handle* handle : handles) {
|
||||
if (!handle) {
|
||||
continue;
|
||||
}
|
||||
LRUHandle* lru_handle = reinterpret_cast<LRUHandle*>(handle);
|
||||
if (!lru_handle->IsPending()) {
|
||||
continue;
|
||||
}
|
||||
uint32_t hash = GetHash(handle);
|
||||
LRUCacheShard* shard = static_cast<LRUCacheShard*>(GetShard(Shard(hash)));
|
||||
shard->Promote(lru_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
|
||||
Vendored
+65
-15
@@ -12,6 +12,7 @@
|
||||
#include <string>
|
||||
|
||||
#include "cache/sharded_cache.h"
|
||||
#include "port/lang.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
@@ -56,7 +57,12 @@ struct LRUHandle {
|
||||
Cache::DeleterFn deleter;
|
||||
const ShardedCache::CacheItemHelper* helper;
|
||||
} info_;
|
||||
LRUHandle* next_hash;
|
||||
// An entry is not added to the LRUHandleTable until the secondary cache
|
||||
// lookup is complete, so its safe to have this union.
|
||||
union {
|
||||
LRUHandle* next_hash;
|
||||
SecondaryCacheResultHandle* sec_handle;
|
||||
};
|
||||
LRUHandle* next;
|
||||
LRUHandle* prev;
|
||||
size_t charge; // TODO(opt): Only allow uint32_t?
|
||||
@@ -75,8 +81,8 @@ struct LRUHandle {
|
||||
IN_HIGH_PRI_POOL = (1 << 2),
|
||||
// Whether this entry has had any lookups (hits).
|
||||
HAS_HIT = (1 << 3),
|
||||
// Can this be inserted into the tiered cache
|
||||
IS_TIERED_CACHE_COMPATIBLE = (1 << 4),
|
||||
// Can this be inserted into the secondary cache
|
||||
IS_SECONDARY_CACHE_COMPATIBLE = (1 << 4),
|
||||
// Is the handle still being read from a lower tier
|
||||
IS_PENDING = (1 << 5),
|
||||
// Has the item been promoted from a lower tier
|
||||
@@ -85,6 +91,14 @@ struct LRUHandle {
|
||||
|
||||
uint8_t flags;
|
||||
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
// TSAN can report a false data race on flags, where one thread is writing
|
||||
// to one of the mutable bits and another thread is reading this immutable
|
||||
// bit. So precisely suppress that TSAN warning, we separate out this bit
|
||||
// during TSAN runs.
|
||||
bool is_secondary_cache_compatible_for_tsan;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
|
||||
// Beginning of the key (MUST BE THE LAST FIELD IN THIS STRUCT!)
|
||||
char key_data[1];
|
||||
|
||||
@@ -108,7 +122,11 @@ struct LRUHandle {
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
bool HasHit() const { return flags & HAS_HIT; }
|
||||
bool IsSecondaryCacheCompatible() const {
|
||||
return flags & IS_TIERED_CACHE_COMPATIBLE;
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
return is_secondary_cache_compatible_for_tsan;
|
||||
#else
|
||||
return flags & IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
}
|
||||
bool IsPending() const { return flags & IS_PENDING; }
|
||||
bool IsPromoted() const { return flags & IS_PROMOTED; }
|
||||
@@ -139,12 +157,15 @@ struct LRUHandle {
|
||||
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void SetSecondaryCacheCompatible(bool tiered) {
|
||||
if (tiered) {
|
||||
flags |= IS_TIERED_CACHE_COMPATIBLE;
|
||||
void SetSecondaryCacheCompatible(bool compat) {
|
||||
if (compat) {
|
||||
flags |= IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
} else {
|
||||
flags &= ~IS_TIERED_CACHE_COMPATIBLE;
|
||||
flags &= ~IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
}
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
is_secondary_cache_compatible_for_tsan = compat;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
}
|
||||
|
||||
void SetIncomplete(bool incomp) {
|
||||
@@ -165,10 +186,24 @@ struct LRUHandle {
|
||||
|
||||
void Free() {
|
||||
assert(refs == 0);
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
// Here we can safely assert they are the same without a data race reported
|
||||
assert(((flags & IS_SECONDARY_CACHE_COMPATIBLE) != 0) ==
|
||||
is_secondary_cache_compatible_for_tsan);
|
||||
#endif // __SANITIZE_THREAD__
|
||||
if (!IsSecondaryCacheCompatible() && info_.deleter) {
|
||||
(*info_.deleter)(key(), value);
|
||||
} else if (IsSecondaryCacheCompatible()) {
|
||||
(*info_.helper->del_cb)(key(), value);
|
||||
if (IsPending()) {
|
||||
assert(sec_handle != nullptr);
|
||||
SecondaryCacheResultHandle* tmp_sec_handle = sec_handle;
|
||||
tmp_sec_handle->Wait();
|
||||
value = tmp_sec_handle->Value();
|
||||
delete tmp_sec_handle;
|
||||
}
|
||||
if (value) {
|
||||
(*info_.helper->del_cb)(key(), value);
|
||||
}
|
||||
}
|
||||
delete[] reinterpret_cast<char*>(this);
|
||||
}
|
||||
@@ -284,16 +319,17 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
const ShardedCache::CacheItemHelper* helper,
|
||||
const ShardedCache::CreateCallback& create_cb,
|
||||
ShardedCache::Priority priority,
|
||||
bool wait) override;
|
||||
ShardedCache::Priority priority, bool wait,
|
||||
Statistics* stats) override;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override {
|
||||
return Lookup(key, hash, nullptr, nullptr, Cache::Priority::LOW, true);
|
||||
return Lookup(key, hash, nullptr, nullptr, Cache::Priority::LOW, true,
|
||||
nullptr);
|
||||
}
|
||||
virtual bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
virtual bool IsReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
virtual bool IsReady(Cache::Handle* /*handle*/) override;
|
||||
virtual void Wait(Cache::Handle* /*handle*/) override {}
|
||||
virtual bool Ref(Cache::Handle* handle) override;
|
||||
virtual bool Release(Cache::Handle* handle,
|
||||
@@ -326,10 +362,23 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
Status InsertItem(LRUHandle* item, Cache::Handle** handle);
|
||||
friend class LRUCache;
|
||||
// Insert an item into the hash table and, if handle is null, insert into
|
||||
// the LRU list. Older items are evicted as necessary. If the cache is full
|
||||
// and free_handle_on_fail is true, the item is deleted and handle is set to.
|
||||
Status InsertItem(LRUHandle* item, Cache::Handle** handle,
|
||||
bool free_handle_on_fail);
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
DeleterFn deleter, const Cache::CacheItemHelper* helper,
|
||||
Cache::Handle** handle, Cache::Priority priority);
|
||||
// Promote an item looked up from the secondary cache to the LRU cache. The
|
||||
// item is only inserted into the hash table and not the LRU list, and only
|
||||
// if the cache is not at full capacity, as is the case during Insert. The
|
||||
// caller should hold a reference on the LRUHandle. When the caller releases
|
||||
// the last reference, the item is added to the LRU list.
|
||||
// The item is promoted to the high pri or low pri pool as specified by the
|
||||
// caller in Lookup.
|
||||
void Promote(LRUHandle* e);
|
||||
void LRU_Remove(LRUHandle* e);
|
||||
void LRU_Insert(LRUHandle* e);
|
||||
|
||||
@@ -416,7 +465,7 @@ class LRUCache
|
||||
virtual uint32_t GetHash(Handle* handle) const override;
|
||||
virtual DeleterFn GetDeleter(Handle* handle) const override;
|
||||
virtual void DisownData() override;
|
||||
virtual void WaitAll(std::vector<Handle*>& /*handles*/) override {}
|
||||
virtual void WaitAll(std::vector<Handle*>& handles) override;
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only
|
||||
size_t TEST_GetLRUSize();
|
||||
@@ -426,6 +475,7 @@ class LRUCache
|
||||
private:
|
||||
LRUCacheShard* shards_ = nullptr;
|
||||
int num_shards_ = 0;
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+952
-38
File diff suppressed because it is too large.
Load diff
Vendored
+2
-2
@@ -83,10 +83,10 @@ Cache::Handle* ShardedCache::Lookup(const Slice& key,
|
||||
const CacheItemHelper* helper,
|
||||
const CreateCallback& create_cb,
|
||||
Priority priority, bool wait,
|
||||
Statistics* /*stats*/) {
|
||||
Statistics* stats) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))
|
||||
->Lookup(key, hash, helper, create_cb, priority, wait);
|
||||
->Lookup(key, hash, helper, create_cb, priority, wait, stats);
|
||||
}
|
||||
|
||||
bool ShardedCache::IsReady(Handle* handle) {
|
||||
|
||||
Vendored
+4
-2
@@ -34,7 +34,8 @@ class CacheShard {
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* helper,
|
||||
const Cache::CreateCallback& create_cb,
|
||||
Cache::Priority priority, bool wait) = 0;
|
||||
Cache::Priority priority, bool wait,
|
||||
Statistics* stats) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool useful,
|
||||
bool force_erase) = 0;
|
||||
virtual bool IsReady(Cache::Handle* handle) = 0;
|
||||
@@ -115,9 +116,10 @@ class ShardedCache : public Cache {
|
||||
int GetNumShardBits() const;
|
||||
uint32_t GetNumShards() const;
|
||||
|
||||
private:
|
||||
protected:
|
||||
inline uint32_t Shard(uint32_t hash) { return hash & shard_mask_; }
|
||||
|
||||
private:
|
||||
const uint32_t shard_mask_;
|
||||
mutable port::Mutex capacity_mutex_;
|
||||
size_t capacity_;
|
||||
|
||||
@@ -24,7 +24,7 @@ mkdir -p $COVERAGE_DIR
|
||||
|
||||
# Find all gcno files to generate the coverage report
|
||||
|
||||
PYTHON=${1:-`which python`}
|
||||
PYTHON=${1:-`which python3`}
|
||||
echo -e "Using $PYTHON"
|
||||
GCNO_FILES=`find $ROOT -name "*.gcno"`
|
||||
$GCOV --preserve-paths --relative-only --no-output $GCNO_FILES 2>/dev/null |
|
||||
|
||||
@@ -31,16 +31,15 @@ Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
|
||||
}
|
||||
|
||||
void ArenaWrappedDBIter::Init(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iteration,
|
||||
uint64_t version_number, ReadCallback* read_callback, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, bool expose_blob_index, bool allow_refresh) {
|
||||
auto mem = arena_.AllocateAligned(sizeof(DBIter));
|
||||
db_iter_ =
|
||||
new (mem) DBIter(env, read_options, cf_options, mutable_cf_options,
|
||||
cf_options.user_comparator, /* iter */ nullptr, version,
|
||||
new (mem) DBIter(env, read_options, ioptions, mutable_cf_options,
|
||||
ioptions.user_comparator, /* iter */ nullptr, version,
|
||||
sequence, true, max_sequential_skip_in_iteration,
|
||||
read_callback, db_impl, cfd, expose_blob_index);
|
||||
sv_number_ = version_number;
|
||||
@@ -88,16 +87,15 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
|
||||
uint64_t version_number, ReadCallback* read_callback, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, bool expose_blob_index, bool allow_refresh) {
|
||||
ArenaWrappedDBIter* iter = new ArenaWrappedDBIter();
|
||||
iter->Init(env, read_options, cf_options, mutable_cf_options, version,
|
||||
sequence, max_sequential_skip_in_iterations, version_number,
|
||||
read_callback, db_impl, cfd, expose_blob_index, allow_refresh);
|
||||
iter->Init(env, read_options, ioptions, mutable_cf_options, version, sequence,
|
||||
max_sequential_skip_in_iterations, version_number, read_callback,
|
||||
db_impl, cfd, expose_blob_index, allow_refresh);
|
||||
if (db_impl != nullptr && cfd != nullptr && allow_refresh) {
|
||||
iter->StoreRefreshInfo(db_impl, cfd, read_callback, expose_blob_index);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <string>
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "memory/arena.h"
|
||||
#include "options/cf_options.h"
|
||||
@@ -34,7 +33,13 @@ class Version;
|
||||
// the same as the inner DBIter.
|
||||
class ArenaWrappedDBIter : public Iterator {
|
||||
public:
|
||||
virtual ~ArenaWrappedDBIter() { db_iter_->~DBIter(); }
|
||||
~ArenaWrappedDBIter() override {
|
||||
if (db_iter_ != nullptr) {
|
||||
db_iter_->~DBIter();
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Get the arena to be used to allocate memory for DBIter to be wrapped,
|
||||
// as well as child iterators in it.
|
||||
@@ -72,7 +77,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
Status Refresh() override;
|
||||
|
||||
void Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
@@ -90,7 +95,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
}
|
||||
|
||||
private:
|
||||
DBIter* db_iter_;
|
||||
DBIter* db_iter_ = nullptr;
|
||||
Arena arena_;
|
||||
uint64_t sv_number_;
|
||||
ColumnFamilyData* cfd_ = nullptr;
|
||||
@@ -105,8 +110,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
// `db_impl` and `cfd` are used for reneweal. If left null, renewal will not
|
||||
// be supported.
|
||||
extern ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableOptions& cf_options,
|
||||
Env* env, const ReadOptions& read_options, const ImmutableOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const Version* version,
|
||||
const SequenceNumber& sequence, uint64_t max_sequential_skip_in_iterations,
|
||||
uint64_t version_number, ReadCallback* read_callback,
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "test_util/sync_point.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// An internal iterator that passes each key-value encountered to
|
||||
// BlobGarbageMeter as inflow in order to measure the total number and size of
|
||||
// blobs in the compaction input on a per-blob file basis.
|
||||
class BlobCountingIterator : public InternalIterator {
|
||||
public:
|
||||
BlobCountingIterator(InternalIterator* iter,
|
||||
BlobGarbageMeter* blob_garbage_meter)
|
||||
: iter_(iter), blob_garbage_meter_(blob_garbage_meter) {
|
||||
assert(iter_);
|
||||
assert(blob_garbage_meter_);
|
||||
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
bool Valid() const override { return iter_->Valid() && status_.ok(); }
|
||||
|
||||
void SeekToFirst() override {
|
||||
iter_->SeekToFirst();
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
void SeekToLast() override {
|
||||
iter_->SeekToLast();
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
void Seek(const Slice& target) override {
|
||||
iter_->Seek(target);
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
void SeekForPrev(const Slice& target) override {
|
||||
iter_->SeekForPrev(target);
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
void Next() override {
|
||||
assert(Valid());
|
||||
|
||||
iter_->Next();
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
bool NextAndGetResult(IterateResult* result) override {
|
||||
assert(Valid());
|
||||
|
||||
const bool res = iter_->NextAndGetResult(result);
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
return res;
|
||||
}
|
||||
|
||||
void Prev() override {
|
||||
assert(Valid());
|
||||
|
||||
iter_->Prev();
|
||||
UpdateAndCountBlobIfNeeded();
|
||||
}
|
||||
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return iter_->key();
|
||||
}
|
||||
|
||||
Slice user_key() const override {
|
||||
assert(Valid());
|
||||
return iter_->user_key();
|
||||
}
|
||||
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
return iter_->value();
|
||||
}
|
||||
|
||||
Status status() const override { return status_; }
|
||||
|
||||
bool PrepareValue() override {
|
||||
assert(Valid());
|
||||
return iter_->PrepareValue();
|
||||
}
|
||||
|
||||
bool MayBeOutOfLowerBound() override {
|
||||
assert(Valid());
|
||||
return iter_->MayBeOutOfLowerBound();
|
||||
}
|
||||
|
||||
IterBoundCheck UpperBoundCheckResult() override {
|
||||
assert(Valid());
|
||||
return iter_->UpperBoundCheckResult();
|
||||
}
|
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
iter_->SetPinnedItersMgr(pinned_iters_mgr);
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const override {
|
||||
assert(Valid());
|
||||
return iter_->IsKeyPinned();
|
||||
}
|
||||
|
||||
bool IsValuePinned() const override {
|
||||
assert(Valid());
|
||||
return iter_->IsValuePinned();
|
||||
}
|
||||
|
||||
Status GetProperty(std::string prop_name, std::string* prop) override {
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateAndCountBlobIfNeeded() {
|
||||
assert(!iter_->Valid() || iter_->status().ok());
|
||||
|
||||
if (!iter_->Valid()) {
|
||||
status_ = iter_->status();
|
||||
return;
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT(
|
||||
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:ProcessInFlow");
|
||||
|
||||
status_ = blob_garbage_meter_->ProcessInFlow(key(), value());
|
||||
}
|
||||
|
||||
InternalIterator* iter_;
|
||||
BlobGarbageMeter* blob_garbage_meter_;
|
||||
Status status_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,326 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_counting_iterator.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/vector_iterator.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
void CheckInFlow(const BlobGarbageMeter& blob_garbage_meter,
|
||||
uint64_t blob_file_number, uint64_t count, uint64_t bytes) {
|
||||
const auto& flows = blob_garbage_meter.flows();
|
||||
|
||||
const auto it = flows.find(blob_file_number);
|
||||
if (it == flows.end()) {
|
||||
ASSERT_EQ(count, 0);
|
||||
ASSERT_EQ(bytes, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& in = it->second.GetInFlow();
|
||||
|
||||
ASSERT_EQ(in.GetCount(), count);
|
||||
ASSERT_EQ(in.GetBytes(), bytes);
|
||||
}
|
||||
|
||||
TEST(BlobCountingIteratorTest, CountBlobs) {
|
||||
// Note: the input consists of three key-values: two are blob references to
|
||||
// different blob files, while the third one is a plain value.
|
||||
constexpr char user_key0[] = "key0";
|
||||
constexpr char user_key1[] = "key1";
|
||||
constexpr char user_key2[] = "key2";
|
||||
|
||||
const std::vector<std::string> keys{
|
||||
test::KeyStr(user_key0, 1, kTypeBlobIndex),
|
||||
test::KeyStr(user_key1, 2, kTypeBlobIndex),
|
||||
test::KeyStr(user_key2, 3, kTypeValue)};
|
||||
|
||||
constexpr uint64_t first_blob_file_number = 4;
|
||||
constexpr uint64_t first_offset = 1000;
|
||||
constexpr uint64_t first_size = 2000;
|
||||
|
||||
std::string first_blob_index;
|
||||
BlobIndex::EncodeBlob(&first_blob_index, first_blob_file_number, first_offset,
|
||||
first_size, kNoCompression);
|
||||
|
||||
constexpr uint64_t second_blob_file_number = 6;
|
||||
constexpr uint64_t second_offset = 2000;
|
||||
constexpr uint64_t second_size = 4000;
|
||||
|
||||
std::string second_blob_index;
|
||||
BlobIndex::EncodeBlob(&second_blob_index, second_blob_file_number,
|
||||
second_offset, second_size, kNoCompression);
|
||||
|
||||
const std::vector<std::string> values{first_blob_index, second_blob_index,
|
||||
"raw_value"};
|
||||
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
VectorIterator input(keys, values);
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
|
||||
|
||||
constexpr uint64_t first_expected_bytes =
|
||||
first_size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(user_key0) - 1);
|
||||
constexpr uint64_t second_expected_bytes =
|
||||
second_size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(user_key1) - 1);
|
||||
|
||||
// Call SeekToFirst and iterate forward
|
||||
blob_counter.SeekToFirst();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1,
|
||||
first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 0, 0);
|
||||
|
||||
blob_counter.Next();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1,
|
||||
first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1,
|
||||
second_expected_bytes);
|
||||
|
||||
blob_counter.Next();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1,
|
||||
first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1,
|
||||
second_expected_bytes);
|
||||
|
||||
blob_counter.Next();
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 1,
|
||||
first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1,
|
||||
second_expected_bytes);
|
||||
|
||||
// Do it again using NextAndGetResult
|
||||
blob_counter.SeekToFirst();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 1,
|
||||
second_expected_bytes);
|
||||
|
||||
{
|
||||
IterateResult result;
|
||||
ASSERT_TRUE(blob_counter.NextAndGetResult(&result));
|
||||
ASSERT_EQ(result.key, keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2,
|
||||
2 * second_expected_bytes);
|
||||
}
|
||||
|
||||
{
|
||||
IterateResult result;
|
||||
ASSERT_TRUE(blob_counter.NextAndGetResult(&result));
|
||||
ASSERT_EQ(result.key, keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2,
|
||||
2 * second_expected_bytes);
|
||||
}
|
||||
|
||||
{
|
||||
IterateResult result;
|
||||
ASSERT_FALSE(blob_counter.NextAndGetResult(&result));
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2,
|
||||
2 * second_expected_bytes);
|
||||
}
|
||||
|
||||
// Call SeekToLast and iterate backward
|
||||
blob_counter.SeekToLast();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 2,
|
||||
2 * second_expected_bytes);
|
||||
|
||||
blob_counter.Prev();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 2,
|
||||
2 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3,
|
||||
3 * second_expected_bytes);
|
||||
|
||||
blob_counter.Prev();
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 3,
|
||||
3 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3,
|
||||
3 * second_expected_bytes);
|
||||
|
||||
blob_counter.Prev();
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 3,
|
||||
3 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3,
|
||||
3 * second_expected_bytes);
|
||||
|
||||
// Call Seek for all keys (plus one that's greater than all of them)
|
||||
blob_counter.Seek(keys[0]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 3,
|
||||
3 * second_expected_bytes);
|
||||
|
||||
blob_counter.Seek(keys[1]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
blob_counter.Seek(keys[2]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
blob_counter.Seek("zzz");
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
// Call SeekForPrev for all keys (plus one that's less than all of them)
|
||||
blob_counter.SeekForPrev("aaa");
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 4,
|
||||
4 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
blob_counter.SeekForPrev(keys[0]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[0]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key0);
|
||||
ASSERT_EQ(blob_counter.value(), values[0]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5,
|
||||
5 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 4,
|
||||
4 * second_expected_bytes);
|
||||
|
||||
blob_counter.SeekForPrev(keys[1]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[1]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key1);
|
||||
ASSERT_EQ(blob_counter.value(), values[1]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5,
|
||||
5 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 5,
|
||||
5 * second_expected_bytes);
|
||||
|
||||
blob_counter.SeekForPrev(keys[2]);
|
||||
ASSERT_TRUE(blob_counter.Valid());
|
||||
ASSERT_OK(blob_counter.status());
|
||||
ASSERT_EQ(blob_counter.key(), keys[2]);
|
||||
ASSERT_EQ(blob_counter.user_key(), user_key2);
|
||||
ASSERT_EQ(blob_counter.value(), values[2]);
|
||||
CheckInFlow(blob_garbage_meter, first_blob_file_number, 5,
|
||||
5 * first_expected_bytes);
|
||||
CheckInFlow(blob_garbage_meter, second_blob_file_number, 5,
|
||||
5 * second_expected_bytes);
|
||||
}
|
||||
|
||||
TEST(BlobCountingIteratorTest, CorruptBlobIndex) {
|
||||
const std::vector<std::string> keys{
|
||||
test::KeyStr("user_key", 1, kTypeBlobIndex)};
|
||||
const std::vector<std::string> values{"i_am_not_a_blob_index"};
|
||||
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
VectorIterator input(keys, values);
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
BlobCountingIterator blob_counter(&input, &blob_garbage_meter);
|
||||
|
||||
blob_counter.SeekToFirst();
|
||||
ASSERT_FALSE(blob_counter.Valid());
|
||||
ASSERT_NOK(blob_counter.status());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_fetcher.h"
|
||||
|
||||
#include "db/version_set.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFetcher::FetchBlob(const Slice& user_key, const Slice& blob_index,
|
||||
PinnableSlice* blob_value) {
|
||||
Status s;
|
||||
assert(version_);
|
||||
constexpr uint64_t* bytes_read = nullptr;
|
||||
s = version_->GetBlob(read_options_, user_key, blob_index, blob_value,
|
||||
bytes_read);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class Version;
|
||||
|
||||
class BlobFetcher {
|
||||
public:
|
||||
BlobFetcher(Version* version, const ReadOptions& read_options)
|
||||
: version_(version), read_options_(read_options) {}
|
||||
|
||||
Status FetchBlob(const Slice& user_key, const Slice& blob_index,
|
||||
PinnableSlice* blob_value);
|
||||
|
||||
private:
|
||||
Version* version_;
|
||||
ReadOptions read_options_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -135,7 +135,8 @@ std::ostream& operator<<(std::ostream& os,
|
||||
<< " total_blob_count: " << blob_file_addition.GetTotalBlobCount()
|
||||
<< " total_blob_bytes: " << blob_file_addition.GetTotalBlobBytes()
|
||||
<< " checksum_method: " << blob_file_addition.GetChecksumMethod()
|
||||
<< " checksum_value: " << blob_file_addition.GetChecksumValue();
|
||||
<< " checksum_value: "
|
||||
<< Slice(blob_file_addition.GetChecksumValue()).ToString(/* hex */ true);
|
||||
|
||||
return os;
|
||||
}
|
||||
@@ -146,7 +147,8 @@ JSONWriter& operator<<(JSONWriter& jw,
|
||||
<< "TotalBlobCount" << blob_file_addition.GetTotalBlobCount()
|
||||
<< "TotalBlobBytes" << blob_file_addition.GetTotalBlobBytes()
|
||||
<< "ChecksumMethod" << blob_file_addition.GetChecksumMethod()
|
||||
<< "ChecksumValue" << blob_file_addition.GetChecksumValue();
|
||||
<< "ChecksumValue"
|
||||
<< Slice(blob_file_addition.GetChecksumValue()).ToString(/* hex */ true);
|
||||
|
||||
return jw;
|
||||
}
|
||||
|
||||
@@ -46,7 +46,9 @@ TEST_F(BlobFileAdditionTest, NonEmpty) {
|
||||
constexpr uint64_t total_blob_count = 2;
|
||||
constexpr uint64_t total_blob_bytes = 123456;
|
||||
const std::string checksum_method("SHA1");
|
||||
const std::string checksum_value("bdb7f34a59dfa1592ce7f52e99f98c570c525cbd");
|
||||
const std::string checksum_value(
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
@@ -113,7 +115,9 @@ TEST_F(BlobFileAdditionTest, DecodeErrors) {
|
||||
ASSERT_TRUE(std::strstr(s.getState(), "checksum value"));
|
||||
}
|
||||
|
||||
constexpr char checksum_value[] = "bdb7f34a59dfa1592ce7f52e99f98c570c525cbd";
|
||||
constexpr char checksum_value[] =
|
||||
"\xbd\xb7\xf3\x4a\x59\xdf\xa1\x59\x2c\xe7\xf5\x2e\x99\xf9\x8c\x57\x0c\x52"
|
||||
"\x5c\xbd";
|
||||
PutLengthPrefixedSlice(&str, checksum_value);
|
||||
slice = str;
|
||||
|
||||
@@ -150,7 +154,7 @@ TEST_F(BlobFileAdditionTest, ForwardCompatibleCustomField) {
|
||||
constexpr uint64_t total_blob_count = 9999;
|
||||
constexpr uint64_t total_blob_bytes = 100000000;
|
||||
const std::string checksum_method("CRC32");
|
||||
const std::string checksum_value("3d87ff57");
|
||||
const std::string checksum_value("\x3d\x87\xff\x57");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
@@ -178,7 +182,7 @@ TEST_F(BlobFileAdditionTest, ForwardIncompatibleCustomField) {
|
||||
constexpr uint64_t total_blob_count = 100;
|
||||
constexpr uint64_t total_blob_bytes = 2000000;
|
||||
const std::string checksum_method("CRC32B");
|
||||
const std::string checksum_value("6dbdf23a");
|
||||
const std::string checksum_value("\x6d\xbd\xf2\x3a");
|
||||
|
||||
BlobFileAddition blob_file_addition(blob_file_number, total_blob_count,
|
||||
total_blob_bytes, checksum_method,
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/version_set.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/read_write_util.h"
|
||||
@@ -29,35 +30,37 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
VersionSet* versions, FileSystem* fs,
|
||||
const ImmutableOptions* immutable_cf_options,
|
||||
const ImmutableOptions* immutable_options,
|
||||
const MutableCFOptions* mutable_cf_options, const FileOptions* file_options,
|
||||
int job_id, uint32_t column_family_id,
|
||||
const std::string& column_family_name, Env::IOPriority io_priority,
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs,
|
||||
immutable_cf_options, mutable_cf_options, file_options,
|
||||
immutable_options, mutable_cf_options, file_options,
|
||||
job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, io_tracer, blob_callback, blob_file_paths,
|
||||
blob_file_additions) {}
|
||||
write_hint, io_tracer, blob_callback, creation_reason,
|
||||
blob_file_paths, blob_file_additions) {}
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
std::function<uint64_t()> file_number_generator, FileSystem* fs,
|
||||
const ImmutableOptions* immutable_cf_options,
|
||||
const ImmutableOptions* immutable_options,
|
||||
const MutableCFOptions* mutable_cf_options, const FileOptions* file_options,
|
||||
int job_id, uint32_t column_family_id,
|
||||
const std::string& column_family_name, Env::IOPriority io_priority,
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: file_number_generator_(std::move(file_number_generator)),
|
||||
fs_(fs),
|
||||
immutable_cf_options_(immutable_cf_options),
|
||||
immutable_options_(immutable_options),
|
||||
min_blob_size_(mutable_cf_options->min_blob_size),
|
||||
blob_file_size_(mutable_cf_options->blob_file_size),
|
||||
blob_compression_type_(mutable_cf_options->blob_compression_type),
|
||||
@@ -69,13 +72,14 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
write_hint_(write_hint),
|
||||
io_tracer_(io_tracer),
|
||||
blob_callback_(blob_callback),
|
||||
creation_reason_(creation_reason),
|
||||
blob_file_paths_(blob_file_paths),
|
||||
blob_file_additions_(blob_file_additions),
|
||||
blob_count_(0),
|
||||
blob_bytes_(0) {
|
||||
assert(file_number_generator_);
|
||||
assert(fs_);
|
||||
assert(immutable_cf_options_);
|
||||
assert(immutable_options_);
|
||||
assert(file_options_);
|
||||
assert(blob_file_paths_);
|
||||
assert(blob_file_paths_->empty());
|
||||
@@ -156,10 +160,15 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
assert(file_number_generator_);
|
||||
const uint64_t blob_file_number = file_number_generator_();
|
||||
|
||||
assert(immutable_cf_options_);
|
||||
assert(!immutable_cf_options_->cf_paths.empty());
|
||||
std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options_->cf_paths.front().path, blob_file_number);
|
||||
assert(immutable_options_);
|
||||
assert(!immutable_options_->cf_paths.empty());
|
||||
std::string blob_file_path =
|
||||
BlobFileName(immutable_options_->cf_paths.front().path, blob_file_number);
|
||||
|
||||
if (blob_callback_) {
|
||||
blob_callback_->OnBlobFileCreationStarted(
|
||||
blob_file_path, column_family_name_, job_id_, creation_reason_);
|
||||
}
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
|
||||
@@ -184,20 +193,20 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
assert(file);
|
||||
file->SetIOPriority(io_priority_);
|
||||
file->SetWriteLifeTimeHint(write_hint_);
|
||||
FileTypeSet tmp_set = immutable_cf_options_->checksum_handoff_file_types;
|
||||
Statistics* const statistics = immutable_cf_options_->stats;
|
||||
FileTypeSet tmp_set = immutable_options_->checksum_handoff_file_types;
|
||||
Statistics* const statistics = immutable_options_->stats;
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_paths_->back(), *file_options_,
|
||||
immutable_cf_options_->clock, io_tracer_, statistics,
|
||||
immutable_cf_options_->listeners,
|
||||
immutable_cf_options_->file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kBlobFile)));
|
||||
immutable_options_->clock, io_tracer_, statistics,
|
||||
immutable_options_->listeners,
|
||||
immutable_options_->file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kBlobFile), false));
|
||||
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
std::unique_ptr<BlobLogWriter> blob_log_writer(new BlobLogWriter(
|
||||
std::move(file_writer), immutable_cf_options_->clock, statistics,
|
||||
blob_file_number, immutable_cf_options_->use_fsync, do_flush));
|
||||
std::move(file_writer), immutable_options_->clock, statistics,
|
||||
blob_file_number, immutable_options_->use_fsync, do_flush));
|
||||
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
@@ -228,6 +237,7 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
assert(blob);
|
||||
assert(compressed_blob);
|
||||
assert(compressed_blob->empty());
|
||||
assert(immutable_options_);
|
||||
|
||||
if (blob_compression_type_ == kNoCompression) {
|
||||
return Status::OK();
|
||||
@@ -242,7 +252,16 @@ Status BlobFileBuilder::CompressBlobIfNeeded(
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
|
||||
if (!CompressData(*blob, info, compression_format_version, compressed_blob)) {
|
||||
bool success = false;
|
||||
|
||||
{
|
||||
StopWatch stop_watch(immutable_options_->clock, immutable_options_->stats,
|
||||
BLOB_DB_COMPRESSION_MICROS);
|
||||
success =
|
||||
CompressData(*blob, info, compression_format_version, compressed_blob);
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
return Status::Corruption("Error compressing blob");
|
||||
}
|
||||
|
||||
@@ -295,20 +314,24 @@ Status BlobFileBuilder::CloseBlobFile() {
|
||||
|
||||
const uint64_t blob_file_number = writer_->get_log_number();
|
||||
|
||||
if (blob_callback_) {
|
||||
s = blob_callback_->OnBlobFileCompleted(
|
||||
blob_file_paths_->back(), column_family_name_, job_id_,
|
||||
blob_file_number, creation_reason_, s, checksum_value, checksum_method,
|
||||
blob_count_, blob_bytes_);
|
||||
}
|
||||
|
||||
assert(blob_file_additions_);
|
||||
blob_file_additions_->emplace_back(blob_file_number, blob_count_, blob_bytes_,
|
||||
std::move(checksum_method),
|
||||
std::move(checksum_value));
|
||||
|
||||
assert(immutable_cf_options_);
|
||||
ROCKS_LOG_INFO(immutable_cf_options_->logger,
|
||||
assert(immutable_options_);
|
||||
ROCKS_LOG_INFO(immutable_options_->logger,
|
||||
"[%s] [JOB %d] Generated blob file #%" PRIu64 ": %" PRIu64
|
||||
" total blobs, %" PRIu64 " total bytes",
|
||||
column_family_name_.c_str(), job_id_, blob_file_number,
|
||||
blob_count_, blob_bytes_);
|
||||
if (blob_callback_) {
|
||||
s = blob_callback_->OnBlobFileCompleted(blob_file_paths_->back());
|
||||
}
|
||||
|
||||
writer_.reset();
|
||||
blob_count_ = 0;
|
||||
@@ -330,15 +353,18 @@ Status BlobFileBuilder::CloseBlobFileIfNeeded() {
|
||||
return CloseBlobFile();
|
||||
}
|
||||
|
||||
void BlobFileBuilder::Abandon() {
|
||||
void BlobFileBuilder::Abandon(const Status& s) {
|
||||
if (!IsBlobFileOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob_callback_) {
|
||||
// BlobFileBuilder::Abandon() is called because of error while writing to
|
||||
// Blob files. So we can ignore the below error.
|
||||
blob_callback_->OnBlobFileCompleted(blob_file_paths_->back())
|
||||
blob_callback_
|
||||
->OnBlobFileCompleted(blob_file_paths_->back(), column_family_name_,
|
||||
job_id_, writer_->get_log_number(),
|
||||
creation_reason_, s, "", "", blob_count_,
|
||||
blob_bytes_)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/types.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -41,6 +42,7 @@ class BlobFileBuilder {
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions);
|
||||
|
||||
@@ -54,6 +56,7 @@ class BlobFileBuilder {
|
||||
Env::WriteLifeTimeHint write_hint,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCompletionCallback* blob_callback,
|
||||
BlobFileCreationReason creation_reason,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions);
|
||||
|
||||
@@ -64,7 +67,7 @@ class BlobFileBuilder {
|
||||
|
||||
Status Add(const Slice& key, const Slice& value, std::string* blob_index);
|
||||
Status Finish();
|
||||
void Abandon();
|
||||
void Abandon(const Status& s);
|
||||
|
||||
private:
|
||||
bool IsBlobFileOpen() const;
|
||||
@@ -77,7 +80,7 @@ class BlobFileBuilder {
|
||||
|
||||
std::function<uint64_t()> file_number_generator_;
|
||||
FileSystem* fs_;
|
||||
const ImmutableOptions* immutable_cf_options_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
uint64_t min_blob_size_;
|
||||
uint64_t blob_file_size_;
|
||||
CompressionType blob_compression_type_;
|
||||
@@ -89,6 +92,7 @@ class BlobFileBuilder {
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
BlobFileCreationReason creation_reason_;
|
||||
std::vector<std::string>* blob_file_paths_;
|
||||
std::vector<BlobFileAddition>* blob_file_additions_;
|
||||
std::unique_ptr<BlobLogWriter> writer_;
|
||||
|
||||
@@ -39,9 +39,10 @@ class TestFileNumberGenerator {
|
||||
|
||||
class BlobFileBuilderTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileBuilderTest() : mock_env_(Env::Default()) {
|
||||
fs_ = mock_env_.GetFileSystem().get();
|
||||
clock_ = mock_env_.GetSystemClock().get();
|
||||
BlobFileBuilderTest() {
|
||||
mock_env_.reset(MockEnv::Create(Env::Default()));
|
||||
fs_ = mock_env_->GetFileSystem().get();
|
||||
clock_ = mock_env_->GetSystemClock().get();
|
||||
}
|
||||
|
||||
void VerifyBlobFile(uint64_t blob_file_number,
|
||||
@@ -108,7 +109,7 @@ class BlobFileBuilderTest : public testing::Test {
|
||||
ASSERT_EQ(footer.expiration_range, ExpirationRange());
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
FileSystem* fs_;
|
||||
SystemClock* clock_;
|
||||
FileOptions file_options_;
|
||||
@@ -123,13 +124,13 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderTest_BuildAndCheckOneFile"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -141,12 +142,11 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -178,9 +178,9 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
|
||||
const std::string& blob_file_path = blob_file_paths[0];
|
||||
|
||||
ASSERT_EQ(blob_file_path,
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
ASSERT_EQ(
|
||||
blob_file_path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number));
|
||||
|
||||
ASSERT_EQ(blob_file_additions.size(), 1);
|
||||
|
||||
@@ -207,14 +207,14 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderTest_BuildAndCheckMultipleFiles"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -226,12 +226,11 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -264,7 +263,7 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
const uint64_t blob_file_number = i + 2;
|
||||
|
||||
ASSERT_EQ(blob_file_paths[i],
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
|
||||
const auto& blob_file_addition = blob_file_additions[i];
|
||||
@@ -295,13 +294,14 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_InlinedValues"),
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderTest_InlinedValues"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 1024;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -313,12 +313,11 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) {
|
||||
const std::string key = std::to_string(i);
|
||||
@@ -350,12 +349,13 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_Compression"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileBuilderTest_Compression"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -367,12 +367,11 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string uncompressed_value(value_size, 'x');
|
||||
@@ -391,9 +390,9 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
|
||||
const std::string& blob_file_path = blob_file_paths[0];
|
||||
|
||||
ASSERT_EQ(blob_file_path,
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
ASSERT_EQ(
|
||||
blob_file_path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number));
|
||||
|
||||
ASSERT_EQ(blob_file_additions.size(), 1);
|
||||
|
||||
@@ -433,12 +432,13 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_CompressionError"),
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderTest_CompressionError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
options.env = &mock_env_;
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
options.env = mock_env_.get();
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -450,12 +450,11 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue",
|
||||
[](void* arg) {
|
||||
@@ -477,9 +476,9 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
constexpr uint64_t blob_file_number = 2;
|
||||
|
||||
ASSERT_EQ(blob_file_paths.size(), 1);
|
||||
ASSERT_EQ(blob_file_paths[0],
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
ASSERT_EQ(
|
||||
blob_file_paths[0],
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number));
|
||||
|
||||
ASSERT_TRUE(blob_file_additions.empty());
|
||||
}
|
||||
@@ -511,13 +510,14 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_Checksum"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileBuilderTest_Checksum"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.file_checksum_gen_factory =
|
||||
std::make_shared<DummyFileChecksumGenFactory>();
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -529,12 +529,11 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string value("deadbeef");
|
||||
@@ -553,9 +552,9 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
|
||||
const std::string& blob_file_path = blob_file_paths[0];
|
||||
|
||||
ASSERT_EQ(blob_file_path,
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
ASSERT_EQ(
|
||||
blob_file_path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number));
|
||||
|
||||
ASSERT_EQ(blob_file_additions.size(), 1);
|
||||
|
||||
@@ -581,12 +580,12 @@ class BlobFileBuilderIOErrorTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
BlobFileBuilderIOErrorTest()
|
||||
: mock_env_(Env::Default()),
|
||||
fs_(mock_env_.GetFileSystem().get()),
|
||||
sync_point_(GetParam()) {}
|
||||
BlobFileBuilderIOErrorTest() : sync_point_(GetParam()) {
|
||||
mock_env_.reset(MockEnv::Create(Env::Default()));
|
||||
fs_ = mock_env_->GetFileSystem().get();
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
FileSystem* fs_;
|
||||
FileOptions file_options_;
|
||||
std::string sync_point_;
|
||||
@@ -608,13 +607,14 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderIOErrorTest_IOError"),
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileBuilderIOErrorTest_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -626,12 +626,11 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), fs_, &immutable_cf_options,
|
||||
&mutable_cf_options, &file_options_, job_id,
|
||||
column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/,
|
||||
nullptr /*BlobFileCompletionCallback*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
BlobFileBuilder builder(
|
||||
TestFileNumberGenerator(), fs_, &immutable_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id, column_family_name, io_priority,
|
||||
write_hint, nullptr /*IOTracer*/, nullptr /*BlobFileCompletionCallback*/,
|
||||
BlobFileCreationReason::kFlush, &blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) {
|
||||
Status* const s = static_cast<Status*>(arg);
|
||||
@@ -658,7 +657,7 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
|
||||
ASSERT_EQ(blob_file_paths.size(), 1);
|
||||
ASSERT_EQ(blob_file_paths[0],
|
||||
BlobFileName(immutable_cf_options.cf_paths.front().path,
|
||||
BlobFileName(immutable_options.cf_paths.front().path,
|
||||
blob_file_number));
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@ BlobFileCache::BlobFileCache(Cache* cache,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: cache_(cache),
|
||||
mutex_(kNumberOfMutexStripes, kGetSliceNPHash64UnseededFnPtr),
|
||||
immutable_cf_options_(immutable_options),
|
||||
immutable_options_(immutable_options),
|
||||
file_options_(file_options),
|
||||
column_family_id_(column_family_id),
|
||||
blob_file_read_hist_(blob_file_read_hist),
|
||||
io_tracer_(io_tracer) {
|
||||
assert(cache_);
|
||||
assert(immutable_cf_options_);
|
||||
assert(immutable_options_);
|
||||
assert(file_options_);
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
assert(immutable_cf_options_);
|
||||
Statistics* const statistics = immutable_cf_options_->stats;
|
||||
assert(immutable_options_);
|
||||
Statistics* const statistics = immutable_options_->stats;
|
||||
|
||||
RecordTick(statistics, NO_FILE_OPENS);
|
||||
|
||||
@@ -73,7 +73,7 @@ Status BlobFileCache::GetBlobFileReader(
|
||||
{
|
||||
assert(file_options_);
|
||||
const Status s = BlobFileReader::Create(
|
||||
*immutable_cf_options_, *file_options_, column_family_id_,
|
||||
*immutable_options_, *file_options_, column_family_id_,
|
||||
blob_file_read_hist_, blob_file_number, io_tracer_, &reader);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
|
||||
@@ -40,7 +40,7 @@ class BlobFileCache {
|
||||
// Note: mutex_ below is used to guard against multiple threads racing to open
|
||||
// the same file.
|
||||
Striped<port::Mutex, Slice> mutex_;
|
||||
const ImmutableOptions* immutable_cf_options_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
const FileOptions* file_options_;
|
||||
uint32_t column_family_id_;
|
||||
HistogramImpl* blob_file_read_hist_;
|
||||
|
||||
@@ -29,28 +29,27 @@ namespace {
|
||||
|
||||
// Creates a test blob file with a single blob in it.
|
||||
void WriteBlobFile(uint32_t column_family_id,
|
||||
const ImmutableOptions& immutable_cf_options,
|
||||
const ImmutableOptions& immutable_options,
|
||||
uint64_t blob_file_number) {
|
||||
assert(!immutable_cf_options.cf_paths.empty());
|
||||
assert(!immutable_options.cf_paths.empty());
|
||||
|
||||
const std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options.cf_paths.front().path, blob_file_number);
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_cf_options.fs.get(), blob_file_path,
|
||||
&file, FileOptions()));
|
||||
ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.clock));
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_path, FileOptions(), immutable_options.clock));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
constexpr bool use_fsync = false;
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer), immutable_options.clock,
|
||||
statistics, blob_file_number, use_fsync,
|
||||
do_flush);
|
||||
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
@@ -85,25 +84,26 @@ void WriteBlobFile(uint32_t column_family_id,
|
||||
|
||||
class BlobFileCacheTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileCacheTest() : mock_env_(Env::Default()) {}
|
||||
BlobFileCacheTest() { mock_env_.reset(MockEnv::Create(Env::Default())); }
|
||||
|
||||
MockEnv mock_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
};
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileCacheTest_GetBlobFileReader"),
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileCacheTest_GetBlobFileReader"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
@@ -111,7 +111,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
@@ -136,19 +136,19 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileCacheTest_GetBlobFileReader_Race"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
@@ -156,7 +156,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
@@ -188,10 +188,10 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileCacheTest_GetBlobFileReader_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
@@ -199,12 +199,12 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
FileOptions file_options;
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
@@ -222,19 +222,19 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileCacheTest_GetBlobFileReader_CacheFull"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_cf_options, blob_file_number);
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 0;
|
||||
constexpr int num_shard_bits = -1; // determined automatically
|
||||
@@ -245,7 +245,7 @@ TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_cf_options,
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "db/error_handler.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
@@ -16,23 +17,51 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class BlobFileCompletionCallback {
|
||||
public:
|
||||
#ifdef ROCKSDB_LITE
|
||||
BlobFileCompletionCallback(SstFileManager* /*sst_file_manager*/,
|
||||
InstrumentedMutex* /*mutex*/,
|
||||
ErrorHandler* /*error_handler*/) {}
|
||||
Status OnBlobFileCompleted(const std::string& /*file_name*/) {
|
||||
return Status::OK();
|
||||
}
|
||||
BlobFileCompletionCallback(
|
||||
SstFileManager* sst_file_manager, InstrumentedMutex* mutex,
|
||||
ErrorHandler* error_handler, EventLogger* event_logger,
|
||||
const std::vector<std::shared_ptr<EventListener>>& listeners,
|
||||
const std::string& dbname)
|
||||
: event_logger_(event_logger), listeners_(listeners), dbname_(dbname) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
sst_file_manager_ = sst_file_manager;
|
||||
mutex_ = mutex;
|
||||
error_handler_ = error_handler;
|
||||
#else
|
||||
BlobFileCompletionCallback(SstFileManager* sst_file_manager,
|
||||
InstrumentedMutex* mutex,
|
||||
ErrorHandler* error_handler)
|
||||
: sst_file_manager_(sst_file_manager),
|
||||
mutex_(mutex),
|
||||
error_handler_(error_handler) {}
|
||||
(void)sst_file_manager;
|
||||
(void)mutex;
|
||||
(void)error_handler;
|
||||
#endif // ROCKSDB_LITE
|
||||
}
|
||||
|
||||
Status OnBlobFileCompleted(const std::string& file_name) {
|
||||
void OnBlobFileCreationStarted(const std::string& file_name,
|
||||
const std::string& column_family_name,
|
||||
int job_id,
|
||||
BlobFileCreationReason creation_reason) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Notify the listeners.
|
||||
EventHelpers::NotifyBlobFileCreationStarted(listeners_, dbname_,
|
||||
column_family_name, file_name,
|
||||
job_id, creation_reason);
|
||||
#else
|
||||
(void)file_name;
|
||||
(void)column_family_name;
|
||||
(void)job_id;
|
||||
(void)creation_reason;
|
||||
#endif
|
||||
}
|
||||
|
||||
Status OnBlobFileCompleted(const std::string& file_name,
|
||||
const std::string& column_family_name, int job_id,
|
||||
uint64_t file_number,
|
||||
BlobFileCreationReason creation_reason,
|
||||
const Status& report_status,
|
||||
const std::string& checksum_value,
|
||||
const std::string& checksum_method,
|
||||
uint64_t blob_count, uint64_t blob_bytes) {
|
||||
Status s;
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager_);
|
||||
if (sfm) {
|
||||
// Report new blob files to SstFileManagerImpl
|
||||
@@ -45,13 +74,28 @@ class BlobFileCompletionCallback {
|
||||
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// Notify the listeners.
|
||||
EventHelpers::LogAndNotifyBlobFileCreationFinished(
|
||||
event_logger_, listeners_, dbname_, column_family_name, file_name,
|
||||
job_id, file_number, creation_reason,
|
||||
(!report_status.ok() ? report_status : s),
|
||||
(checksum_value.empty() ? kUnknownFileChecksum : checksum_value),
|
||||
(checksum_method.empty() ? kUnknownFileChecksumFuncName
|
||||
: checksum_method),
|
||||
blob_count, blob_bytes);
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
#ifndef ROCKSDB_LITE
|
||||
SstFileManager* sst_file_manager_;
|
||||
InstrumentedMutex* mutex_;
|
||||
ErrorHandler* error_handler_;
|
||||
#endif // ROCKSDB_LITE
|
||||
EventLogger* event_logger_;
|
||||
std::vector<std::shared_ptr<EventListener>> listeners_;
|
||||
std::string dbname_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -8,7 +8,13 @@
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
uint64_t SharedBlobFileMetaData::GetBlobFileSize() const {
|
||||
return BlobLogHeader::kSize + total_blob_bytes_ + BlobLogFooter::kSize;
|
||||
}
|
||||
|
||||
std::string SharedBlobFileMetaData::DebugString() const {
|
||||
std::ostringstream oss;
|
||||
@@ -23,7 +29,8 @@ std::ostream& operator<<(std::ostream& os,
|
||||
<< " total_blob_count: " << shared_meta.GetTotalBlobCount()
|
||||
<< " total_blob_bytes: " << shared_meta.GetTotalBlobBytes()
|
||||
<< " checksum_method: " << shared_meta.GetChecksumMethod()
|
||||
<< " checksum_value: " << shared_meta.GetChecksumValue();
|
||||
<< " checksum_value: "
|
||||
<< Slice(shared_meta.GetChecksumValue()).ToString(/* hex */ true);
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ class SharedBlobFileMetaData {
|
||||
SharedBlobFileMetaData(SharedBlobFileMetaData&&) = delete;
|
||||
SharedBlobFileMetaData& operator=(SharedBlobFileMetaData&&) = delete;
|
||||
|
||||
uint64_t GetBlobFileSize() const;
|
||||
uint64_t GetBlobFileNumber() const { return blob_file_number_; }
|
||||
uint64_t GetTotalBlobCount() const { return total_blob_count_; }
|
||||
uint64_t GetTotalBlobBytes() const { return total_blob_bytes_; }
|
||||
@@ -112,6 +113,11 @@ class BlobFileMetaData {
|
||||
return shared_meta_;
|
||||
}
|
||||
|
||||
uint64_t GetBlobFileSize() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetBlobFileSize();
|
||||
}
|
||||
|
||||
uint64_t GetBlobFileNumber() const {
|
||||
assert(shared_meta_);
|
||||
return shared_meta_->GetBlobFileNumber();
|
||||
|
||||
+170
-33
@@ -10,6 +10,7 @@
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/filename.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/slice.h"
|
||||
@@ -17,14 +18,14 @@
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/crc32c.h"
|
||||
#include "util/stop_watch.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFileReader::Create(
|
||||
const ImmutableOptions& immutable_cf_options,
|
||||
const FileOptions& file_options, uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist, uint64_t blob_file_number,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const ImmutableOptions& immutable_options, const FileOptions& file_options,
|
||||
uint32_t column_family_id, HistogramImpl* blob_file_read_hist,
|
||||
uint64_t blob_file_number, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
std::unique_ptr<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(!*blob_file_reader);
|
||||
@@ -34,7 +35,7 @@ Status BlobFileReader::Create(
|
||||
|
||||
{
|
||||
const Status s =
|
||||
OpenFile(immutable_cf_options, file_options, blob_file_read_hist,
|
||||
OpenFile(immutable_options, file_options, blob_file_read_hist,
|
||||
blob_file_number, io_tracer, &file_size, &file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -43,44 +44,47 @@ Status BlobFileReader::Create(
|
||||
|
||||
assert(file_reader);
|
||||
|
||||
Statistics* const statistics = immutable_options.stats;
|
||||
|
||||
CompressionType compression_type = kNoCompression;
|
||||
|
||||
{
|
||||
const Status s =
|
||||
ReadHeader(file_reader.get(), column_family_id, &compression_type);
|
||||
const Status s = ReadHeader(file_reader.get(), column_family_id, statistics,
|
||||
&compression_type);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
const Status s = ReadFooter(file_size, file_reader.get());
|
||||
const Status s = ReadFooter(file_reader.get(), file_size, statistics);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
blob_file_reader->reset(
|
||||
new BlobFileReader(std::move(file_reader), file_size, compression_type));
|
||||
new BlobFileReader(std::move(file_reader), file_size, compression_type,
|
||||
immutable_options.clock, statistics));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobFileReader::OpenFile(
|
||||
const ImmutableOptions& immutable_cf_options, const FileOptions& file_opts,
|
||||
const ImmutableOptions& immutable_options, const FileOptions& file_opts,
|
||||
HistogramImpl* blob_file_read_hist, uint64_t blob_file_number,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, uint64_t* file_size,
|
||||
std::unique_ptr<RandomAccessFileReader>* file_reader) {
|
||||
assert(file_size);
|
||||
assert(file_reader);
|
||||
|
||||
const auto& cf_paths = immutable_cf_options.cf_paths;
|
||||
const auto& cf_paths = immutable_options.cf_paths;
|
||||
assert(!cf_paths.empty());
|
||||
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(cf_paths.front().path, blob_file_number);
|
||||
|
||||
FileSystem* const fs = immutable_cf_options.fs.get();
|
||||
FileSystem* const fs = immutable_options.fs.get();
|
||||
assert(fs);
|
||||
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
@@ -113,21 +117,22 @@ Status BlobFileReader::OpenFile(
|
||||
|
||||
assert(file);
|
||||
|
||||
if (immutable_cf_options.advise_random_on_open) {
|
||||
if (immutable_options.advise_random_on_open) {
|
||||
file->Hint(FSRandomAccessFile::kRandom);
|
||||
}
|
||||
|
||||
file_reader->reset(new RandomAccessFileReader(
|
||||
std::move(file), blob_file_path, immutable_cf_options.clock, io_tracer,
|
||||
immutable_cf_options.stats, BLOB_DB_BLOB_FILE_READ_MICROS,
|
||||
blob_file_read_hist, immutable_cf_options.rate_limiter.get(),
|
||||
immutable_cf_options.listeners));
|
||||
std::move(file), blob_file_path, immutable_options.clock, io_tracer,
|
||||
immutable_options.stats, BLOB_DB_BLOB_FILE_READ_MICROS,
|
||||
blob_file_read_hist, immutable_options.rate_limiter.get(),
|
||||
immutable_options.listeners));
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
uint32_t column_family_id,
|
||||
Statistics* statistics,
|
||||
CompressionType* compression_type) {
|
||||
assert(file_reader);
|
||||
assert(compression_type);
|
||||
@@ -142,8 +147,9 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
constexpr uint64_t read_offset = 0;
|
||||
constexpr size_t read_size = BlobLogHeader::kSize;
|
||||
|
||||
const Status s = ReadFromFile(file_reader, read_offset, read_size,
|
||||
&header_slice, &buf, &aligned_buf);
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_offset, read_size, statistics,
|
||||
&header_slice, &buf, &aligned_buf);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -176,8 +182,8 @@ Status BlobFileReader::ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobFileReader::ReadFooter(uint64_t file_size,
|
||||
const RandomAccessFileReader* file_reader) {
|
||||
Status BlobFileReader::ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
uint64_t file_size, Statistics* statistics) {
|
||||
assert(file_size >= BlobLogHeader::kSize + BlobLogFooter::kSize);
|
||||
assert(file_reader);
|
||||
|
||||
@@ -191,8 +197,9 @@ Status BlobFileReader::ReadFooter(uint64_t file_size,
|
||||
const uint64_t read_offset = file_size - BlobLogFooter::kSize;
|
||||
constexpr size_t read_size = BlobLogFooter::kSize;
|
||||
|
||||
const Status s = ReadFromFile(file_reader, read_offset, read_size,
|
||||
&footer_slice, &buf, &aligned_buf);
|
||||
const Status s =
|
||||
ReadFromFile(file_reader, read_offset, read_size, statistics,
|
||||
&footer_slice, &buf, &aligned_buf);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -221,14 +228,16 @@ Status BlobFileReader::ReadFooter(uint64_t file_size,
|
||||
|
||||
Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Slice* slice, Buffer* buf,
|
||||
AlignedBuf* aligned_buf) {
|
||||
Statistics* statistics, Slice* slice,
|
||||
Buffer* buf, AlignedBuf* aligned_buf) {
|
||||
assert(slice);
|
||||
assert(buf);
|
||||
assert(aligned_buf);
|
||||
|
||||
assert(file_reader);
|
||||
|
||||
RecordTick(statistics, BLOB_DB_BLOB_FILE_BYTES_READ, read_size);
|
||||
|
||||
Status s;
|
||||
|
||||
if (file_reader->use_direct_io()) {
|
||||
@@ -257,10 +266,13 @@ Status BlobFileReader::ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
|
||||
BlobFileReader::BlobFileReader(
|
||||
std::unique_ptr<RandomAccessFileReader>&& file_reader, uint64_t file_size,
|
||||
CompressionType compression_type)
|
||||
CompressionType compression_type, SystemClock* clock,
|
||||
Statistics* statistics)
|
||||
: file_reader_(std::move(file_reader)),
|
||||
file_size_(file_size),
|
||||
compression_type_(compression_type) {
|
||||
compression_type_(compression_type),
|
||||
clock_(clock),
|
||||
statistics_(statistics) {
|
||||
assert(file_reader_);
|
||||
}
|
||||
|
||||
@@ -305,7 +317,7 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
TEST_SYNC_POINT("BlobFileReader::GetBlob:ReadFromFile");
|
||||
|
||||
const Status s = ReadFromFile(file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size),
|
||||
static_cast<size_t>(record_size), statistics_,
|
||||
&record_slice, &buf, &aligned_buf);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
@@ -325,8 +337,8 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
const Slice value_slice(record_slice.data() + adjustment, value_size);
|
||||
|
||||
{
|
||||
const Status s =
|
||||
UncompressBlobIfNeeded(value_slice, compression_type, value);
|
||||
const Status s = UncompressBlobIfNeeded(value_slice, compression_type,
|
||||
clock_, statistics_, value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -339,6 +351,124 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void BlobFileReader::MultiGetBlob(
|
||||
const ReadOptions& read_options,
|
||||
const autovector<std::reference_wrapper<const Slice>>& user_keys,
|
||||
const autovector<uint64_t>& offsets,
|
||||
const autovector<uint64_t>& value_sizes, autovector<Status*>& statuses,
|
||||
autovector<PinnableSlice*>& values, uint64_t* bytes_read) const {
|
||||
const size_t num_blobs = user_keys.size();
|
||||
assert(num_blobs > 0);
|
||||
assert(num_blobs == offsets.size());
|
||||
assert(num_blobs == value_sizes.size());
|
||||
assert(num_blobs == statuses.size());
|
||||
assert(num_blobs == values.size());
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 0; i < offsets.size() - 1; ++i) {
|
||||
assert(offsets[i] <= offsets[i + 1]);
|
||||
}
|
||||
#endif // !NDEBUG
|
||||
|
||||
std::vector<FSReadRequest> read_reqs(num_blobs);
|
||||
autovector<uint64_t> adjustments;
|
||||
uint64_t total_len = 0;
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
const size_t key_size = user_keys[i].get().size();
|
||||
assert(IsValidBlobOffset(offsets[i], key_size, value_sizes[i], file_size_));
|
||||
const uint64_t adjustment =
|
||||
read_options.verify_checksums
|
||||
? BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size)
|
||||
: 0;
|
||||
assert(offsets[i] >= adjustment);
|
||||
adjustments.push_back(adjustment);
|
||||
read_reqs[i].offset = offsets[i] - adjustment;
|
||||
read_reqs[i].len = value_sizes[i] + adjustment;
|
||||
total_len += read_reqs[i].len;
|
||||
}
|
||||
|
||||
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_READ, total_len);
|
||||
|
||||
Buffer buf;
|
||||
AlignedBuf aligned_buf;
|
||||
|
||||
Status s;
|
||||
bool direct_io = file_reader_->use_direct_io();
|
||||
if (direct_io) {
|
||||
for (size_t i = 0; i < read_reqs.size(); ++i) {
|
||||
read_reqs[i].scratch = nullptr;
|
||||
}
|
||||
} else {
|
||||
buf.reset(new char[total_len]);
|
||||
std::ptrdiff_t pos = 0;
|
||||
for (size_t i = 0; i < read_reqs.size(); ++i) {
|
||||
read_reqs[i].scratch = buf.get() + pos;
|
||||
pos += read_reqs[i].len;
|
||||
}
|
||||
}
|
||||
TEST_SYNC_POINT("BlobFileReader::MultiGetBlob:ReadFromFile");
|
||||
s = file_reader_->MultiRead(IOOptions(), read_reqs.data(), read_reqs.size(),
|
||||
direct_io ? &aligned_buf : nullptr);
|
||||
if (!s.ok()) {
|
||||
for (auto& req : read_reqs) {
|
||||
req.status.PermitUncheckedError();
|
||||
}
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
*statuses[i] = s;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
assert(s.ok());
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
auto& req = read_reqs[i];
|
||||
assert(statuses[i]);
|
||||
if (req.status.ok() && req.result.size() != req.len) {
|
||||
req.status = IOStatus::Corruption("Failed to read data from blob file");
|
||||
}
|
||||
*statuses[i] = req.status;
|
||||
}
|
||||
|
||||
if (read_options.verify_checksums) {
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
if (!statuses[i]->ok()) {
|
||||
continue;
|
||||
}
|
||||
const Slice& record_slice = read_reqs[i].result;
|
||||
s = VerifyBlob(record_slice, user_keys[i], value_sizes[i]);
|
||||
if (!s.ok()) {
|
||||
assert(statuses[i]);
|
||||
*statuses[i] = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
assert(statuses[i]);
|
||||
if (!statuses[i]->ok()) {
|
||||
continue;
|
||||
}
|
||||
const Slice& record_slice = read_reqs[i].result;
|
||||
const Slice value_slice(record_slice.data() + adjustments[i],
|
||||
value_sizes[i]);
|
||||
s = UncompressBlobIfNeeded(value_slice, compression_type_, clock_,
|
||||
statistics_, values[i]);
|
||||
if (!s.ok()) {
|
||||
*statuses[i] = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_read) {
|
||||
uint64_t total_bytes = 0;
|
||||
for (const auto& req : read_reqs) {
|
||||
total_bytes += req.result.size();
|
||||
}
|
||||
*bytes_read = total_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
Status BlobFileReader::VerifyBlob(const Slice& record_slice,
|
||||
const Slice& user_key, uint64_t value_size) {
|
||||
BlobLogRecord record;
|
||||
@@ -383,6 +513,8 @@ Status BlobFileReader::VerifyBlob(const Slice& record_slice,
|
||||
|
||||
Status BlobFileReader::UncompressBlobIfNeeded(const Slice& value_slice,
|
||||
CompressionType compression_type,
|
||||
SystemClock* clock,
|
||||
Statistics* statistics,
|
||||
PinnableSlice* value) {
|
||||
assert(value);
|
||||
|
||||
@@ -400,9 +532,14 @@ Status BlobFileReader::UncompressBlobIfNeeded(const Slice& value_slice,
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
constexpr MemoryAllocator* allocator = nullptr;
|
||||
|
||||
CacheAllocationPtr output =
|
||||
UncompressData(info, value_slice.data(), value_slice.size(),
|
||||
&uncompressed_size, compression_format_version, allocator);
|
||||
CacheAllocationPtr output;
|
||||
|
||||
{
|
||||
StopWatch stop_watch(clock, statistics, BLOB_DB_DECOMPRESSION_MICROS);
|
||||
output = UncompressData(info, value_slice.data(), value_slice.size(),
|
||||
&uncompressed_size, compression_format_version,
|
||||
allocator);
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", &output);
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "rocksdb/compression_type.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -21,6 +22,7 @@ class HistogramImpl;
|
||||
struct ReadOptions;
|
||||
class Slice;
|
||||
class PinnableSlice;
|
||||
class Statistics;
|
||||
|
||||
class BlobFileReader {
|
||||
public:
|
||||
@@ -42,9 +44,22 @@ class BlobFileReader {
|
||||
CompressionType compression_type, PinnableSlice* value,
|
||||
uint64_t* bytes_read) const;
|
||||
|
||||
// offsets must be sorted in ascending order by caller.
|
||||
void MultiGetBlob(
|
||||
const ReadOptions& read_options,
|
||||
const autovector<std::reference_wrapper<const Slice>>& user_keys,
|
||||
const autovector<uint64_t>& offsets,
|
||||
const autovector<uint64_t>& value_sizes, autovector<Status*>& statuses,
|
||||
autovector<PinnableSlice*>& values, uint64_t* bytes_read) const;
|
||||
|
||||
CompressionType GetCompressionType() const { return compression_type_; }
|
||||
|
||||
uint64_t GetFileSize() const { return file_size_; }
|
||||
|
||||
private:
|
||||
BlobFileReader(std::unique_ptr<RandomAccessFileReader>&& file_reader,
|
||||
uint64_t file_size, CompressionType compression_type);
|
||||
uint64_t file_size, CompressionType compression_type,
|
||||
SystemClock* clock, Statistics* statistics);
|
||||
|
||||
static Status OpenFile(const ImmutableOptions& immutable_options,
|
||||
const FileOptions& file_opts,
|
||||
@@ -55,17 +70,17 @@ class BlobFileReader {
|
||||
std::unique_ptr<RandomAccessFileReader>* file_reader);
|
||||
|
||||
static Status ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
uint32_t column_family_id,
|
||||
uint32_t column_family_id, Statistics* statistics,
|
||||
CompressionType* compression_type);
|
||||
|
||||
static Status ReadFooter(uint64_t file_size,
|
||||
const RandomAccessFileReader* file_reader);
|
||||
static Status ReadFooter(const RandomAccessFileReader* file_reader,
|
||||
uint64_t file_size, Statistics* statistics);
|
||||
|
||||
using Buffer = std::unique_ptr<char[]>;
|
||||
|
||||
static Status ReadFromFile(const RandomAccessFileReader* file_reader,
|
||||
uint64_t read_offset, size_t read_size,
|
||||
Slice* slice, Buffer* buf,
|
||||
Statistics* statistics, Slice* slice, Buffer* buf,
|
||||
AlignedBuf* aligned_buf);
|
||||
|
||||
static Status VerifyBlob(const Slice& record_slice, const Slice& user_key,
|
||||
@@ -73,6 +88,8 @@ class BlobFileReader {
|
||||
|
||||
static Status UncompressBlobIfNeeded(const Slice& value_slice,
|
||||
CompressionType compression_type,
|
||||
SystemClock* clock,
|
||||
Statistics* statistics,
|
||||
PinnableSlice* value);
|
||||
|
||||
static void SaveValue(const Slice& src, PinnableSlice* dst);
|
||||
@@ -80,6 +97,8 @@ class BlobFileReader {
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader_;
|
||||
uint64_t file_size_;
|
||||
CompressionType compression_type_;
|
||||
SystemClock* clock_;
|
||||
Statistics* statistics_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+308
-162
@@ -27,124 +27,153 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a test blob file with a single blob in it. Note: this method
|
||||
// makes it possible to test various corner cases by allowing the caller
|
||||
// to specify the contents of various blob file header/footer fields.
|
||||
void WriteBlobFile(const ImmutableOptions& immutable_cf_options,
|
||||
// Creates a test blob file with `num` blobs in it.
|
||||
void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
uint32_t column_family_id, bool has_ttl,
|
||||
const ExpirationRange& expiration_range_header,
|
||||
const ExpirationRange& expiration_range_footer,
|
||||
uint64_t blob_file_number, const Slice& key,
|
||||
const Slice& blob, CompressionType compression_type,
|
||||
uint64_t* blob_offset, uint64_t* blob_size) {
|
||||
assert(!immutable_cf_options.cf_paths.empty());
|
||||
assert(blob_offset);
|
||||
assert(blob_size);
|
||||
|
||||
const std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options.cf_paths.front().path, blob_file_number);
|
||||
uint64_t blob_file_number, const std::vector<Slice>& keys,
|
||||
const std::vector<Slice>& blobs, CompressionType compression,
|
||||
std::vector<uint64_t>& blob_offsets,
|
||||
std::vector<uint64_t>& blob_sizes) {
|
||||
assert(!immutable_options.cf_paths.empty());
|
||||
size_t num = keys.size();
|
||||
assert(num == blobs.size());
|
||||
assert(num == blob_offsets.size());
|
||||
assert(num == blob_sizes.size());
|
||||
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_cf_options.fs.get(), blob_file_path,
|
||||
&file, FileOptions()));
|
||||
ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.clock));
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
std::move(file), blob_file_path, FileOptions(), immutable_options.clock));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
constexpr bool use_fsync = false;
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer), immutable_options.clock,
|
||||
statistics, blob_file_number, use_fsync,
|
||||
do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, compression_type, has_ttl,
|
||||
BlobLogHeader header(column_family_id, compression, has_ttl,
|
||||
expiration_range_header);
|
||||
|
||||
ASSERT_OK(blob_log_writer.WriteHeader(header));
|
||||
|
||||
std::string compressed_blob;
|
||||
Slice blob_to_write;
|
||||
|
||||
if (compression_type == kNoCompression) {
|
||||
blob_to_write = blob;
|
||||
*blob_size = blob.size();
|
||||
std::vector<std::string> compressed_blobs(num);
|
||||
std::vector<Slice> blobs_to_write(num);
|
||||
if (kNoCompression == compression) {
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
blobs_to_write[i] = blobs[i];
|
||||
blob_sizes[i] = blobs[i].size();
|
||||
}
|
||||
} else {
|
||||
CompressionOptions opts;
|
||||
CompressionContext context(compression_type);
|
||||
CompressionContext context(compression);
|
||||
constexpr uint64_t sample_for_compression = 0;
|
||||
|
||||
CompressionInfo info(opts, context, CompressionDict::GetEmptyDict(),
|
||||
compression_type, sample_for_compression);
|
||||
compression, sample_for_compression);
|
||||
|
||||
constexpr uint32_t compression_format_version = 2;
|
||||
|
||||
ASSERT_TRUE(
|
||||
CompressData(blob, info, compression_format_version, &compressed_blob));
|
||||
|
||||
blob_to_write = compressed_blob;
|
||||
*blob_size = compressed_blob.size();
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
ASSERT_TRUE(CompressData(blobs[i], info, compression_format_version,
|
||||
&compressed_blobs[i]));
|
||||
blobs_to_write[i] = compressed_blobs[i];
|
||||
blob_sizes[i] = compressed_blobs[i].size();
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t key_offset = 0;
|
||||
|
||||
ASSERT_OK(
|
||||
blob_log_writer.AddRecord(key, blob_to_write, &key_offset, blob_offset));
|
||||
for (size_t i = 0; i < num; ++i) {
|
||||
uint64_t key_offset = 0;
|
||||
ASSERT_OK(blob_log_writer.AddRecord(keys[i], blobs_to_write[i], &key_offset,
|
||||
&blob_offsets[i]));
|
||||
}
|
||||
|
||||
BlobLogFooter footer;
|
||||
footer.blob_count = 1;
|
||||
footer.blob_count = num;
|
||||
footer.expiration_range = expiration_range_footer;
|
||||
|
||||
std::string checksum_method;
|
||||
std::string checksum_value;
|
||||
|
||||
ASSERT_OK(
|
||||
blob_log_writer.AppendFooter(footer, &checksum_method, &checksum_value));
|
||||
}
|
||||
|
||||
// Creates a test blob file with a single blob in it. Note: this method
|
||||
// makes it possible to test various corner cases by allowing the caller
|
||||
// to specify the contents of various blob file header/footer fields.
|
||||
void WriteBlobFile(const ImmutableOptions& immutable_options,
|
||||
uint32_t column_family_id, bool has_ttl,
|
||||
const ExpirationRange& expiration_range_header,
|
||||
const ExpirationRange& expiration_range_footer,
|
||||
uint64_t blob_file_number, const Slice& key,
|
||||
const Slice& blob, CompressionType compression,
|
||||
uint64_t* blob_offset, uint64_t* blob_size) {
|
||||
std::vector<Slice> keys{key};
|
||||
std::vector<Slice> blobs{blob};
|
||||
std::vector<uint64_t> blob_offsets{0};
|
||||
std::vector<uint64_t> blob_sizes{0};
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl,
|
||||
expiration_range_header, expiration_range_footer,
|
||||
blob_file_number, keys, blobs, compression, blob_offsets,
|
||||
blob_sizes);
|
||||
if (blob_offset) {
|
||||
*blob_offset = blob_offsets[0];
|
||||
}
|
||||
if (blob_size) {
|
||||
*blob_size = blob_sizes[0];
|
||||
}
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class BlobFileReaderTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileReaderTest() : mock_env_(Env::Default()) {}
|
||||
|
||||
MockEnv mock_env_;
|
||||
BlobFileReaderTest() { mock_env_.reset(MockEnv::Create(Env::Default())); }
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
};
|
||||
|
||||
TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_CreateReaderAndGetBlob"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
constexpr uint64_t blob_file_number = 1;
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
constexpr size_t num_blobs = 3;
|
||||
const std::vector<std::string> key_strs = {"key1", "key2", "key3"};
|
||||
const std::vector<std::string> blob_strs = {"blob1", "blob2", "blob3"};
|
||||
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
const std::vector<Slice> keys = {key_strs[0], key_strs[1], key_strs[2]};
|
||||
const std::vector<Slice> blobs = {blob_strs[0], blob_strs[1], blob_strs[2]};
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
std::vector<uint64_t> blob_offsets(keys.size());
|
||||
std::vector<uint64_t> blob_sizes(keys.size());
|
||||
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, keys, blobs, kNoCompression,
|
||||
blob_offsets, blob_sizes);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
// Make sure the blob can be retrieved with and without checksum verification
|
||||
ReadOptions read_options;
|
||||
@@ -154,10 +183,36 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kNoCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_EQ(bytes_read, blob_size);
|
||||
ASSERT_OK(reader->GetBlob(read_options, keys[0], blob_offsets[0],
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read));
|
||||
ASSERT_EQ(value, blobs[0]);
|
||||
ASSERT_EQ(bytes_read, blob_sizes[0]);
|
||||
|
||||
// MultiGetBlob
|
||||
bytes_read = 0;
|
||||
size_t total_size = 0;
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
ASSERT_EQ(value_buf[i], blobs[i]);
|
||||
total_size += blob_sizes[i];
|
||||
}
|
||||
ASSERT_EQ(bytes_read, total_size);
|
||||
}
|
||||
|
||||
read_options.verify_checksums = true;
|
||||
@@ -166,14 +221,15 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kNoCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_OK(reader->GetBlob(read_options, keys[1], blob_offsets[1],
|
||||
blob_sizes[1], kNoCompression, &value,
|
||||
&bytes_read));
|
||||
ASSERT_EQ(value, blobs[1]);
|
||||
|
||||
constexpr uint64_t key_size = sizeof(key) - 1;
|
||||
const uint64_t key_size = keys[1].size();
|
||||
ASSERT_EQ(bytes_read,
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size);
|
||||
blob_sizes[1]);
|
||||
}
|
||||
|
||||
// Invalid offset (too close to start of file)
|
||||
@@ -182,8 +238,9 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset - 1, blob_size,
|
||||
kNoCompression, &value, &bytes_read)
|
||||
->GetBlob(read_options, keys[0], blob_offsets[0] - 1,
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
@@ -194,8 +251,9 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset + 1, blob_size,
|
||||
kNoCompression, &value, &bytes_read)
|
||||
->GetBlob(read_options, keys[2], blob_offsets[2] + 1,
|
||||
blob_sizes[2], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
@@ -206,8 +264,8 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size, kZSTD,
|
||||
&value, &bytes_read)
|
||||
->GetBlob(read_options, keys[0], blob_offsets[0],
|
||||
blob_sizes[0], kZSTD, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
@@ -220,23 +278,82 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, shorter_key,
|
||||
blob_offset - (sizeof(key) - sizeof(shorter_key)),
|
||||
blob_size, kNoCompression, &value, &bytes_read)
|
||||
blob_offsets[0] -
|
||||
(keys[0].size() - sizeof(shorter_key) + 1),
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
Slice shorter_key_slice(shorter_key, sizeof(shorter_key) - 1);
|
||||
key_refs[1] = std::cref(shorter_key_slice);
|
||||
|
||||
autovector<uint64_t> offsets{
|
||||
blob_offsets[0],
|
||||
blob_offsets[1] - (keys[1].size() - key_refs[1].get().size()),
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i == 1) {
|
||||
ASSERT_TRUE(statuses_buf[i].IsCorruption());
|
||||
} else {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Incorrect key
|
||||
{
|
||||
constexpr char incorrect_key[] = "foo";
|
||||
constexpr char incorrect_key[] = "foo1";
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, incorrect_key, blob_offset,
|
||||
blob_size, kNoCompression, &value, &bytes_read)
|
||||
->GetBlob(read_options, incorrect_key, blob_offsets[0],
|
||||
blob_sizes[0], kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
Slice wrong_key_slice(incorrect_key, sizeof(incorrect_key) - 1);
|
||||
key_refs[2] = std::cref(wrong_key_slice);
|
||||
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1], blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i == num_blobs - 1) {
|
||||
ASSERT_TRUE(statuses_buf[i].IsCorruption());
|
||||
} else {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Incorrect value size
|
||||
@@ -245,10 +362,35 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size + 1,
|
||||
kNoCompression, &value, &bytes_read)
|
||||
->GetBlob(read_options, keys[1], blob_offsets[1],
|
||||
blob_sizes[1] + 1, kNoCompression, &value,
|
||||
&bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
// MultiGetBlob
|
||||
autovector<std::reference_wrapper<const Slice>> key_refs;
|
||||
for (const auto& key_ref : keys) {
|
||||
key_refs.emplace_back(std::cref(key_ref));
|
||||
}
|
||||
autovector<uint64_t> offsets{blob_offsets[0], blob_offsets[1],
|
||||
blob_offsets[2]};
|
||||
autovector<uint64_t> sizes{blob_sizes[0], blob_sizes[1] + 1, blob_sizes[2]};
|
||||
std::array<Status, num_blobs> statuses_buf;
|
||||
autovector<Status*> statuses{&statuses_buf[0], &statuses_buf[1],
|
||||
&statuses_buf[2]};
|
||||
std::array<PinnableSlice, num_blobs> value_buf;
|
||||
autovector<PinnableSlice*> values{&value_buf[0], &value_buf[1],
|
||||
&value_buf[2]};
|
||||
reader->MultiGetBlob(read_options, key_refs, offsets, sizes, statuses,
|
||||
values, &bytes_read);
|
||||
for (size_t i = 0; i < num_blobs; ++i) {
|
||||
if (i != 1) {
|
||||
ASSERT_OK(statuses_buf[i]);
|
||||
} else {
|
||||
ASSERT_TRUE(statuses_buf[i].IsCorruption());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,12 +399,13 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
// detect the error when we open it for reading
|
||||
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_Malformed"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileReaderTest_Malformed"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr uint64_t blob_file_number = 1;
|
||||
@@ -271,23 +414,23 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
|
||||
const std::string blob_file_path = BlobFileName(
|
||||
immutable_cf_options.cf_paths.front().path, blob_file_number);
|
||||
const std::string blob_file_path =
|
||||
BlobFileName(immutable_options.cf_paths.front().path, blob_file_number);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
ASSERT_OK(NewWritableFile(immutable_cf_options.fs.get(), blob_file_path,
|
||||
&file, FileOptions()));
|
||||
ASSERT_OK(NewWritableFile(immutable_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
std::unique_ptr<WritableFileWriter> file_writer(
|
||||
new WritableFileWriter(std::move(file), blob_file_path, FileOptions(),
|
||||
immutable_cf_options.clock));
|
||||
immutable_options.clock));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
constexpr bool use_fsync = false;
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
BlobLogWriter blob_log_writer(std::move(file_writer),
|
||||
immutable_cf_options.clock, statistics,
|
||||
immutable_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, kNoCompression, has_ttl,
|
||||
@@ -300,7 +443,7 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
@@ -309,12 +452,12 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, TTL) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_TTL"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileReaderTest_TTL"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = true;
|
||||
@@ -326,15 +469,15 @@ TEST_F(BlobFileReaderTest, TTL) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
@@ -343,14 +486,14 @@ TEST_F(BlobFileReaderTest, TTL) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_ExpirationRangeInHeader"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -364,7 +507,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl,
|
||||
expiration_range_header, expiration_range_footer,
|
||||
blob_file_number, key, blob, kNoCompression, &blob_offset,
|
||||
&blob_size);
|
||||
@@ -373,7 +516,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
@@ -382,14 +525,14 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_ExpirationRangeInFooter"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -403,7 +546,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl,
|
||||
expiration_range_header, expiration_range_footer,
|
||||
blob_file_number, key, blob, kNoCompression, &blob_offset,
|
||||
&blob_size);
|
||||
@@ -412,7 +555,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
@@ -421,14 +564,14 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_IncorrectColumnFamily"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -440,9 +583,9 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
@@ -450,7 +593,7 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
|
||||
constexpr uint32_t incorrect_column_family_id = 2;
|
||||
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
ASSERT_TRUE(BlobFileReader::Create(immutable_options, FileOptions(),
|
||||
incorrect_column_family_id,
|
||||
blob_file_read_hist, blob_file_number,
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
@@ -459,12 +602,13 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
|
||||
TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_BlobCRCError"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileReaderTest_BlobCRCError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -476,17 +620,17 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::VerifyBlob:CheckBlobCRC", [](void* arg) {
|
||||
@@ -517,12 +661,13 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
}
|
||||
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_Compression"), 0);
|
||||
test::PerThreadDBPath(mock_env_.get(), "BlobFileReaderTest_Compression"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -534,8 +679,8 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob,
|
||||
kSnappyCompression, &blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
@@ -543,8 +688,8 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
// Make sure the blob can be retrieved with and without checksum verification
|
||||
ReadOptions read_options;
|
||||
@@ -583,14 +728,14 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
}
|
||||
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderTest_UncompressionError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -602,8 +747,8 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob,
|
||||
kSnappyCompression, &blob_offset, &blob_size);
|
||||
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
@@ -611,8 +756,8 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", [](void* arg) {
|
||||
@@ -642,13 +787,13 @@ class BlobFileReaderIOErrorTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
BlobFileReaderIOErrorTest()
|
||||
: mock_env_(Env::Default()),
|
||||
fault_injection_env_(&mock_env_),
|
||||
sync_point_(GetParam()) {}
|
||||
BlobFileReaderIOErrorTest() : sync_point_(GetParam()) {
|
||||
mock_env_.reset(MockEnv::Create(Env::Default()));
|
||||
fault_injection_env_.reset(new FaultInjectionTestEnv(mock_env_.get()));
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
FaultInjectionTestEnv fault_injection_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
@@ -664,14 +809,14 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
// Simulates an I/O error during the specified step
|
||||
|
||||
Options options;
|
||||
options.env = &fault_injection_env_;
|
||||
options.env = fault_injection_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&fault_injection_env_,
|
||||
test::PerThreadDBPath(fault_injection_env_.get(),
|
||||
"BlobFileReaderIOErrorTest_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -683,13 +828,13 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -698,8 +843,8 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
const Status s = BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
|
||||
const bool fail_during_create =
|
||||
(sync_point_ != "BlobFileReader::GetBlob:ReadFromFile");
|
||||
@@ -727,10 +872,11 @@ class BlobFileReaderDecodingErrorTest
|
||||
: public testing::Test,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
BlobFileReaderDecodingErrorTest()
|
||||
: mock_env_(Env::Default()), sync_point_(GetParam()) {}
|
||||
BlobFileReaderDecodingErrorTest() : sync_point_(GetParam()) {
|
||||
mock_env_.reset(MockEnv::Create(Env::Default()));
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
std::unique_ptr<Env> mock_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
@@ -742,14 +888,14 @@ INSTANTIATE_TEST_CASE_P(BlobFileReaderTest, BlobFileReaderDecodingErrorTest,
|
||||
|
||||
TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.env = mock_env_.get();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
test::PerThreadDBPath(mock_env_.get(),
|
||||
"BlobFileReaderDecodingErrorTest_DecodingError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -761,9 +907,9 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
uint64_t blob_offset = 0;
|
||||
uint64_t blob_size = 0;
|
||||
|
||||
WriteBlobFile(immutable_cf_options, column_family_id, has_ttl,
|
||||
expiration_range, expiration_range, blob_file_number, key, blob,
|
||||
kNoCompression, &blob_offset, &blob_size);
|
||||
WriteBlobFile(immutable_options, column_family_id, has_ttl, expiration_range,
|
||||
expiration_range, blob_file_number, key, blob, kNoCompression,
|
||||
&blob_offset, &blob_size);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [](void* arg) {
|
||||
Slice* const slice = static_cast<Slice*>(arg);
|
||||
@@ -780,8 +926,8 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
const Status s = BlobFileReader::Create(
|
||||
immutable_cf_options, FileOptions(), column_family_id,
|
||||
blob_file_read_hist, blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader);
|
||||
|
||||
const bool fail_during_create =
|
||||
sync_point_ != "BlobFileReader::GetBlob:TamperWithResult";
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/dbformat.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobGarbageMeter::ProcessInFlow(const Slice& key, const Slice& value) {
|
||||
uint64_t blob_file_number = kInvalidBlobFileNumber;
|
||||
uint64_t bytes = 0;
|
||||
|
||||
const Status s = Parse(key, value, &blob_file_number, &bytes);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (blob_file_number == kInvalidBlobFileNumber) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
flows_[blob_file_number].AddInFlow(bytes);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobGarbageMeter::ProcessOutFlow(const Slice& key, const Slice& value) {
|
||||
uint64_t blob_file_number = kInvalidBlobFileNumber;
|
||||
uint64_t bytes = 0;
|
||||
|
||||
const Status s = Parse(key, value, &blob_file_number, &bytes);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (blob_file_number == kInvalidBlobFileNumber) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Note: in order to measure the amount of additional garbage, we only need to
|
||||
// track the outflow for preexisting files, i.e. those that also had inflow.
|
||||
// (Newly written files would only have outflow.)
|
||||
auto it = flows_.find(blob_file_number);
|
||||
if (it == flows_.end()) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
it->second.AddOutFlow(bytes);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status BlobGarbageMeter::Parse(const Slice& key, const Slice& value,
|
||||
uint64_t* blob_file_number, uint64_t* bytes) {
|
||||
assert(blob_file_number);
|
||||
assert(*blob_file_number == kInvalidBlobFileNumber);
|
||||
assert(bytes);
|
||||
assert(*bytes == 0);
|
||||
|
||||
ParsedInternalKey ikey;
|
||||
|
||||
{
|
||||
constexpr bool log_err_key = false;
|
||||
const Status s = ParseInternalKey(key, &ikey, log_err_key);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
if (ikey.type != kTypeBlobIndex) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
BlobIndex blob_index;
|
||||
|
||||
{
|
||||
const Status s = blob_index.DecodeFrom(value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
if (blob_index.IsInlined() || blob_index.HasTTL()) {
|
||||
return Status::Corruption("Unexpected TTL/inlined blob index");
|
||||
}
|
||||
|
||||
*blob_file_number = blob_index.file_number();
|
||||
*bytes =
|
||||
blob_index.size() +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(ikey.user_key.size());
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "db/blob/blob_constants.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Slice;
|
||||
|
||||
// A class that can be used to compute the amount of additional garbage
|
||||
// generated by a compaction. It parses the keys and blob references in the
|
||||
// input and output of a compaction, and aggregates the "inflow" and "outflow"
|
||||
// on a per-blob file basis. The amount of additional garbage for any given blob
|
||||
// file can then be computed by subtracting the outflow from the inflow.
|
||||
class BlobGarbageMeter {
|
||||
public:
|
||||
// A class to store the number and total size of blobs on a per-blob file
|
||||
// basis.
|
||||
class BlobStats {
|
||||
public:
|
||||
void Add(uint64_t bytes) {
|
||||
++count_;
|
||||
bytes_ += bytes;
|
||||
}
|
||||
void Add(uint64_t count, uint64_t bytes) {
|
||||
count_ += count;
|
||||
bytes_ += bytes;
|
||||
}
|
||||
|
||||
uint64_t GetCount() const { return count_; }
|
||||
uint64_t GetBytes() const { return bytes_; }
|
||||
|
||||
private:
|
||||
uint64_t count_ = 0;
|
||||
uint64_t bytes_ = 0;
|
||||
};
|
||||
|
||||
// A class to keep track of the "inflow" and the "outflow" and to compute the
|
||||
// amount of additional garbage for a given blob file.
|
||||
class BlobInOutFlow {
|
||||
public:
|
||||
void AddInFlow(uint64_t bytes) {
|
||||
in_flow_.Add(bytes);
|
||||
assert(IsValid());
|
||||
}
|
||||
void AddOutFlow(uint64_t bytes) {
|
||||
out_flow_.Add(bytes);
|
||||
assert(IsValid());
|
||||
}
|
||||
|
||||
const BlobStats& GetInFlow() const { return in_flow_; }
|
||||
const BlobStats& GetOutFlow() const { return out_flow_; }
|
||||
|
||||
bool IsValid() const {
|
||||
return in_flow_.GetCount() >= out_flow_.GetCount() &&
|
||||
in_flow_.GetBytes() >= out_flow_.GetBytes();
|
||||
}
|
||||
bool HasGarbage() const {
|
||||
assert(IsValid());
|
||||
return in_flow_.GetCount() > out_flow_.GetCount();
|
||||
}
|
||||
uint64_t GetGarbageCount() const {
|
||||
assert(IsValid());
|
||||
assert(HasGarbage());
|
||||
return in_flow_.GetCount() - out_flow_.GetCount();
|
||||
}
|
||||
uint64_t GetGarbageBytes() const {
|
||||
assert(IsValid());
|
||||
assert(HasGarbage());
|
||||
return in_flow_.GetBytes() - out_flow_.GetBytes();
|
||||
}
|
||||
|
||||
private:
|
||||
BlobStats in_flow_;
|
||||
BlobStats out_flow_;
|
||||
};
|
||||
|
||||
Status ProcessInFlow(const Slice& key, const Slice& value);
|
||||
Status ProcessOutFlow(const Slice& key, const Slice& value);
|
||||
|
||||
const std::unordered_map<uint64_t, BlobInOutFlow>& flows() const {
|
||||
return flows_;
|
||||
}
|
||||
|
||||
private:
|
||||
static Status Parse(const Slice& key, const Slice& value,
|
||||
uint64_t* blob_file_number, uint64_t* bytes);
|
||||
|
||||
std::unordered_map<uint64_t, BlobInOutFlow> flows_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,196 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
TEST(BlobGarbageMeterTest, MeasureGarbage) {
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
struct BlobDescriptor {
|
||||
std::string user_key;
|
||||
uint64_t blob_file_number;
|
||||
uint64_t offset;
|
||||
uint64_t size;
|
||||
CompressionType compression_type;
|
||||
bool has_in_flow;
|
||||
bool has_out_flow;
|
||||
|
||||
uint64_t GetExpectedBytes() const {
|
||||
return size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(user_key.size());
|
||||
}
|
||||
};
|
||||
|
||||
// Note: blob file 4 has the same inflow and outflow and hence no additional
|
||||
// garbage. Blob file 5 has less outflow than inflow and thus it does have
|
||||
// additional garbage. Blob file 6 is a newly written file (i.e. no inflow,
|
||||
// only outflow) and is thus not tracked by the meter.
|
||||
std::vector<BlobDescriptor> blobs{
|
||||
{"key", 4, 1234, 555, kLZ4Compression, true, true},
|
||||
{"other_key", 4, 6789, 101010, kLZ4Compression, true, true},
|
||||
{"yet_another_key", 5, 22222, 3456, kLZ4Compression, true, true},
|
||||
{"foo_key", 5, 77777, 8888, kLZ4Compression, true, true},
|
||||
{"bar_key", 5, 999999, 1212, kLZ4Compression, true, false},
|
||||
{"baz_key", 5, 1234567, 890, kLZ4Compression, true, false},
|
||||
{"new_key", 6, 7777, 9999, kNoCompression, false, true}};
|
||||
|
||||
for (const auto& blob : blobs) {
|
||||
constexpr SequenceNumber seq = 123;
|
||||
const InternalKey key(blob.user_key, seq, kTypeBlobIndex);
|
||||
const Slice key_slice = key.Encode();
|
||||
|
||||
std::string value;
|
||||
BlobIndex::EncodeBlob(&value, blob.blob_file_number, blob.offset, blob.size,
|
||||
blob.compression_type);
|
||||
const Slice value_slice(value);
|
||||
|
||||
if (blob.has_in_flow) {
|
||||
ASSERT_OK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
}
|
||||
if (blob.has_out_flow) {
|
||||
ASSERT_OK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
}
|
||||
}
|
||||
|
||||
const auto& flows = blob_garbage_meter.flows();
|
||||
ASSERT_EQ(flows.size(), 2);
|
||||
|
||||
{
|
||||
const auto it = flows.find(4);
|
||||
ASSERT_NE(it, flows.end());
|
||||
|
||||
const auto& flow = it->second;
|
||||
|
||||
constexpr uint64_t expected_count = 2;
|
||||
const uint64_t expected_bytes =
|
||||
blobs[0].GetExpectedBytes() + blobs[1].GetExpectedBytes();
|
||||
|
||||
const auto& in = flow.GetInFlow();
|
||||
ASSERT_EQ(in.GetCount(), expected_count);
|
||||
ASSERT_EQ(in.GetBytes(), expected_bytes);
|
||||
|
||||
const auto& out = flow.GetOutFlow();
|
||||
ASSERT_EQ(out.GetCount(), expected_count);
|
||||
ASSERT_EQ(out.GetBytes(), expected_bytes);
|
||||
|
||||
ASSERT_TRUE(flow.IsValid());
|
||||
ASSERT_FALSE(flow.HasGarbage());
|
||||
}
|
||||
|
||||
{
|
||||
const auto it = flows.find(5);
|
||||
ASSERT_NE(it, flows.end());
|
||||
|
||||
const auto& flow = it->second;
|
||||
|
||||
const auto& in = flow.GetInFlow();
|
||||
|
||||
constexpr uint64_t expected_in_count = 4;
|
||||
const uint64_t expected_in_bytes =
|
||||
blobs[2].GetExpectedBytes() + blobs[3].GetExpectedBytes() +
|
||||
blobs[4].GetExpectedBytes() + blobs[5].GetExpectedBytes();
|
||||
|
||||
ASSERT_EQ(in.GetCount(), expected_in_count);
|
||||
ASSERT_EQ(in.GetBytes(), expected_in_bytes);
|
||||
|
||||
const auto& out = flow.GetOutFlow();
|
||||
|
||||
constexpr uint64_t expected_out_count = 2;
|
||||
const uint64_t expected_out_bytes =
|
||||
blobs[2].GetExpectedBytes() + blobs[3].GetExpectedBytes();
|
||||
|
||||
ASSERT_EQ(out.GetCount(), expected_out_count);
|
||||
ASSERT_EQ(out.GetBytes(), expected_out_bytes);
|
||||
|
||||
ASSERT_TRUE(flow.IsValid());
|
||||
ASSERT_TRUE(flow.HasGarbage());
|
||||
ASSERT_EQ(flow.GetGarbageCount(), expected_in_count - expected_out_count);
|
||||
ASSERT_EQ(flow.GetGarbageBytes(), expected_in_bytes - expected_out_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(BlobGarbageMeterTest, PlainValue) {
|
||||
constexpr char user_key[] = "user_key";
|
||||
constexpr SequenceNumber seq = 123;
|
||||
|
||||
const InternalKey key(user_key, seq, kTypeValue);
|
||||
const Slice key_slice = key.Encode();
|
||||
|
||||
constexpr char value[] = "value";
|
||||
const Slice value_slice(value);
|
||||
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
ASSERT_OK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
ASSERT_OK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
ASSERT_TRUE(blob_garbage_meter.flows().empty());
|
||||
}
|
||||
|
||||
TEST(BlobGarbageMeterTest, CorruptInternalKey) {
|
||||
constexpr char corrupt_key[] = "i_am_corrupt";
|
||||
const Slice key_slice(corrupt_key);
|
||||
|
||||
constexpr char value[] = "value";
|
||||
const Slice value_slice(value);
|
||||
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
}
|
||||
|
||||
TEST(BlobGarbageMeterTest, CorruptBlobIndex) {
|
||||
constexpr char user_key[] = "user_key";
|
||||
constexpr SequenceNumber seq = 123;
|
||||
|
||||
const InternalKey key(user_key, seq, kTypeBlobIndex);
|
||||
const Slice key_slice = key.Encode();
|
||||
|
||||
constexpr char value[] = "i_am_not_a_blob_index";
|
||||
const Slice value_slice(value);
|
||||
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
}
|
||||
|
||||
TEST(BlobGarbageMeterTest, InlinedTTLBlobIndex) {
|
||||
constexpr char user_key[] = "user_key";
|
||||
constexpr SequenceNumber seq = 123;
|
||||
|
||||
const InternalKey key(user_key, seq, kTypeBlobIndex);
|
||||
const Slice key_slice = key.Encode();
|
||||
|
||||
constexpr uint64_t expiration = 1234567890;
|
||||
constexpr char inlined_value[] = "inlined";
|
||||
|
||||
std::string value;
|
||||
BlobIndex::EncodeInlinedTTL(&value, expiration, inlined_value);
|
||||
|
||||
const Slice value_slice(value);
|
||||
|
||||
BlobGarbageMeter blob_garbage_meter;
|
||||
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessInFlow(key_slice, value_slice));
|
||||
ASSERT_NOK(blob_garbage_meter.ProcessOutFlow(key_slice, value_slice));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -52,6 +52,9 @@ class BlobIndex {
|
||||
|
||||
BlobIndex() : type_(Type::kUnknown) {}
|
||||
|
||||
BlobIndex(const BlobIndex&) = default;
|
||||
BlobIndex& operator=(const BlobIndex&) = default;
|
||||
|
||||
bool IsInlined() const { return type_ == Type::kInlinedTTL; }
|
||||
|
||||
bool HasTTL() const {
|
||||
|
||||
@@ -107,7 +107,8 @@ struct BlobLogRecord {
|
||||
// Note that the offset field of BlobIndex actually points to the blob value
|
||||
// as opposed to the start of the blob record. The following method can
|
||||
// be used to calculate the adjustment needed to read the blob record header.
|
||||
static uint64_t CalculateAdjustmentForRecordHeader(uint64_t key_size) {
|
||||
static constexpr uint64_t CalculateAdjustmentForRecordHeader(
|
||||
uint64_t key_size) {
|
||||
return key_size + kHeaderSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "rocksdb/slice.h"
|
||||
|
||||
#define MAX_HEADER_SIZE(a, b, c) (a > b ? (a > c ? a : c) : (b > c ? b : c))
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class RandomAccessFileReader;
|
||||
@@ -69,10 +71,13 @@ class BlobLogSequentialReader {
|
||||
Statistics* statistics_;
|
||||
|
||||
Slice buffer_;
|
||||
char header_buf_[BlobLogRecord::kHeaderSize];
|
||||
char header_buf_[MAX_HEADER_SIZE(BlobLogHeader::kSize, BlobLogFooter::kSize,
|
||||
BlobLogRecord::kHeaderSize)];
|
||||
|
||||
// which byte to read next
|
||||
uint64_t next_byte_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#undef MAX_HEADER_SIZE
|
||||
@@ -4,8 +4,10 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include <array>
|
||||
#include <sstream>
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
@@ -16,7 +18,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class DBBlobBasicTest : public DBTestBase {
|
||||
protected:
|
||||
DBBlobBasicTest()
|
||||
: DBTestBase("/db_blob_basic_test", /* env_do_fsync */ false) {}
|
||||
: DBTestBase("db_blob_basic_test", /* env_do_fsync */ false) {}
|
||||
};
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob) {
|
||||
@@ -125,6 +127,237 @@ TEST_F(DBBlobBasicTest, MultiGetBlobs) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, MultiGetWithDirectIO) {
|
||||
Options options = GetDefaultOptions();
|
||||
|
||||
// First, create an external SST file ["b"].
|
||||
const std::string file_path = dbname_ + "/test.sst";
|
||||
{
|
||||
SstFileWriter sst_file_writer(EnvOptions(), GetDefaultOptions());
|
||||
Status s = sst_file_writer.Open(file_path);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(sst_file_writer.Put("b", "b_value"));
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
}
|
||||
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 1000;
|
||||
options.use_direct_reads = true;
|
||||
options.allow_ingest_behind = true;
|
||||
|
||||
// Open DB with fixed-prefix sst-partitioner so that compaction will cut
|
||||
// new table file when encountering a new key whose 1-byte prefix changes.
|
||||
constexpr size_t key_len = 1;
|
||||
options.sst_partitioner_factory =
|
||||
NewSstPartitionerFixedPrefixFactory(key_len);
|
||||
|
||||
Status s = TryReopen(options);
|
||||
if (s.IsInvalidArgument()) {
|
||||
ROCKSDB_GTEST_SKIP("This test requires direct IO support");
|
||||
return;
|
||||
}
|
||||
ASSERT_OK(s);
|
||||
|
||||
constexpr size_t num_keys = 3;
|
||||
constexpr size_t blob_size = 3000;
|
||||
|
||||
constexpr char first_key[] = "a";
|
||||
const std::string first_blob(blob_size, 'a');
|
||||
ASSERT_OK(Put(first_key, first_blob));
|
||||
|
||||
constexpr char second_key[] = "b";
|
||||
const std::string second_blob(2 * blob_size, 'b');
|
||||
ASSERT_OK(Put(second_key, second_blob));
|
||||
|
||||
constexpr char third_key[] = "d";
|
||||
const std::string third_blob(blob_size, 'd');
|
||||
ASSERT_OK(Put(third_key, third_blob));
|
||||
|
||||
// first_blob, second_blob and third_blob in the same blob file.
|
||||
// SST Blob file
|
||||
// L0 ["a", "b", "d"] |'aaaa', 'bbbb', 'dddd'|
|
||||
// | | | ^ ^ ^
|
||||
// | | | | | |
|
||||
// | | +---------|-------|--------+
|
||||
// | +-----------------|-------+
|
||||
// +-------------------------+
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr char fourth_key[] = "c";
|
||||
const std::string fourth_blob(blob_size, 'c');
|
||||
ASSERT_OK(Put(fourth_key, fourth_blob));
|
||||
// fourth_blob in another blob file.
|
||||
// SST Blob file SST Blob file
|
||||
// L0 ["a", "b", "d"] |'aaaa', 'bbbb', 'dddd'| ["c"] |'cccc'|
|
||||
// | | | ^ ^ ^ | ^
|
||||
// | | | | | | | |
|
||||
// | | +---------|-------|--------+ +-------+
|
||||
// | +-----------------|-------+
|
||||
// +-------------------------+
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
|
||||
// Due to the above sst partitioner, we get 4 L1 files. The blob files are
|
||||
// unchanged.
|
||||
// |'aaaa', 'bbbb', 'dddd'| |'cccc'|
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
// L0 | | | |
|
||||
// L1 ["a"] ["b"] ["c"] | | ["d"] |
|
||||
// | | | | | |
|
||||
// | | +---------|-------|---------------+
|
||||
// | +-----------------|-------+
|
||||
// +-------------------------+
|
||||
ASSERT_EQ(4, NumTableFilesAtLevel(/*level=*/1));
|
||||
|
||||
{
|
||||
// Ingest the external SST file into bottommost level.
|
||||
std::vector<std::string> ext_files{file_path};
|
||||
IngestExternalFileOptions opts;
|
||||
opts.ingest_behind = true;
|
||||
ASSERT_OK(
|
||||
db_->IngestExternalFile(db_->DefaultColumnFamily(), ext_files, opts));
|
||||
}
|
||||
|
||||
// Now the database becomes as follows.
|
||||
// |'aaaa', 'bbbb', 'dddd'| |'cccc'|
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
// L0 | | | |
|
||||
// L1 ["a"] ["b"] ["c"] | | ["d"] |
|
||||
// | | | | | |
|
||||
// | | +---------|-------|---------------+
|
||||
// | +-----------------|-------+
|
||||
// +-------------------------+
|
||||
//
|
||||
// L6 ["b"]
|
||||
|
||||
{
|
||||
// Compact ["b"] to bottommost level.
|
||||
Slice begin = Slice(second_key);
|
||||
Slice end = Slice(second_key);
|
||||
CompactRangeOptions cro;
|
||||
cro.bottommost_level_compaction = BottommostLevelCompaction::kForce;
|
||||
ASSERT_OK(db_->CompactRange(cro, &begin, &end));
|
||||
}
|
||||
|
||||
// |'aaaa', 'bbbb', 'dddd'| |'cccc'|
|
||||
// ^ ^ ^ ^
|
||||
// | | | |
|
||||
// L0 | | | |
|
||||
// L1 ["a"] ["c"] | | ["d"] |
|
||||
// | | | | |
|
||||
// | +---------|-------|---------------+
|
||||
// | +-----------------|-------+
|
||||
// +-------|-----------------+
|
||||
// |
|
||||
// L6 ["b"]
|
||||
ASSERT_EQ(3, NumTableFilesAtLevel(/*level=*/1));
|
||||
ASSERT_EQ(1, NumTableFilesAtLevel(/*level=*/6));
|
||||
|
||||
bool called = false;
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"RandomAccessFileReader::MultiRead:AlignedReqs", [&](void* arg) {
|
||||
auto* aligned_reqs = static_cast<std::vector<FSReadRequest>*>(arg);
|
||||
assert(aligned_reqs);
|
||||
ASSERT_EQ(1, aligned_reqs->size());
|
||||
called = true;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::array<Slice, num_keys> keys{{first_key, third_key, second_key}};
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
// The MultiGet(), when constructing the KeyContexts, will process the keys
|
||||
// in such order: a, d, b. The reason is that ["a"] and ["d"] are in L1,
|
||||
// while ["b"] resides in L6.
|
||||
// Consequently, the original FSReadRequest list prepared by
|
||||
// Version::MultiGetblob() will be for "a", "d" and "b". It is unsorted as
|
||||
// follows:
|
||||
//
|
||||
// ["a", offset=30, len=3033],
|
||||
// ["d", offset=9096, len=3033],
|
||||
// ["b", offset=3063, len=6033]
|
||||
//
|
||||
// If we do not sort them before calling MultiRead() in DirectIO, then the
|
||||
// underlying IO merging logic will yield two requests.
|
||||
//
|
||||
// [offset=0, len=4096] (for "a")
|
||||
// [offset=0, len=12288] (result of merging the request for "d" and "b")
|
||||
//
|
||||
// We need to sort them in Version::MultiGetBlob() so that the underlying
|
||||
// IO merging logic in DirectIO mode works as expected. The correct
|
||||
// behavior will be one aligned request:
|
||||
//
|
||||
// [offset=0, len=12288]
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_TRUE(called);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], first_blob);
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(values[1], third_blob);
|
||||
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], second_blob);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobsFromMultipleFiles) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr size_t kNumBlobFiles = 3;
|
||||
constexpr size_t kNumBlobsPerFile = 3;
|
||||
constexpr size_t kNumKeys = kNumBlobsPerFile * kNumBlobFiles;
|
||||
|
||||
std::vector<std::string> key_strs;
|
||||
std::vector<std::string> value_strs;
|
||||
for (size_t i = 0; i < kNumBlobFiles; ++i) {
|
||||
for (size_t j = 0; j < kNumBlobsPerFile; ++j) {
|
||||
std::string key = "key" + std::to_string(i) + "_" + std::to_string(j);
|
||||
std::string value =
|
||||
"value_as_blob" + std::to_string(i) + "_" + std::to_string(j);
|
||||
ASSERT_OK(Put(key, value));
|
||||
key_strs.push_back(key);
|
||||
value_strs.push_back(value);
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
assert(key_strs.size() == kNumKeys);
|
||||
std::array<Slice, kNumKeys> keys;
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
keys[i] = key_strs[i];
|
||||
}
|
||||
std::array<PinnableSlice, kNumKeys> values;
|
||||
std::array<Status, kNumKeys> statuses;
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), kNumKeys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ(value_strs[i], values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_CorruptIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -148,6 +381,83 @@ TEST_F(DBBlobBasicTest, GetBlob_CorruptIndex) {
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlob_CorruptIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
constexpr size_t kNumOfKeys = 3;
|
||||
std::array<std::string, kNumOfKeys> key_strs;
|
||||
std::array<std::string, kNumOfKeys> value_strs;
|
||||
std::array<Slice, kNumOfKeys + 1> keys;
|
||||
for (size_t i = 0; i < kNumOfKeys; ++i) {
|
||||
key_strs[i] = "foo" + std::to_string(i);
|
||||
value_strs[i] = "blob_value" + std::to_string(i);
|
||||
ASSERT_OK(Put(key_strs[i], value_strs[i]));
|
||||
keys[i] = key_strs[i];
|
||||
}
|
||||
|
||||
constexpr char key[] = "key";
|
||||
{
|
||||
// Fake a corrupt blob index.
|
||||
const std::string blob_index("foobar");
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
keys[kNumOfKeys] = Slice(static_cast<const char*>(key), sizeof(key) - 1);
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<PinnableSlice, kNumOfKeys + 1> values;
|
||||
std::array<Status, kNumOfKeys + 1> statuses;
|
||||
db_->MultiGet(ReadOptions(), dbfull()->DefaultColumnFamily(), kNumOfKeys + 1,
|
||||
keys.data(), values.data(), statuses.data(),
|
||||
/*sorted_input=*/false);
|
||||
for (size_t i = 0; i < kNumOfKeys + 1; ++i) {
|
||||
if (i != kNumOfKeys) {
|
||||
ASSERT_OK(statuses[i]);
|
||||
ASSERT_EQ("blob_value" + std::to_string(i), values[i]);
|
||||
} else {
|
||||
ASSERT_TRUE(statuses[i].IsCorruption());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlob_ExceedSoftLimit) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr size_t kNumOfKeys = 3;
|
||||
std::array<std::string, kNumOfKeys> key_bufs;
|
||||
std::array<std::string, kNumOfKeys> value_bufs;
|
||||
std::array<Slice, kNumOfKeys> keys;
|
||||
for (size_t i = 0; i < kNumOfKeys; ++i) {
|
||||
key_bufs[i] = "foo" + std::to_string(i);
|
||||
value_bufs[i] = "blob_value" + std::to_string(i);
|
||||
ASSERT_OK(Put(key_bufs[i], value_bufs[i]));
|
||||
keys[i] = key_bufs[i];
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<PinnableSlice, kNumOfKeys> values;
|
||||
std::array<Status, kNumOfKeys> statuses;
|
||||
ReadOptions read_opts;
|
||||
read_opts.value_size_soft_limit = 1;
|
||||
db_->MultiGet(read_opts, dbfull()->DefaultColumnFamily(), kNumOfKeys,
|
||||
keys.data(), values.data(), statuses.data(),
|
||||
/*sorted_input=*/true);
|
||||
for (const auto& s : statuses) {
|
||||
ASSERT_TRUE(s.IsAborted());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_InlinedTTLIndex) {
|
||||
constexpr uint64_t min_blob_size = 10;
|
||||
|
||||
@@ -310,6 +620,204 @@ TEST_F(DBBlobBasicTest, BestEffortsRecovery_MissingNewestBlobFile) {
|
||||
ASSERT_EQ("value" + std::to_string(kNumTableFiles - 2), value);
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetMergeBlobWithPut) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("Key1", "v1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("Key1", "v2"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("Key1", "v3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "Key1", &value));
|
||||
ASSERT_EQ(Get("Key1"), "v1,v2,v3");
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetMergeBlobWithPut) {
|
||||
constexpr size_t num_keys = 3;
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("Key0", "v0_0"));
|
||||
ASSERT_OK(Put("Key1", "v1_0"));
|
||||
ASSERT_OK(Put("Key2", "v2_0"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("Key0", "v0_1"));
|
||||
ASSERT_OK(Merge("Key1", "v1_1"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Merge("Key0", "v0_2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<Slice, num_keys> keys{{"Key0", "Key1", "Key2"}};
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], "v0_0,v0_1,v0_2");
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(values[1], "v1_0,v1_1");
|
||||
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], "v2_0");
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, Properties) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key1[] = "key1";
|
||||
constexpr size_t key1_size = sizeof(key1) - 1;
|
||||
|
||||
constexpr char key2[] = "key2";
|
||||
constexpr size_t key2_size = sizeof(key2) - 1;
|
||||
|
||||
constexpr char key3[] = "key3";
|
||||
constexpr size_t key3_size = sizeof(key3) - 1;
|
||||
|
||||
constexpr char blob[] = "0000000000";
|
||||
constexpr size_t blob_size = sizeof(blob) - 1;
|
||||
|
||||
ASSERT_OK(Put(key1, blob));
|
||||
ASSERT_OK(Put(key2, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr size_t first_blob_file_expected_size =
|
||||
BlobLogHeader::kSize +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key1_size) + blob_size +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key2_size) + blob_size +
|
||||
BlobLogFooter::kSize;
|
||||
|
||||
ASSERT_OK(Put(key3, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr size_t second_blob_file_expected_size =
|
||||
BlobLogHeader::kSize +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key3_size) + blob_size +
|
||||
BlobLogFooter::kSize;
|
||||
|
||||
constexpr size_t total_expected_size =
|
||||
first_blob_file_expected_size + second_blob_file_expected_size;
|
||||
|
||||
// Number of blob files
|
||||
uint64_t num_blob_files = 0;
|
||||
ASSERT_TRUE(
|
||||
db_->GetIntProperty(DB::Properties::kNumBlobFiles, &num_blob_files));
|
||||
ASSERT_EQ(num_blob_files, 2);
|
||||
|
||||
// Total size of live blob files
|
||||
uint64_t live_blob_file_size = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kLiveBlobFileSize,
|
||||
&live_blob_file_size));
|
||||
ASSERT_EQ(live_blob_file_size, total_expected_size);
|
||||
|
||||
// Total size of all blob files across all versions
|
||||
// Note: this should be the same as above since we only have one
|
||||
// version at this point.
|
||||
uint64_t total_blob_file_size = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kTotalBlobFileSize,
|
||||
&total_blob_file_size));
|
||||
ASSERT_EQ(total_blob_file_size, total_expected_size);
|
||||
|
||||
// Delete key2 to create some garbage
|
||||
ASSERT_OK(Delete(key2));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
constexpr size_t expected_garbage_size =
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key2_size) + blob_size;
|
||||
|
||||
// Blob file stats
|
||||
std::string blob_stats;
|
||||
ASSERT_TRUE(db_->GetProperty(DB::Properties::kBlobStats, &blob_stats));
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "Number of blob files: 2\nTotal size of blob files: "
|
||||
<< total_expected_size
|
||||
<< "\nTotal size of garbage in blob files: " << expected_garbage_size
|
||||
<< '\n';
|
||||
|
||||
ASSERT_EQ(blob_stats, oss.str());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, PropertiesMultiVersion) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key1[] = "key1";
|
||||
constexpr char key2[] = "key2";
|
||||
constexpr char key3[] = "key3";
|
||||
|
||||
constexpr size_t key_size = sizeof(key1) - 1;
|
||||
static_assert(sizeof(key2) - 1 == key_size, "unexpected size: key2");
|
||||
static_assert(sizeof(key3) - 1 == key_size, "unexpected size: key3");
|
||||
|
||||
constexpr char blob[] = "0000000000";
|
||||
constexpr size_t blob_size = sizeof(blob) - 1;
|
||||
|
||||
ASSERT_OK(Put(key1, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(key2, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Create an iterator to keep the current version alive
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(ReadOptions()));
|
||||
ASSERT_OK(iter->status());
|
||||
|
||||
// Note: the Delete and subsequent compaction results in the first blob file
|
||||
// not making it to the final version. (It is still part of the previous
|
||||
// version kept alive by the iterator though.) On the other hand, the Put
|
||||
// results in a third blob file.
|
||||
ASSERT_OK(Delete(key1));
|
||||
ASSERT_OK(Put(key3, blob));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
// Total size of all blob files across all versions: between the two versions,
|
||||
// we should have three blob files of the same size with one blob each.
|
||||
// The version kept alive by the iterator contains the first and the second
|
||||
// blob file, while the final version contains the second and the third blob
|
||||
// file. (The second blob file is thus shared by the two versions but should
|
||||
// be counted only once.)
|
||||
uint64_t total_blob_file_size = 0;
|
||||
ASSERT_TRUE(db_->GetIntProperty(DB::Properties::kTotalBlobFileSize,
|
||||
&total_blob_file_size));
|
||||
ASSERT_EQ(total_blob_file_size,
|
||||
3 * (BlobLogHeader::kSize +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size + BlobLogFooter::kSize));
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
@@ -322,11 +830,21 @@ class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
class DBBlobBasicIOErrorMultiGetTest : public DBBlobBasicIOErrorTest {
|
||||
public:
|
||||
DBBlobBasicIOErrorMultiGetTest() : DBBlobBasicIOErrorTest() {}
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlobBasicTest, DBBlobBasicIOErrorTest,
|
||||
::testing::ValuesIn(std::vector<std::string>{
|
||||
"BlobFileReader::OpenFile:NewRandomAccessFile",
|
||||
"BlobFileReader::GetBlob:ReadFromFile"}));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlobBasicTest, DBBlobBasicIOErrorMultiGetTest,
|
||||
::testing::ValuesIn(std::vector<std::string>{
|
||||
"BlobFileReader::OpenFile:NewRandomAccessFile",
|
||||
"BlobFileReader::MultiGetBlob:ReadFromFile"}));
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, GetBlob_IOError) {
|
||||
Options options;
|
||||
options.env = fault_injection_env_.get();
|
||||
@@ -356,7 +874,7 @@ TEST_P(DBBlobBasicIOErrorTest, GetBlob_IOError) {
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, MultiGetBlobs_IOError) {
|
||||
TEST_P(DBBlobBasicIOErrorMultiGetTest, MultiGetBlobs_IOError) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
@@ -398,6 +916,53 @@ TEST_P(DBBlobBasicIOErrorTest, MultiGetBlobs_IOError) {
|
||||
ASSERT_TRUE(statuses[1].IsIOError());
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorMultiGetTest, MultipleBlobFiles) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr size_t num_keys = 2;
|
||||
|
||||
constexpr char key1[] = "key1";
|
||||
constexpr char value1[] = "blob1";
|
||||
|
||||
ASSERT_OK(Put(key1, value1));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
constexpr char key2[] = "key2";
|
||||
constexpr char value2[] = "blob2";
|
||||
|
||||
ASSERT_OK(Put(key2, value2));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<Slice, num_keys> keys{{key1, key2}};
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
bool first_blob_file = true;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
sync_point_, [&first_blob_file, this](void* /* arg */) {
|
||||
if (first_blob_file) {
|
||||
first_blob_file = false;
|
||||
return;
|
||||
}
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys,
|
||||
keys.data(), values.data(), statuses.data());
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(value1, values[0]);
|
||||
ASSERT_TRUE(statuses[1].IsIOError());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class ReadBlobCompactionFilter : public CompactionFilter {
|
||||
@@ -453,8 +1018,17 @@ TEST_P(DBBlobBasicIOErrorTest, CompactionFilterReadBlob_IOError) {
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -4,17 +4,17 @@
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBBlobCompactionTest : public DBTestBase {
|
||||
public:
|
||||
explicit DBBlobCompactionTest()
|
||||
: DBTestBase("/db_blob_compaction_test", /*env_do_fsync=*/false) {}
|
||||
: DBTestBase("db_blob_compaction_test", /*env_do_fsync=*/false) {}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const std::vector<InternalStats::CompactionStats>& GetCompactionStats() {
|
||||
@@ -152,6 +152,31 @@ class AlwaysKeepFilter : public CompactionFilter {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
};
|
||||
|
||||
class SkipUntilFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit SkipUntilFilter(std::string skip_until)
|
||||
: skip_until_(std::move(skip_until)) {}
|
||||
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.skip.until";
|
||||
}
|
||||
|
||||
CompactionFilter::Decision FilterV2(int /* level */, const Slice& /* key */,
|
||||
ValueType /* value_type */,
|
||||
const Slice& /* existing_value */,
|
||||
std::string* /* new_value */,
|
||||
std::string* skip_until) const override {
|
||||
assert(skip_until);
|
||||
*skip_until = skip_until_;
|
||||
|
||||
return CompactionFilter::Decision::kRemoveAndSkipUntil;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string skip_until_;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class DBBlobBadCompactionFilterTest
|
||||
@@ -254,6 +279,49 @@ TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, SkipUntilFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new SkipUntilFilter("z"));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
|
||||
Reopen(options);
|
||||
|
||||
const std::vector<std::string> keys{"a", "b", "c"};
|
||||
const std::vector<std::string> values{"a_value", "b_value", "c_value"};
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
ASSERT_OK(Put(keys[i], values[i]));
|
||||
}
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
int process_in_flow_called = 0;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobCountingIterator::UpdateAndCountBlobIfNeeded:ProcessInFlow",
|
||||
[&process_in_flow_called](void* /* arg */) { ++process_in_flow_called; });
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /* begin */ nullptr,
|
||||
/* end */ nullptr));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
for (const auto& key : keys) {
|
||||
ASSERT_EQ(Get(key), "NOT_FOUND");
|
||||
}
|
||||
|
||||
// Make sure SkipUntil was performed using iteration rather than Seek
|
||||
ASSERT_EQ(process_in_flow_called, keys.size());
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBadCompactionFilterTest, BadDecisionFromCompactionFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
@@ -390,10 +458,151 @@ TEST_F(DBBlobCompactionTest, CompactionFilterReadBlobAndKeep) {
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, TrackGarbage) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// First table+blob file pair: 4 blobs with different keys
|
||||
constexpr char first_key[] = "first_key";
|
||||
constexpr char first_value[] = "first_value";
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char second_value[] = "second_value";
|
||||
constexpr char third_key[] = "third_key";
|
||||
constexpr char third_value[] = "third_value";
|
||||
constexpr char fourth_key[] = "fourth_key";
|
||||
constexpr char fourth_value[] = "fourth_value";
|
||||
|
||||
ASSERT_OK(Put(first_key, first_value));
|
||||
ASSERT_OK(Put(second_key, second_value));
|
||||
ASSERT_OK(Put(third_key, third_value));
|
||||
ASSERT_OK(Put(fourth_key, fourth_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Second table+blob file pair: overwrite 2 existing keys
|
||||
constexpr char new_first_value[] = "new_first_value";
|
||||
constexpr char new_second_value[] = "new_second_value";
|
||||
|
||||
ASSERT_OK(Put(first_key, new_first_value));
|
||||
ASSERT_OK(Put(second_key, new_second_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Compact them together. The first blob file should have 2 garbage blobs
|
||||
// corresponding to the 2 overwritten keys.
|
||||
constexpr Slice* begin = nullptr;
|
||||
constexpr Slice* end = nullptr;
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), begin, end));
|
||||
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
Version* const current = cfd->current();
|
||||
assert(current);
|
||||
|
||||
const VersionStorageInfo* const storage_info = current->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
const auto& blob_files = storage_info->GetBlobFiles();
|
||||
ASSERT_EQ(blob_files.size(), 2);
|
||||
|
||||
{
|
||||
auto it = blob_files.begin();
|
||||
const auto& meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
constexpr uint64_t first_expected_bytes =
|
||||
sizeof(first_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(first_key) -
|
||||
1);
|
||||
constexpr uint64_t second_expected_bytes =
|
||||
sizeof(second_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(second_key) -
|
||||
1);
|
||||
constexpr uint64_t third_expected_bytes =
|
||||
sizeof(third_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(third_key) -
|
||||
1);
|
||||
constexpr uint64_t fourth_expected_bytes =
|
||||
sizeof(fourth_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(fourth_key) -
|
||||
1);
|
||||
|
||||
ASSERT_EQ(meta->GetTotalBlobCount(), 4);
|
||||
ASSERT_EQ(meta->GetTotalBlobBytes(),
|
||||
first_expected_bytes + second_expected_bytes +
|
||||
third_expected_bytes + fourth_expected_bytes);
|
||||
ASSERT_EQ(meta->GetGarbageBlobCount(), 2);
|
||||
ASSERT_EQ(meta->GetGarbageBlobBytes(),
|
||||
first_expected_bytes + second_expected_bytes);
|
||||
}
|
||||
|
||||
{
|
||||
auto it = blob_files.rbegin();
|
||||
const auto& meta = it->second;
|
||||
assert(meta);
|
||||
|
||||
constexpr uint64_t new_first_expected_bytes =
|
||||
sizeof(new_first_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(first_key) -
|
||||
1);
|
||||
constexpr uint64_t new_second_expected_bytes =
|
||||
sizeof(new_second_value) - 1 +
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(sizeof(second_key) -
|
||||
1);
|
||||
|
||||
ASSERT_EQ(meta->GetTotalBlobCount(), 2);
|
||||
ASSERT_EQ(meta->GetTotalBlobBytes(),
|
||||
new_first_expected_bytes + new_second_expected_bytes);
|
||||
ASSERT_EQ(meta->GetGarbageBlobCount(), 0);
|
||||
ASSERT_EQ(meta->GetGarbageBlobBytes(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, MergeBlobWithBase) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
options.disable_auto_compactions = true;
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_OK(Put("Key1", "v1_1"));
|
||||
ASSERT_OK(Put("Key2", "v2_1"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Merge("Key1", "v1_2"));
|
||||
ASSERT_OK(Merge("Key2", "v2_2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Merge("Key1", "v1_3"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
ASSERT_EQ(Get("Key1"), "v1_1,v1_2,v1_3");
|
||||
ASSERT_EQ(Get("Key2"), "v2_1,v2_2");
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
class DBBlobCorruptionTest : public DBTestBase {
|
||||
protected:
|
||||
DBBlobCorruptionTest()
|
||||
: DBTestBase("/db_blob_corruption_test", /* env_do_fsync */ false) {}
|
||||
: DBTestBase("db_blob_corruption_test", /* env_do_fsync */ false) {}
|
||||
|
||||
void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) {
|
||||
// Pick file to corrupt
|
||||
@@ -74,8 +74,17 @@ TEST_F(DBBlobCorruptionTest, VerifyWholeBlobFileChecksum) {
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -41,8 +41,7 @@ class DBBlobIndexTest : public DBTestBase {
|
||||
Tier::kImmutableMemtables,
|
||||
Tier::kL0SstFile, Tier::kLnSstFile};
|
||||
|
||||
DBBlobIndexTest()
|
||||
: DBTestBase("/db_blob_index_test", /*env_do_fsync=*/true) {}
|
||||
DBBlobIndexTest() : DBTestBase("db_blob_index_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
ColumnFamilyHandle* cfh() { return dbfull()->DefaultColumnFamily(); }
|
||||
|
||||
@@ -399,7 +398,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
create_normal_iterator);
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_normal_iterator);
|
||||
verify(11, Status::kNotSupported, "", "", create_normal_iterator);
|
||||
verify(11, Status::kCorruption, "", "", create_normal_iterator);
|
||||
verify(13, Status::kOk,
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
@@ -418,7 +417,11 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
if (tier <= kImmutableMemtables) {
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
} else {
|
||||
verify(11, Status::kCorruption, "", "", create_blob_iterator);
|
||||
}
|
||||
verify(13, Status::kOk,
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
@@ -440,7 +443,11 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_blob_iterator, check_is_blob(false));
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
if (tier <= kImmutableMemtables) {
|
||||
verify(11, Status::kNotSupported, "", "", create_blob_iterator);
|
||||
} else {
|
||||
verify(11, Status::kCorruption, "", "", create_blob_iterator);
|
||||
}
|
||||
verify(13, Status::kOk,
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
get_value(13, 2) + "," + get_value(13, 1) + "," + get_value(13, 0),
|
||||
@@ -455,10 +462,119 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobIndexTest, IntegratedBlobIterate) {
|
||||
const std::vector<std::vector<std::string>> data = {
|
||||
/*00*/ {"Put"},
|
||||
/*01*/ {"Put", "Merge", "Merge", "Merge"},
|
||||
/*02*/ {"Put"}};
|
||||
|
||||
auto get_key = [](size_t index) { return ("key" + std::to_string(index)); };
|
||||
|
||||
auto get_value = [&](size_t index, size_t version) {
|
||||
return get_key(index) + "_value" + ToString(version);
|
||||
};
|
||||
|
||||
auto check_iterator = [&](Iterator* iterator, Status expected_status,
|
||||
const Slice& expected_value) {
|
||||
ASSERT_EQ(expected_status, iterator->status());
|
||||
if (expected_status.ok()) {
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_EQ(expected_value, iterator->value());
|
||||
} else {
|
||||
ASSERT_FALSE(iterator->Valid());
|
||||
}
|
||||
};
|
||||
|
||||
auto verify = [&](size_t index, Status expected_status,
|
||||
const Slice& expected_value) {
|
||||
// Seek
|
||||
{
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
std::unique_ptr<Iterator> iterator_guard(iterator);
|
||||
ASSERT_OK(iterator->status());
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->Seek(get_key(index));
|
||||
check_iterator(iterator, expected_status, expected_value);
|
||||
}
|
||||
// Next
|
||||
{
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
std::unique_ptr<Iterator> iterator_guard(iterator);
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->Seek(get_key(index - 1));
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_OK(iterator->status());
|
||||
iterator->Next();
|
||||
check_iterator(iterator, expected_status, expected_value);
|
||||
}
|
||||
// SeekForPrev
|
||||
{
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
std::unique_ptr<Iterator> iterator_guard(iterator);
|
||||
ASSERT_OK(iterator->status());
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->SeekForPrev(get_key(index));
|
||||
check_iterator(iterator, expected_status, expected_value);
|
||||
}
|
||||
// Prev
|
||||
{
|
||||
Iterator* iterator = db_->NewIterator(ReadOptions());
|
||||
std::unique_ptr<Iterator> iterator_guard(iterator);
|
||||
iterator->Seek(get_key(index + 1));
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_OK(iterator->status());
|
||||
iterator->Prev();
|
||||
check_iterator(iterator, expected_status, expected_value);
|
||||
}
|
||||
};
|
||||
|
||||
Options options = GetTestOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// fill data
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
for (size_t j = 0; j < data[i].size(); j++) {
|
||||
std::string key = get_key(i);
|
||||
std::string value = get_value(i, j);
|
||||
if (data[i][j] == "Put") {
|
||||
ASSERT_OK(Put(key, value));
|
||||
ASSERT_OK(Flush());
|
||||
} else if (data[i][j] == "Merge") {
|
||||
ASSERT_OK(Merge(key, value));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string expected_value = get_value(1, 0) + "," + get_value(1, 1) + "," +
|
||||
get_value(1, 2) + "," + get_value(1, 3);
|
||||
Status expected_status;
|
||||
verify(1, expected_status, expected_value);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Test DBIter::FindValueForCurrentKeyUsingSeek flow.
|
||||
ASSERT_OK(dbfull()->SetOptions(cfh(),
|
||||
{{"max_sequential_skip_in_iterations", "0"}}));
|
||||
verify(1, expected_status, expected_value);
|
||||
#endif // !ROCKSDB_LITE
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
extern "C" {
|
||||
void RegisterCustomObjects(int argc, char** argv);
|
||||
}
|
||||
#else
|
||||
void RegisterCustomObjects(int /*argc*/, char** /*argv*/) {}
|
||||
#endif // !ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
RegisterCustomObjects(argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+44
-13
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/event_helpers.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/merge_helper.h"
|
||||
@@ -65,11 +64,13 @@ Status BuildTable(
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, IOStatus* io_status,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, EventLogger* event_logger,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCreationReason blob_creation_reason, EventLogger* event_logger,
|
||||
int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, Env::WriteLifeTimeHint write_hint,
|
||||
const std::string* full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback, uint64_t* num_input_entries) {
|
||||
BlobFileCompletionCallback* blob_callback, uint64_t* num_input_entries,
|
||||
uint64_t* memtable_payload_bytes, uint64_t* memtable_garbage_bytes) {
|
||||
assert((tboptions.column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
tboptions.column_family_name.empty());
|
||||
@@ -89,9 +90,12 @@ Status BuildTable(
|
||||
new CompactionRangeDelAggregator(&tboptions.internal_comparator,
|
||||
snapshots));
|
||||
uint64_t num_unfragmented_tombstones = 0;
|
||||
uint64_t total_tombstone_payload_bytes = 0;
|
||||
for (auto& range_del_iter : range_del_iters) {
|
||||
num_unfragmented_tombstones +=
|
||||
range_del_iter->num_unfragmented_tombstones();
|
||||
total_tombstone_payload_bytes +=
|
||||
range_del_iter->total_tombstone_payload_bytes();
|
||||
range_del_agg->AddTombstones(std::move(range_del_iter));
|
||||
}
|
||||
|
||||
@@ -161,7 +165,7 @@ Status BuildTable(
|
||||
std::move(file), fname, file_options, ioptions.clock, io_tracer,
|
||||
ioptions.stats, ioptions.listeners,
|
||||
ioptions.file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kTableFile)));
|
||||
tmp_set.Contains(FileType::kTableFile), false));
|
||||
|
||||
builder = NewTableBuilder(tboptions, file_writer.get());
|
||||
}
|
||||
@@ -174,12 +178,12 @@ Status BuildTable(
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
(mutable_cf_options.enable_blob_files && blob_file_additions)
|
||||
? new BlobFileBuilder(versions, fs, &ioptions, &mutable_cf_options,
|
||||
&file_options, job_id,
|
||||
tboptions.column_family_id,
|
||||
tboptions.column_family_name, io_priority,
|
||||
write_hint, io_tracer, blob_callback,
|
||||
&blob_file_paths, blob_file_additions)
|
||||
? new BlobFileBuilder(
|
||||
versions, fs, &ioptions, &mutable_cf_options, &file_options,
|
||||
job_id, tboptions.column_family_id,
|
||||
tboptions.column_family_name, io_priority, write_hint,
|
||||
io_tracer, blob_callback, blob_creation_reason,
|
||||
&blob_file_paths, blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
CompactionIterator c_iter(
|
||||
@@ -191,7 +195,8 @@ Status BuildTable(
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr,
|
||||
/*preserve_deletes_seqnum=*/0, /*manual_compaction_paused=*/nullptr,
|
||||
db_options.info_log, full_history_ts_low);
|
||||
/*manual_compaction_canceled=*/nullptr, db_options.info_log,
|
||||
full_history_ts_low);
|
||||
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
@@ -199,6 +204,8 @@ Status BuildTable(
|
||||
const Slice& value = c_iter.value();
|
||||
const ParsedInternalKey& ikey = c_iter.ikey();
|
||||
// Generate a rolling 64-bit hash of the key and values
|
||||
// Note :
|
||||
// Here "key" integrates 'sequence_number'+'kType'+'user key'.
|
||||
s = output_validator.Add(key, value);
|
||||
if (!s.ok()) {
|
||||
break;
|
||||
@@ -253,6 +260,25 @@ Status BuildTable(
|
||||
meta->marked_for_compaction = builder->NeedCompact();
|
||||
assert(meta->fd.GetFileSize() > 0);
|
||||
tp = builder->GetTableProperties(); // refresh now that builder is finished
|
||||
if (memtable_payload_bytes != nullptr &&
|
||||
memtable_garbage_bytes != nullptr) {
|
||||
const CompactionIterationStats& ci_stats = c_iter.iter_stats();
|
||||
uint64_t total_payload_bytes = ci_stats.total_input_raw_key_bytes +
|
||||
ci_stats.total_input_raw_value_bytes +
|
||||
total_tombstone_payload_bytes;
|
||||
uint64_t total_payload_bytes_written =
|
||||
(tp.raw_key_size + tp.raw_value_size);
|
||||
// Prevent underflow, which may still happen at this point
|
||||
// since we only support inserts, deletes, and deleteRanges.
|
||||
if (total_payload_bytes_written <= total_payload_bytes) {
|
||||
*memtable_payload_bytes = total_payload_bytes;
|
||||
*memtable_garbage_bytes =
|
||||
total_payload_bytes - total_payload_bytes_written;
|
||||
} else {
|
||||
*memtable_payload_bytes = 0;
|
||||
*memtable_garbage_bytes = 0;
|
||||
}
|
||||
}
|
||||
if (table_properties) {
|
||||
*table_properties = tp;
|
||||
}
|
||||
@@ -285,7 +311,7 @@ Status BuildTable(
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
} else {
|
||||
blob_file_builder->Abandon();
|
||||
blob_file_builder->Abandon(s);
|
||||
}
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
@@ -354,14 +380,19 @@ Status BuildTable(
|
||||
}
|
||||
}
|
||||
|
||||
Status status_for_listener = s;
|
||||
if (meta->fd.GetFileSize() == 0) {
|
||||
fname = "(nil)";
|
||||
if (s.ok()) {
|
||||
status_for_listener = Status::Aborted("Empty SST file not kept");
|
||||
}
|
||||
}
|
||||
// Output to event logger and fire events.
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, tboptions.column_family_name,
|
||||
fname, job_id, meta->fd, meta->oldest_blob_file_number, tp,
|
||||
tboptions.reason, s, file_checksum, file_checksum_func_name);
|
||||
tboptions.reason, status_for_listener, file_checksum,
|
||||
file_checksum_func_name);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
+4
-1
@@ -60,12 +60,15 @@ extern Status BuildTable(
|
||||
SnapshotChecker* snapshot_checker, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, IOStatus* io_status,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
BlobFileCreationReason blob_creation_reason,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr,
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const std::string* full_history_ts_low = nullptr,
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
uint64_t* num_input_entries = nullptr);
|
||||
uint64_t* num_input_entries = nullptr,
|
||||
uint64_t* memtable_payload_bytes = nullptr,
|
||||
uint64_t* memtable_garbage_bytes = nullptr);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -88,6 +88,7 @@ using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::NewCompactOnDeletionCollectorFactory;
|
||||
using ROCKSDB_NAMESPACE::NewGenericRateLimiter;
|
||||
using ROCKSDB_NAMESPACE::NewLRUCache;
|
||||
using ROCKSDB_NAMESPACE::NewRibbonFilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::OptimisticTransactionDB;
|
||||
using ROCKSDB_NAMESPACE::OptimisticTransactionOptions;
|
||||
using ROCKSDB_NAMESPACE::Options;
|
||||
@@ -2750,6 +2751,15 @@ double rocksdb_options_get_blob_gc_age_cutoff(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_garbage_collection_age_cutoff;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_gc_force_threshold(rocksdb_options_t* opt,
|
||||
double val) {
|
||||
opt->rep.blob_garbage_collection_force_threshold = val;
|
||||
}
|
||||
|
||||
double rocksdb_options_get_blob_gc_force_threshold(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_garbage_collection_force_threshold;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_num_levels(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.num_levels = n;
|
||||
}
|
||||
@@ -3028,6 +3038,11 @@ unsigned char rocksdb_options_get_advise_random_on_open(
|
||||
return opt->rep.advise_random_on_open;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_experimental_mempurge_threshold(rocksdb_options_t* opt,
|
||||
double v) {
|
||||
opt->rep.experimental_mempurge_threshold = v;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_access_hint_on_compaction_start(
|
||||
rocksdb_options_t* opt, int v) {
|
||||
switch(v) {
|
||||
@@ -3834,7 +3849,8 @@ void rocksdb_filterpolicy_destroy(rocksdb_filterpolicy_t* filter) {
|
||||
delete filter;
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(int bits_per_key, bool original_format) {
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(
|
||||
double bits_per_key, bool original_format) {
|
||||
// Make a rocksdb_filterpolicy_t, but override all of its methods so
|
||||
// they delegate to a NewBloomFilterPolicy() instead of user
|
||||
// supplied C functions.
|
||||
@@ -3869,14 +3885,63 @@ rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_format(int bits_per_ke
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_full(int bits_per_key) {
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom_full(
|
||||
double bits_per_key) {
|
||||
return rocksdb_filterpolicy_create_bloom_format(bits_per_key, false);
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom(int bits_per_key) {
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_bloom(double bits_per_key) {
|
||||
return rocksdb_filterpolicy_create_bloom_format(bits_per_key, true);
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_ribbon_format(
|
||||
double bloom_equivalent_bits_per_key, int bloom_before_level) {
|
||||
// Make a rocksdb_filterpolicy_t, but override all of its methods so
|
||||
// they delegate to a NewRibbonFilterPolicy() instead of user
|
||||
// supplied C functions.
|
||||
struct Wrapper : public rocksdb_filterpolicy_t {
|
||||
const FilterPolicy* rep_;
|
||||
~Wrapper() override { delete rep_; }
|
||||
const char* Name() const override { return rep_->Name(); }
|
||||
void CreateFilter(const Slice* keys, int n,
|
||||
std::string* dst) const override {
|
||||
return rep_->CreateFilter(keys, n, dst);
|
||||
}
|
||||
bool KeyMayMatch(const Slice& key, const Slice& filter) const override {
|
||||
return rep_->KeyMayMatch(key, filter);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::FilterBitsBuilder* GetBuilderWithContext(
|
||||
const ROCKSDB_NAMESPACE::FilterBuildingContext& context)
|
||||
const override {
|
||||
return rep_->GetBuilderWithContext(context);
|
||||
}
|
||||
ROCKSDB_NAMESPACE::FilterBitsReader* GetFilterBitsReader(
|
||||
const Slice& contents) const override {
|
||||
return rep_->GetFilterBitsReader(contents);
|
||||
}
|
||||
static void DoNothing(void*) {}
|
||||
};
|
||||
Wrapper* wrapper = new Wrapper;
|
||||
wrapper->rep_ =
|
||||
NewRibbonFilterPolicy(bloom_equivalent_bits_per_key, bloom_before_level);
|
||||
wrapper->state_ = nullptr;
|
||||
wrapper->delete_filter_ = nullptr;
|
||||
wrapper->destructor_ = &Wrapper::DoNothing;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_ribbon(
|
||||
double bloom_equivalent_bits_per_key) {
|
||||
return rocksdb_filterpolicy_create_ribbon_format(
|
||||
bloom_equivalent_bits_per_key, /*bloom_before_level = disabled*/ -1);
|
||||
}
|
||||
|
||||
rocksdb_filterpolicy_t* rocksdb_filterpolicy_create_ribbon_hybrid(
|
||||
double bloom_equivalent_bits_per_key, int bloom_before_level) {
|
||||
return rocksdb_filterpolicy_create_ribbon_format(
|
||||
bloom_equivalent_bits_per_key, bloom_before_level);
|
||||
}
|
||||
|
||||
rocksdb_mergeoperator_t* rocksdb_mergeoperator_create(
|
||||
void* state, void (*destructor)(void*),
|
||||
char* (*full_merge)(void*, const char* key, size_t key_length,
|
||||
@@ -4828,6 +4893,27 @@ void rocksdb_optimistictransaction_options_set_set_snapshot(
|
||||
opt->rep.set_snapshot = v;
|
||||
}
|
||||
|
||||
char* rocksdb_optimistictransactiondb_property_value(
|
||||
rocksdb_optimistictransactiondb_t* db, const char* propname) {
|
||||
std::string tmp;
|
||||
if (db->rep->GetProperty(Slice(propname), &tmp)) {
|
||||
// We use strdup() since we expect human readable output.
|
||||
return strdup(tmp.c_str());
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int rocksdb_optimistictransactiondb_property_int(
|
||||
rocksdb_optimistictransactiondb_t* db, const char* propname,
|
||||
uint64_t* out_val) {
|
||||
if (db->rep->GetIntProperty(Slice(propname), out_val)) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_column_family_handle_t* rocksdb_transactiondb_create_column_family(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_options_t* column_family_options,
|
||||
@@ -4898,6 +4984,27 @@ void rocksdb_transactiondb_release_snapshot(
|
||||
delete snapshot;
|
||||
}
|
||||
|
||||
char* rocksdb_transactiondb_property_value(rocksdb_transactiondb_t* db,
|
||||
const char* propname) {
|
||||
std::string tmp;
|
||||
if (db->rep->GetProperty(Slice(propname), &tmp)) {
|
||||
// We use strdup() since we expect human readable output.
|
||||
return strdup(tmp.c_str());
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int rocksdb_transactiondb_property_int(rocksdb_transactiondb_t* db,
|
||||
const char* propname,
|
||||
uint64_t* out_val) {
|
||||
if (db->rep->GetIntProperty(Slice(propname), out_val)) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
rocksdb_transaction_t* rocksdb_transaction_begin(
|
||||
rocksdb_transactiondb_t* txn_db,
|
||||
const rocksdb_writeoptions_t* write_options,
|
||||
@@ -5300,12 +5407,31 @@ rocksdb_transaction_t* rocksdb_optimistictransaction_begin(
|
||||
return old_txn;
|
||||
}
|
||||
|
||||
// Write batch into OptimisticTransactionDB
|
||||
void rocksdb_optimistictransactiondb_write(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db,
|
||||
const rocksdb_writeoptions_t* options, rocksdb_writebatch_t* batch,
|
||||
char** errptr) {
|
||||
SaveError(errptr, otxn_db->rep->Write(options->rep, &batch->rep));
|
||||
}
|
||||
|
||||
void rocksdb_optimistictransactiondb_close(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db) {
|
||||
delete otxn_db->rep;
|
||||
delete otxn_db;
|
||||
}
|
||||
|
||||
rocksdb_checkpoint_t* rocksdb_optimistictransactiondb_checkpoint_object_create(
|
||||
rocksdb_optimistictransactiondb_t* otxn_db, char** errptr) {
|
||||
Checkpoint* checkpoint;
|
||||
if (SaveError(errptr, Checkpoint::Create(otxn_db->rep, &checkpoint))) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_checkpoint_t* result = new rocksdb_checkpoint_t;
|
||||
result->rep = checkpoint;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_free(void* ptr) { free(ptr); }
|
||||
|
||||
rocksdb_pinnableslice_t* rocksdb_get_pinned(
|
||||
|
||||
+21
-9
@@ -1043,19 +1043,25 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
StartPhase("filter");
|
||||
for (run = 0; run <= 2; run++) {
|
||||
// First run uses custom filter
|
||||
// Second run uses old block-based bloom filter
|
||||
// Third run uses full bloom filter
|
||||
for (run = 0; run <= 4; run++) {
|
||||
// run=0 uses custom filter
|
||||
// run=1 uses old block-based bloom filter
|
||||
// run=2 run uses full bloom filter
|
||||
// run=3 uses Ribbon
|
||||
// run=4 uses Ribbon-Bloom hybrid configuration
|
||||
CheckNoError(err);
|
||||
rocksdb_filterpolicy_t* policy;
|
||||
if (run == 0) {
|
||||
policy = rocksdb_filterpolicy_create(NULL, FilterDestroy, FilterCreate,
|
||||
FilterKeyMatch, NULL, FilterName);
|
||||
} else if (run == 1) {
|
||||
policy = rocksdb_filterpolicy_create_bloom(8);
|
||||
policy = rocksdb_filterpolicy_create_bloom(8.0);
|
||||
} else if (run == 2) {
|
||||
policy = rocksdb_filterpolicy_create_bloom_full(8.0);
|
||||
} else if (run == 3) {
|
||||
policy = rocksdb_filterpolicy_create_ribbon(8.0);
|
||||
} else {
|
||||
policy = rocksdb_filterpolicy_create_bloom_full(8);
|
||||
policy = rocksdb_filterpolicy_create_ribbon_hybrid(8.0, 1);
|
||||
}
|
||||
rocksdb_block_based_options_set_filter_policy(table_options, policy);
|
||||
|
||||
@@ -1121,9 +1127,12 @@ int main(int argc, char** argv) {
|
||||
} else if (run == 1) {
|
||||
// Essentially a fingerprint of the block-based Bloom schema
|
||||
CheckCondition(hits == 241);
|
||||
} else {
|
||||
} else if (run == 2 || run == 4) {
|
||||
// Essentially a fingerprint of full Bloom schema, format_version=5
|
||||
CheckCondition(hits == 188);
|
||||
} else {
|
||||
// Essentially a fingerprint of Ribbon schema
|
||||
CheckCondition(hits == 226);
|
||||
}
|
||||
CheckCondition(
|
||||
(keys_to_query - hits) ==
|
||||
@@ -1784,8 +1793,11 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_enable_blob_gc(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_gc(o));
|
||||
|
||||
rocksdb_options_set_blob_gc_age_cutoff(o, 0.75);
|
||||
CheckCondition(0.75 == rocksdb_options_get_blob_gc_age_cutoff(o));
|
||||
rocksdb_options_set_blob_gc_age_cutoff(o, 0.5);
|
||||
CheckCondition(0.5 == rocksdb_options_get_blob_gc_age_cutoff(o));
|
||||
|
||||
rocksdb_options_set_blob_gc_force_threshold(o, 0.75);
|
||||
CheckCondition(0.75 == rocksdb_options_get_blob_gc_force_threshold(o));
|
||||
|
||||
// Create a copy that should be equal to the original.
|
||||
rocksdb_options_t* copy;
|
||||
|
||||
+61
-42
@@ -29,7 +29,7 @@
|
||||
#include "db/version_set.h"
|
||||
#include "db/write_controller.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "memtable/hash_skiplist_rep.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
@@ -203,7 +203,7 @@ Status CheckCFPathsSupported(const DBOptions& db_options,
|
||||
namespace {
|
||||
const uint64_t kDefaultTtl = 0xfffffffffffffffe;
|
||||
const uint64_t kDefaultPeriodicCompSecs = 0xfffffffffffffffe;
|
||||
}; // namespace
|
||||
} // namespace
|
||||
|
||||
ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const ColumnFamilyOptions& src) {
|
||||
@@ -349,12 +349,18 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
}
|
||||
|
||||
if (result.level_compaction_dynamic_level_bytes) {
|
||||
if (result.compaction_style != kCompactionStyleLevel ||
|
||||
result.cf_paths.size() > 1U) {
|
||||
// 1. level_compaction_dynamic_level_bytes only makes sense for
|
||||
// level-based compaction.
|
||||
// 2. we don't yet know how to make both of this feature and multiple
|
||||
// DB path work.
|
||||
if (result.compaction_style != kCompactionStyleLevel) {
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
"level_compaction_dynamic_level_bytes only makes sense"
|
||||
"for level-based compaction");
|
||||
result.level_compaction_dynamic_level_bytes = false;
|
||||
} else if (result.cf_paths.size() > 1U) {
|
||||
// we don't yet know how to make both of this feature and multiple
|
||||
// DB path work.
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
"multiple cf_paths/db_paths and"
|
||||
"level_compaction_dynamic_level_bytes"
|
||||
"can't be used together");
|
||||
result.level_compaction_dynamic_level_bytes = false;
|
||||
}
|
||||
}
|
||||
@@ -445,6 +451,9 @@ bool SuperVersion::Unref() {
|
||||
|
||||
void SuperVersion::Cleanup() {
|
||||
assert(refs.load(std::memory_order_relaxed) == 0);
|
||||
// Since this SuperVersion object is being deleted,
|
||||
// decrement reference to the immutable MemtableList
|
||||
// this SV object was pointing to.
|
||||
imm->Unref(&to_delete);
|
||||
MemTable* m = mem->Unref();
|
||||
if (m != nullptr) {
|
||||
@@ -454,7 +463,7 @@ void SuperVersion::Cleanup() {
|
||||
to_delete.push_back(m);
|
||||
}
|
||||
current->Unref();
|
||||
cfd->UnrefAndTryDelete(this);
|
||||
cfd->UnrefAndTryDelete();
|
||||
}
|
||||
|
||||
void SuperVersion::Init(ColumnFamilyData* new_cfd, MemTable* new_mem,
|
||||
@@ -472,10 +481,10 @@ void SuperVersion::Init(ColumnFamilyData* new_cfd, MemTable* new_mem,
|
||||
|
||||
namespace {
|
||||
void SuperVersionUnrefHandle(void* ptr) {
|
||||
// UnrefHandle is called when a thread exists or a ThreadLocalPtr gets
|
||||
// destroyed. When former happens, the thread shouldn't see kSVInUse.
|
||||
// When latter happens, we are in ~ColumnFamilyData(), no get should happen as
|
||||
// well.
|
||||
// UnrefHandle is called when a thread exits or a ThreadLocalPtr gets
|
||||
// destroyed. When the former happens, the thread shouldn't see kSVInUse.
|
||||
// When the latter happens, only super_version_ holds a reference
|
||||
// to ColumnFamilyData, so no further queries are possible.
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
bool was_last_ref __attribute__((__unused__));
|
||||
was_last_ref = sv->Unref();
|
||||
@@ -502,9 +511,10 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
uint32_t id, const std::string& name, Version* _dummy_versions,
|
||||
Cache* _table_cache, WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& cf_options, const ImmutableDBOptions& db_options,
|
||||
const FileOptions& file_options, ColumnFamilySet* column_family_set,
|
||||
const FileOptions* file_options, ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id)
|
||||
: id_(id),
|
||||
name_(name),
|
||||
dummy_versions_(_dummy_versions),
|
||||
@@ -562,7 +572,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
internal_stats_.reset(
|
||||
new InternalStats(ioptions_.num_levels, ioptions_.clock, this));
|
||||
table_cache_.reset(new TableCache(ioptions_, file_options, _table_cache,
|
||||
block_cache_tracer, io_tracer));
|
||||
block_cache_tracer, io_tracer,
|
||||
db_session_id));
|
||||
blob_file_cache_.reset(
|
||||
new BlobFileCache(_table_cache, ioptions(), soptions(), id_,
|
||||
internal_stats_->GetBlobFileReadHist(), io_tracer));
|
||||
@@ -663,7 +674,7 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ColumnFamilyData::UnrefAndTryDelete(SuperVersion* sv_under_cleanup) {
|
||||
bool ColumnFamilyData::UnrefAndTryDelete() {
|
||||
int old_refs = refs_.fetch_sub(1);
|
||||
assert(old_refs > 0);
|
||||
|
||||
@@ -673,22 +684,17 @@ bool ColumnFamilyData::UnrefAndTryDelete(SuperVersion* sv_under_cleanup) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If called under SuperVersion::Cleanup, we should not re-enter Cleanup on
|
||||
// the same SuperVersion. (But while installing a new SuperVersion, this
|
||||
// cfd could be referenced only by two SuperVersions.)
|
||||
if (old_refs == 2 && super_version_ != nullptr &&
|
||||
super_version_ != sv_under_cleanup) {
|
||||
if (old_refs == 2 && super_version_ != nullptr) {
|
||||
// Only the super_version_ holds me
|
||||
SuperVersion* sv = super_version_;
|
||||
super_version_ = nullptr;
|
||||
// Release SuperVersion reference kept in ThreadLocalPtr.
|
||||
// This must be done outside of mutex_ since unref handler can lock mutex.
|
||||
sv->db_mutex->Unlock();
|
||||
|
||||
// Release SuperVersion references kept in ThreadLocalPtr.
|
||||
local_sv_.reset();
|
||||
sv->db_mutex->Lock();
|
||||
|
||||
if (sv->Unref()) {
|
||||
// May delete this ColumnFamilyData after calling Cleanup()
|
||||
// Note: sv will delete this ColumnFamilyData during Cleanup()
|
||||
assert(sv->cfd == this);
|
||||
sv->Cleanup();
|
||||
delete sv;
|
||||
return true;
|
||||
@@ -1049,6 +1055,10 @@ uint64_t ColumnFamilyData::GetTotalSstFilesSize() const {
|
||||
return VersionSet::GetTotalSstFilesSize(dummy_versions_);
|
||||
}
|
||||
|
||||
uint64_t ColumnFamilyData::GetTotalBlobFileSize() const {
|
||||
return VersionSet::GetTotalBlobFileSize(dummy_versions_);
|
||||
}
|
||||
|
||||
uint64_t ColumnFamilyData::GetLiveSstFilesSize() const {
|
||||
return current_->GetSstFilesSize();
|
||||
}
|
||||
@@ -1253,14 +1263,13 @@ bool ColumnFamilyData::ReturnThreadLocalSuperVersion(SuperVersion* sv) {
|
||||
void ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex) {
|
||||
db_mutex->AssertHeld();
|
||||
return InstallSuperVersion(sv_context, db_mutex, mutable_cf_options_);
|
||||
return InstallSuperVersion(sv_context, mutable_cf_options_);
|
||||
}
|
||||
|
||||
void ColumnFamilyData::InstallSuperVersion(
|
||||
SuperVersionContext* sv_context, InstrumentedMutex* db_mutex,
|
||||
SuperVersionContext* sv_context,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
SuperVersion* new_superversion = sv_context->new_superversion.release();
|
||||
new_superversion->db_mutex = db_mutex;
|
||||
new_superversion->mutable_cf_options = mutable_cf_options;
|
||||
new_superversion->Init(this, mem_, imm_.current(), current_);
|
||||
SuperVersion* old_superversion = super_version_;
|
||||
@@ -1349,12 +1358,19 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
}
|
||||
}
|
||||
|
||||
if (cf_options.enable_blob_garbage_collection &&
|
||||
(cf_options.blob_garbage_collection_age_cutoff < 0.0 ||
|
||||
cf_options.blob_garbage_collection_age_cutoff > 1.0)) {
|
||||
return Status::InvalidArgument(
|
||||
"The age cutoff for blob garbage collection should be in the range "
|
||||
"[0.0, 1.0].");
|
||||
if (cf_options.enable_blob_garbage_collection) {
|
||||
if (cf_options.blob_garbage_collection_age_cutoff < 0.0 ||
|
||||
cf_options.blob_garbage_collection_age_cutoff > 1.0) {
|
||||
return Status::InvalidArgument(
|
||||
"The age cutoff for blob garbage collection should be in the range "
|
||||
"[0.0, 1.0].");
|
||||
}
|
||||
if (cf_options.blob_garbage_collection_force_threshold < 0.0 ||
|
||||
cf_options.blob_garbage_collection_force_threshold > 1.0) {
|
||||
return Status::InvalidArgument(
|
||||
"The garbage ratio threshold for forcing blob garbage collection "
|
||||
"should be in the range [0.0, 1.0].");
|
||||
}
|
||||
}
|
||||
|
||||
if (cf_options.compaction_style == kCompactionStyleFIFO &&
|
||||
@@ -1451,21 +1467,23 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
WriteBufferManager* _write_buffer_manager,
|
||||
WriteController* _write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id)
|
||||
: max_column_family_(0),
|
||||
file_options_(file_options),
|
||||
dummy_cfd_(new ColumnFamilyData(
|
||||
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
|
||||
nullptr, ColumnFamilyOptions(), *db_options, file_options, nullptr,
|
||||
block_cache_tracer, io_tracer)),
|
||||
nullptr, ColumnFamilyOptions(), *db_options, &file_options_, nullptr,
|
||||
block_cache_tracer, io_tracer, db_session_id)),
|
||||
default_cfd_cache_(nullptr),
|
||||
db_name_(dbname),
|
||||
db_options_(db_options),
|
||||
file_options_(file_options),
|
||||
table_cache_(table_cache),
|
||||
write_buffer_manager_(_write_buffer_manager),
|
||||
write_controller_(_write_controller),
|
||||
block_cache_tracer_(block_cache_tracer),
|
||||
io_tracer_(io_tracer) {
|
||||
io_tracer_(io_tracer),
|
||||
db_session_id_(db_session_id) {
|
||||
// initialize linked list
|
||||
dummy_cfd_->prev_ = dummy_cfd_;
|
||||
dummy_cfd_->next_ = dummy_cfd_;
|
||||
@@ -1531,7 +1549,8 @@ ColumnFamilyData* ColumnFamilySet::CreateColumnFamily(
|
||||
assert(column_families_.find(name) == column_families_.end());
|
||||
ColumnFamilyData* new_cfd = new ColumnFamilyData(
|
||||
id, name, dummy_versions, table_cache_, write_buffer_manager_, options,
|
||||
*db_options_, file_options_, this, block_cache_tracer_, io_tracer_);
|
||||
*db_options_, &file_options_, this, block_cache_tracer_, io_tracer_,
|
||||
db_session_id_);
|
||||
column_families_.insert({name, id});
|
||||
column_family_data_.insert({id, new_cfd});
|
||||
max_column_family_ = std::max(max_column_family_, id);
|
||||
|
||||
+11
-9
@@ -208,8 +208,6 @@ struct SuperVersion {
|
||||
uint64_t version_number;
|
||||
WriteStallCondition write_stall_condition;
|
||||
|
||||
InstrumentedMutex* db_mutex;
|
||||
|
||||
// should be called outside the mutex
|
||||
SuperVersion() = default;
|
||||
~SuperVersion();
|
||||
@@ -281,8 +279,7 @@ class ColumnFamilyData {
|
||||
// UnrefAndTryDelete() decreases the reference count and do free if needed,
|
||||
// return true if this is freed else false, UnrefAndTryDelete() can only
|
||||
// be called while holding a DB mutex, or during single-threaded recovery.
|
||||
// sv_under_cleanup is only provided when called from SuperVersion::Cleanup.
|
||||
bool UnrefAndTryDelete(SuperVersion* sv_under_cleanup = nullptr);
|
||||
bool UnrefAndTryDelete();
|
||||
|
||||
// SetDropped() can only be called under following conditions:
|
||||
// 1) Holding a DB mutex,
|
||||
@@ -360,6 +357,7 @@ class ColumnFamilyData {
|
||||
uint64_t GetNumLiveVersions() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetLiveSstFilesSize() const; // REQUIRE: DB mutex held
|
||||
uint64_t GetTotalBlobFileSize() const; // REQUIRE: DB mutex held
|
||||
void SetMemtable(MemTable* new_mem) {
|
||||
uint64_t memtable_id = last_memtable_id_.fetch_add(1) + 1;
|
||||
new_mem->SetID(memtable_id);
|
||||
@@ -453,7 +451,6 @@ class ColumnFamilyData {
|
||||
// the clients to allocate SuperVersion outside of mutex.
|
||||
// IMPORTANT: Only call this from DBImpl::InstallSuperVersion()
|
||||
void InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
void InstallSuperVersion(SuperVersionContext* sv_context,
|
||||
InstrumentedMutex* db_mutex);
|
||||
@@ -520,6 +517,7 @@ class ColumnFamilyData {
|
||||
}
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
WriteBufferManager* write_buffer_mgr() { return write_buffer_manager_; }
|
||||
|
||||
private:
|
||||
friend class ColumnFamilySet;
|
||||
@@ -529,10 +527,11 @@ class ColumnFamilyData {
|
||||
WriteBufferManager* write_buffer_manager,
|
||||
const ColumnFamilyOptions& options,
|
||||
const ImmutableDBOptions& db_options,
|
||||
const FileOptions& file_options,
|
||||
const FileOptions* file_options,
|
||||
ColumnFamilySet* column_family_set,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer);
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id);
|
||||
|
||||
std::vector<std::string> GetDbPaths() const;
|
||||
|
||||
@@ -668,7 +667,8 @@ class ColumnFamilySet {
|
||||
WriteBufferManager* _write_buffer_manager,
|
||||
WriteController* _write_controller,
|
||||
BlockCacheTracer* const block_cache_tracer,
|
||||
const std::shared_ptr<IOTracer>& io_tracer);
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_session_id);
|
||||
~ColumnFamilySet();
|
||||
|
||||
ColumnFamilyData* GetDefault() const;
|
||||
@@ -718,6 +718,8 @@ class ColumnFamilySet {
|
||||
std::unordered_map<uint32_t, ColumnFamilyData*> column_family_data_;
|
||||
|
||||
uint32_t max_column_family_;
|
||||
const FileOptions file_options_;
|
||||
|
||||
ColumnFamilyData* dummy_cfd_;
|
||||
// We don't hold the refcount here, since default column family always exists
|
||||
// We are also not responsible for cleaning up default_cfd_cache_. This is
|
||||
@@ -727,12 +729,12 @@ class ColumnFamilySet {
|
||||
|
||||
const std::string db_name_;
|
||||
const ImmutableDBOptions* const db_options_;
|
||||
const FileOptions file_options_;
|
||||
Cache* table_cache_;
|
||||
WriteBufferManager* write_buffer_manager_;
|
||||
WriteController* write_controller_;
|
||||
BlockCacheTracer* const block_cache_tracer_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
std::string db_session_id_;
|
||||
};
|
||||
|
||||
// We use ColumnFamilyMemTablesImpl to provide WriteBatch a way to access
|
||||
|
||||
+34
-14
@@ -56,16 +56,8 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
public:
|
||||
explicit ColumnFamilyTestBase(uint32_t format) : rnd_(139), format_(format) {
|
||||
Env* base_env = Env::Default();
|
||||
#ifndef ROCKSDB_LITE
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
if (test_env_uri) {
|
||||
Env* test_env = nullptr;
|
||||
Status s = Env::LoadEnv(test_env_uri, &test_env, &env_guard_);
|
||||
base_env = test_env;
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
EXPECT_OK(
|
||||
test::CreateEnvFromSystem(ConfigOptions(), &base_env, &env_guard_));
|
||||
EXPECT_NE(nullptr, base_env);
|
||||
env_ = new EnvCounter(base_env);
|
||||
env_->skip_fsync_ = true;
|
||||
@@ -2983,7 +2975,8 @@ TEST_P(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));
|
||||
column_family_options_.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
@@ -3028,7 +3021,8 @@ TEST_P(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));
|
||||
column_family_options_.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
@@ -3079,7 +3073,8 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
env.SetBackgroundThreads(2, Env::HIGH);
|
||||
db_options_.env = &env;
|
||||
db_options_.max_background_flushes = 1;
|
||||
column_family_options_.memtable_factory.reset(new SpecialSkipListFactory(2));
|
||||
column_family_options_.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(2));
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
@@ -3137,7 +3132,8 @@ TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
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_.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(3));
|
||||
column_family_options_.level0_file_num_compaction_trigger = 2;
|
||||
Open();
|
||||
CreateColumnFamilies({"one"});
|
||||
@@ -3411,6 +3407,30 @@ TEST(ColumnFamilyTest, ValidateBlobGCCutoff) {
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
TEST(ColumnFamilyTest, ValidateBlobGCForceThreshold) {
|
||||
DBOptions db_options;
|
||||
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.enable_blob_garbage_collection = true;
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = -0.5;
|
||||
ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options)
|
||||
.IsInvalidArgument());
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = 0.0;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = 0.5;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = 1.0;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_force_threshold = 1.5;
|
||||
ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#include "table/internal_iterator.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// An internal iterator that wraps another one and ensures that any keys
|
||||
// returned are strictly within a range [start, end). If the underlying
|
||||
// iterator has already performed the bounds checking, it relies on that result;
|
||||
// otherwise, it performs the necessary key comparisons itself. Both bounds
|
||||
// are optional.
|
||||
class ClippingIterator : public InternalIterator {
|
||||
public:
|
||||
ClippingIterator(InternalIterator* iter, const Slice* start, const Slice* end,
|
||||
const Comparator* cmp)
|
||||
: iter_(iter), start_(start), end_(end), cmp_(cmp), valid_(false) {
|
||||
assert(iter_);
|
||||
assert(cmp_);
|
||||
assert(!start_ || !end_ || cmp_->Compare(*start_, *end_) <= 0);
|
||||
|
||||
UpdateAndEnforceBounds();
|
||||
}
|
||||
|
||||
bool Valid() const override { return valid_; }
|
||||
|
||||
void SeekToFirst() override {
|
||||
if (start_) {
|
||||
iter_->Seek(*start_);
|
||||
} else {
|
||||
iter_->SeekToFirst();
|
||||
}
|
||||
|
||||
UpdateAndEnforceUpperBound();
|
||||
}
|
||||
|
||||
void SeekToLast() override {
|
||||
if (end_) {
|
||||
iter_->SeekForPrev(*end_);
|
||||
|
||||
// Upper bound is exclusive, so we need a key which is strictly smaller
|
||||
if (iter_->Valid() && cmp_->Compare(iter_->key(), *end_) == 0) {
|
||||
iter_->Prev();
|
||||
}
|
||||
} else {
|
||||
iter_->SeekToLast();
|
||||
}
|
||||
|
||||
UpdateAndEnforceLowerBound();
|
||||
}
|
||||
|
||||
void Seek(const Slice& target) override {
|
||||
if (start_ && cmp_->Compare(target, *start_) < 0) {
|
||||
iter_->Seek(*start_);
|
||||
UpdateAndEnforceUpperBound();
|
||||
return;
|
||||
}
|
||||
|
||||
if (end_ && cmp_->Compare(target, *end_) >= 0) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
iter_->Seek(target);
|
||||
UpdateAndEnforceUpperBound();
|
||||
}
|
||||
|
||||
void SeekForPrev(const Slice& target) override {
|
||||
if (start_ && cmp_->Compare(target, *start_) < 0) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (end_ && cmp_->Compare(target, *end_) >= 0) {
|
||||
iter_->SeekForPrev(*end_);
|
||||
|
||||
// Upper bound is exclusive, so we need a key which is strictly smaller
|
||||
if (iter_->Valid() && cmp_->Compare(iter_->key(), *end_) == 0) {
|
||||
iter_->Prev();
|
||||
}
|
||||
|
||||
UpdateAndEnforceLowerBound();
|
||||
return;
|
||||
}
|
||||
|
||||
iter_->SeekForPrev(target);
|
||||
UpdateAndEnforceLowerBound();
|
||||
}
|
||||
|
||||
void Next() override {
|
||||
assert(valid_);
|
||||
iter_->Next();
|
||||
UpdateAndEnforceUpperBound();
|
||||
}
|
||||
|
||||
bool NextAndGetResult(IterateResult* result) override {
|
||||
assert(valid_);
|
||||
assert(result);
|
||||
|
||||
IterateResult res;
|
||||
valid_ = iter_->NextAndGetResult(&res);
|
||||
|
||||
if (!valid_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (end_) {
|
||||
EnforceUpperBoundImpl(res.bound_check_result);
|
||||
|
||||
if (!valid_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
res.bound_check_result = IterBoundCheck::kInbound;
|
||||
*result = res;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Prev() override {
|
||||
assert(valid_);
|
||||
iter_->Prev();
|
||||
UpdateAndEnforceLowerBound();
|
||||
}
|
||||
|
||||
Slice key() const override {
|
||||
assert(valid_);
|
||||
return iter_->key();
|
||||
}
|
||||
|
||||
Slice user_key() const override {
|
||||
assert(valid_);
|
||||
return iter_->user_key();
|
||||
}
|
||||
|
||||
Slice value() const override {
|
||||
assert(valid_);
|
||||
return iter_->value();
|
||||
}
|
||||
|
||||
Status status() const override { return iter_->status(); }
|
||||
|
||||
bool PrepareValue() override {
|
||||
assert(valid_);
|
||||
|
||||
if (iter_->PrepareValue()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(!iter_->Valid());
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MayBeOutOfLowerBound() override {
|
||||
assert(valid_);
|
||||
return false;
|
||||
}
|
||||
|
||||
IterBoundCheck UpperBoundCheckResult() override {
|
||||
assert(valid_);
|
||||
return IterBoundCheck::kInbound;
|
||||
}
|
||||
|
||||
void SetPinnedItersMgr(PinnedIteratorsManager* pinned_iters_mgr) override {
|
||||
iter_->SetPinnedItersMgr(pinned_iters_mgr);
|
||||
}
|
||||
|
||||
bool IsKeyPinned() const override {
|
||||
assert(valid_);
|
||||
return iter_->IsKeyPinned();
|
||||
}
|
||||
|
||||
bool IsValuePinned() const override {
|
||||
assert(valid_);
|
||||
return iter_->IsValuePinned();
|
||||
}
|
||||
|
||||
Status GetProperty(std::string prop_name, std::string* prop) override {
|
||||
return iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
private:
|
||||
void UpdateValid() {
|
||||
assert(!iter_->Valid() || iter_->status().ok());
|
||||
|
||||
valid_ = iter_->Valid();
|
||||
}
|
||||
|
||||
void EnforceUpperBoundImpl(IterBoundCheck bound_check_result) {
|
||||
if (bound_check_result == IterBoundCheck::kInbound) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bound_check_result == IterBoundCheck::kOutOfBound) {
|
||||
valid_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
assert(bound_check_result == IterBoundCheck::kUnknown);
|
||||
|
||||
if (cmp_->Compare(key(), *end_) >= 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void EnforceUpperBound() {
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!end_) {
|
||||
return;
|
||||
}
|
||||
|
||||
EnforceUpperBoundImpl(iter_->UpperBoundCheckResult());
|
||||
}
|
||||
|
||||
void EnforceLowerBound() {
|
||||
if (!valid_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!start_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!iter_->MayBeOutOfLowerBound()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cmp_->Compare(key(), *start_) < 0) {
|
||||
valid_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void AssertBounds() {
|
||||
assert(!valid_ || !start_ || cmp_->Compare(key(), *start_) >= 0);
|
||||
assert(!valid_ || !end_ || cmp_->Compare(key(), *end_) < 0);
|
||||
}
|
||||
|
||||
void UpdateAndEnforceBounds() {
|
||||
UpdateValid();
|
||||
EnforceUpperBound();
|
||||
EnforceLowerBound();
|
||||
AssertBounds();
|
||||
}
|
||||
|
||||
void UpdateAndEnforceUpperBound() {
|
||||
UpdateValid();
|
||||
EnforceUpperBound();
|
||||
AssertBounds();
|
||||
}
|
||||
|
||||
void UpdateAndEnforceLowerBound() {
|
||||
UpdateValid();
|
||||
EnforceLowerBound();
|
||||
AssertBounds();
|
||||
}
|
||||
|
||||
InternalIterator* iter_;
|
||||
const Slice* start_;
|
||||
const Slice* end_;
|
||||
const Comparator* cmp_;
|
||||
bool valid_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,258 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/compaction/clipping_iterator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/dbformat.h"
|
||||
#include "rocksdb/comparator.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/vector_iterator.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A vector iterator which does its own bounds checking. This is for testing the
|
||||
// optimizations in the clipping iterator where we bypass the bounds checking if
|
||||
// the input iterator has already performed it.
|
||||
class BoundsCheckingVectorIterator : public VectorIterator {
|
||||
public:
|
||||
BoundsCheckingVectorIterator(const std::vector<std::string>& keys,
|
||||
const std::vector<std::string>& values,
|
||||
const Slice* start, const Slice* end,
|
||||
const Comparator* cmp)
|
||||
: VectorIterator(keys, values, cmp), start_(start), end_(end), cmp_(cmp) {
|
||||
assert(cmp_);
|
||||
}
|
||||
|
||||
bool NextAndGetResult(IterateResult* result) override {
|
||||
assert(Valid());
|
||||
assert(result);
|
||||
|
||||
Next();
|
||||
|
||||
if (!Valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result->key = key();
|
||||
result->bound_check_result = UpperBoundCheckResult();
|
||||
result->value_prepared = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MayBeOutOfLowerBound() override {
|
||||
assert(Valid());
|
||||
|
||||
if (!start_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return cmp_->Compare(key(), *start_) < 0;
|
||||
}
|
||||
|
||||
IterBoundCheck UpperBoundCheckResult() override {
|
||||
assert(Valid());
|
||||
|
||||
if (!end_) {
|
||||
return IterBoundCheck::kInbound;
|
||||
}
|
||||
|
||||
return cmp_->Compare(key(), *end_) >= 0 ? IterBoundCheck::kOutOfBound
|
||||
: IterBoundCheck::kInbound;
|
||||
}
|
||||
|
||||
private:
|
||||
const Slice* start_;
|
||||
const Slice* end_;
|
||||
const Comparator* cmp_;
|
||||
};
|
||||
|
||||
class ClippingIteratorTest
|
||||
: public ::testing::Test,
|
||||
public ::testing::WithParamInterface<std::tuple<bool, size_t, size_t>> {};
|
||||
|
||||
TEST_P(ClippingIteratorTest, Clip) {
|
||||
const std::vector<std::string> keys{"key0", "key1", "key2", "key3", "key4",
|
||||
"key5", "key6", "key7", "key8", "key9"};
|
||||
const std::vector<std::string> values{
|
||||
"unused0", "value1", "value2", "value3", "unused4",
|
||||
"unused5", "unused6", "unused7", "unused8", "unused9"};
|
||||
|
||||
assert(keys.size() == values.size());
|
||||
|
||||
// Note: the input always contains key1, key2, and key3; however, the clipping
|
||||
// window is based on the test parameters: its left edge is a value in the
|
||||
// range [0, 4], and its size is a value in the range [0, 5]
|
||||
const std::vector<std::string> input_keys{keys[1], keys[2], keys[3]};
|
||||
const std::vector<std::string> input_values{values[1], values[2], values[3]};
|
||||
|
||||
const bool use_bounds_checking_vec_it = std::get<0>(GetParam());
|
||||
|
||||
const size_t clip_start_idx = std::get<1>(GetParam());
|
||||
const size_t clip_window_size = std::get<2>(GetParam());
|
||||
const size_t clip_end_idx = clip_start_idx + clip_window_size;
|
||||
|
||||
const Slice start(keys[clip_start_idx]);
|
||||
const Slice end(keys[clip_end_idx]);
|
||||
|
||||
std::unique_ptr<InternalIterator> input(
|
||||
use_bounds_checking_vec_it
|
||||
? new BoundsCheckingVectorIterator(input_keys, input_values, &start,
|
||||
&end, BytewiseComparator())
|
||||
: new VectorIterator(input_keys, input_values, BytewiseComparator()));
|
||||
|
||||
ClippingIterator clip(input.get(), &start, &end, BytewiseComparator());
|
||||
|
||||
// The range the clipping iterator should return values from. This is
|
||||
// essentially the intersection of the input range [1, 4) and the clipping
|
||||
// window [clip_start_idx, clip_end_idx)
|
||||
const size_t data_start_idx =
|
||||
std::max(clip_start_idx, static_cast<size_t>(1));
|
||||
const size_t data_end_idx = std::min(clip_end_idx, static_cast<size_t>(4));
|
||||
|
||||
// Range is empty; all Seeks should fail
|
||||
if (data_start_idx >= data_end_idx) {
|
||||
clip.SeekToFirst();
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
clip.SeekToLast();
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
clip.Seek(keys[i]);
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
clip.SeekForPrev(keys[i]);
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Range is non-empty; call SeekToFirst and iterate forward
|
||||
clip.SeekToFirst();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_start_idx]);
|
||||
ASSERT_EQ(clip.value(), values[data_start_idx]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
|
||||
for (size_t i = data_start_idx + 1; i < data_end_idx; ++i) {
|
||||
clip.Next();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
}
|
||||
|
||||
clip.Next();
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
// Do it again using NextAndGetResult
|
||||
clip.SeekToFirst();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_start_idx]);
|
||||
ASSERT_EQ(clip.value(), values[data_start_idx]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
|
||||
for (size_t i = data_start_idx + 1; i < data_end_idx; ++i) {
|
||||
IterateResult result;
|
||||
ASSERT_TRUE(clip.NextAndGetResult(&result));
|
||||
ASSERT_EQ(result.key, keys[i]);
|
||||
ASSERT_EQ(result.bound_check_result, IterBoundCheck::kInbound);
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
}
|
||||
|
||||
IterateResult result;
|
||||
ASSERT_FALSE(clip.NextAndGetResult(&result));
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
// Call SeekToLast and iterate backward
|
||||
clip.SeekToLast();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_end_idx - 1]);
|
||||
ASSERT_EQ(clip.value(), values[data_end_idx - 1]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
|
||||
for (size_t i = data_end_idx - 2; i >= data_start_idx; --i) {
|
||||
clip.Prev();
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
}
|
||||
|
||||
clip.Prev();
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
|
||||
// Call Seek/SeekForPrev for all keys; Seek should return the smallest key
|
||||
// which is >= the target; SeekForPrev should return the largest key which is
|
||||
// <= the target
|
||||
for (size_t i = 0; i < keys.size(); ++i) {
|
||||
clip.Seek(keys[i]);
|
||||
|
||||
if (i < data_start_idx) {
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_start_idx]);
|
||||
ASSERT_EQ(clip.value(), values[data_start_idx]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
} else if (i < data_end_idx) {
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
} else {
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
}
|
||||
|
||||
clip.SeekForPrev(keys[i]);
|
||||
|
||||
if (i < data_start_idx) {
|
||||
ASSERT_FALSE(clip.Valid());
|
||||
} else if (i < data_end_idx) {
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[i]);
|
||||
ASSERT_EQ(clip.value(), values[i]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
} else {
|
||||
ASSERT_TRUE(clip.Valid());
|
||||
ASSERT_EQ(clip.key(), keys[data_end_idx - 1]);
|
||||
ASSERT_EQ(clip.value(), values[data_end_idx - 1]);
|
||||
ASSERT_FALSE(clip.MayBeOutOfLowerBound());
|
||||
ASSERT_EQ(clip.UpperBoundCheckResult(), IterBoundCheck::kInbound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
ClippingIteratorTest, ClippingIteratorTest,
|
||||
::testing::Combine(
|
||||
::testing::Bool(),
|
||||
::testing::Range(static_cast<size_t>(0), static_cast<size_t>(5)),
|
||||
::testing::Range(static_cast<size_t>(0), static_cast<size_t>(6))));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -211,9 +211,9 @@ Compaction::Compaction(
|
||||
std::vector<CompactionInputFiles> _inputs, int _output_level,
|
||||
uint64_t _target_file_size, uint64_t _max_compaction_bytes,
|
||||
uint32_t _output_path_id, CompressionType _compression,
|
||||
CompressionOptions _compression_opts, uint32_t _max_subcompactions,
|
||||
std::vector<FileMetaData*> _grandparents, bool _manual_compaction,
|
||||
double _score, bool _deletion_compaction,
|
||||
CompressionOptions _compression_opts, Temperature _output_temperature,
|
||||
uint32_t _max_subcompactions, std::vector<FileMetaData*> _grandparents,
|
||||
bool _manual_compaction, double _score, bool _deletion_compaction,
|
||||
CompactionReason _compaction_reason)
|
||||
: input_vstorage_(vstorage),
|
||||
start_level_(_inputs[0].level),
|
||||
@@ -229,6 +229,7 @@ Compaction::Compaction(
|
||||
output_path_id_(_output_path_id),
|
||||
output_compression_(_compression),
|
||||
output_compression_opts_(_compression_opts),
|
||||
output_temperature_(_output_temperature),
|
||||
deletion_compaction_(_deletion_compaction),
|
||||
inputs_(PopulateWithAtomicBoundaries(vstorage, std::move(_inputs))),
|
||||
grandparents_(std::move(_grandparents)),
|
||||
@@ -237,7 +238,8 @@ Compaction::Compaction(
|
||||
is_full_compaction_(IsFullCompaction(vstorage, inputs_)),
|
||||
is_manual_compaction_(_manual_compaction),
|
||||
is_trivial_move_(false),
|
||||
compaction_reason_(_compaction_reason) {
|
||||
compaction_reason_(_compaction_reason),
|
||||
notify_on_compaction_completion_(false) {
|
||||
MarkFilesBeingCompacted(true);
|
||||
if (is_manual_compaction_) {
|
||||
compaction_reason_ = CompactionReason::kManualCompaction;
|
||||
@@ -307,6 +309,12 @@ bool Compaction::IsTrivialMove() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (start_level_ == output_level_) {
|
||||
// It doesn't make sense if compaction picker picks files just to trivial
|
||||
// move to the same level.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Used in universal compaction, where trivial move can be done if the
|
||||
// input files are non overlapping
|
||||
if ((mutable_cf_options_.compaction_options_universal.allow_trivial_move) &&
|
||||
@@ -563,6 +571,14 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
if (max_subcompactions_ <= 1 || cfd_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: the subcompaction boundary picking logic does not currently guarantee
|
||||
// that all user keys that differ only by timestamp get processed by the same
|
||||
// subcompaction.
|
||||
if (cfd_->user_comparator()->timestamp_size() > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cfd_->ioptions()->compaction_style == kCompactionStyleLevel) {
|
||||
return (start_level_ == 0 || is_manual_compaction_) && output_level_ > 0 &&
|
||||
!IsOutputLevelEmpty();
|
||||
@@ -573,10 +589,42 @@ bool Compaction::ShouldFormSubcompactions() const {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t Compaction::MinInputFileOldestAncesterTime() const {
|
||||
bool Compaction::DoesInputReferenceBlobFiles() const {
|
||||
assert(input_version_);
|
||||
|
||||
const VersionStorageInfo* storage_info = input_version_->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
if (storage_info->GetBlobFiles().empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < inputs_.size(); ++i) {
|
||||
for (const FileMetaData* meta : inputs_[i].files) {
|
||||
assert(meta);
|
||||
|
||||
if (meta->oldest_blob_file_number != kInvalidBlobFileNumber) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t Compaction::MinInputFileOldestAncesterTime(
|
||||
const InternalKey* start, const InternalKey* end) const {
|
||||
uint64_t min_oldest_ancester_time = port::kMaxUint64;
|
||||
const InternalKeyComparator& icmp =
|
||||
column_family_data()->internal_comparator();
|
||||
for (const auto& level_files : inputs_) {
|
||||
for (const auto& file : level_files.files) {
|
||||
if (start != nullptr && icmp.Compare(file->largest, *start) < 0) {
|
||||
continue;
|
||||
}
|
||||
if (end != nullptr && icmp.Compare(file->smallest, *end) > 0) {
|
||||
continue;
|
||||
}
|
||||
uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time != 0) {
|
||||
min_oldest_ancester_time =
|
||||
|
||||
@@ -76,7 +76,8 @@ class Compaction {
|
||||
std::vector<CompactionInputFiles> inputs, int output_level,
|
||||
uint64_t target_file_size, uint64_t max_compaction_bytes,
|
||||
uint32_t output_path_id, CompressionType compression,
|
||||
CompressionOptions compression_opts, uint32_t max_subcompactions,
|
||||
CompressionOptions compression_opts,
|
||||
Temperature output_temperature, uint32_t max_subcompactions,
|
||||
std::vector<FileMetaData*> grandparents,
|
||||
bool manual_compaction = false, double score = -1,
|
||||
bool deletion_compaction = false,
|
||||
@@ -223,9 +224,9 @@ class Compaction {
|
||||
// How many total levels are there?
|
||||
int number_levels() const { return number_levels_; }
|
||||
|
||||
// Return the ImmutableCFOptions that should be used throughout the compaction
|
||||
// Return the ImmutableOptions that should be used throughout the compaction
|
||||
// procedure
|
||||
const ImmutableOptions* immutable_cf_options() const {
|
||||
const ImmutableOptions* immutable_options() const {
|
||||
return &immutable_options_;
|
||||
}
|
||||
|
||||
@@ -266,6 +267,11 @@ class Compaction {
|
||||
// Should this compaction be broken up into smaller ones run in parallel?
|
||||
bool ShouldFormSubcompactions() const;
|
||||
|
||||
// Returns true iff at least one input file references a blob file.
|
||||
//
|
||||
// PRE: input version has been set.
|
||||
bool DoesInputReferenceBlobFiles() const;
|
||||
|
||||
// test function to validate the functionality of IsBottommostLevel()
|
||||
// function -- determines if compaction with inputs and storage is bottommost
|
||||
static bool TEST_IsBottommostLevel(
|
||||
@@ -294,9 +300,24 @@ class Compaction {
|
||||
|
||||
uint64_t max_compaction_bytes() const { return max_compaction_bytes_; }
|
||||
|
||||
Temperature output_temperature() const { return output_temperature_; }
|
||||
|
||||
uint32_t max_subcompactions() const { return max_subcompactions_; }
|
||||
|
||||
uint64_t MinInputFileOldestAncesterTime() const;
|
||||
// start and end are sub compact range. Null if no boundary.
|
||||
// This is used to filter out some input files' ancester's time range.
|
||||
uint64_t MinInputFileOldestAncesterTime(const InternalKey* start,
|
||||
const InternalKey* end) const;
|
||||
|
||||
// Called by DBImpl::NotifyOnCompactionCompleted to make sure number of
|
||||
// compaction begin and compaction completion callbacks match.
|
||||
void SetNotifyOnCompactionCompleted() {
|
||||
notify_on_compaction_completion_ = true;
|
||||
}
|
||||
|
||||
bool ShouldNotifyOnCompactionCompleted() const {
|
||||
return notify_on_compaction_completion_;
|
||||
}
|
||||
|
||||
private:
|
||||
// mark (or clear) all files that are being compacted
|
||||
@@ -341,6 +362,7 @@ class Compaction {
|
||||
const uint32_t output_path_id_;
|
||||
CompressionType output_compression_;
|
||||
CompressionOptions output_compression_opts_;
|
||||
Temperature output_temperature_;
|
||||
// If true, then the compaction can be done by simply deleting input files.
|
||||
const bool deletion_compaction_;
|
||||
|
||||
@@ -381,6 +403,10 @@ class Compaction {
|
||||
|
||||
// Reason for compaction
|
||||
CompactionReason compaction_reason_;
|
||||
|
||||
// Notify on compaction completion only if listener was notified on compaction
|
||||
// begin.
|
||||
bool notify_on_compaction_completion_;
|
||||
};
|
||||
|
||||
// Return sum of sizes of all files in `files`.
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
struct CompactionIterationStats {
|
||||
@@ -38,4 +40,6 @@ struct CompactionIterationStats {
|
||||
// Blob related statistics
|
||||
uint64_t num_blobs_read = 0;
|
||||
uint64_t total_blob_bytes_read = 0;
|
||||
uint64_t num_blobs_relocated = 0;
|
||||
uint64_t total_blob_bytes_relocated = 0;
|
||||
};
|
||||
@@ -11,27 +11,12 @@
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "logging/logging.h"
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/listener.h"
|
||||
#include "table/internal_iterator.h"
|
||||
#include "test_util/sync_point.h"
|
||||
|
||||
#define DEFINITELY_IN_SNAPSHOT(seq, snapshot) \
|
||||
((seq) <= (snapshot) && \
|
||||
(snapshot_checker_ == nullptr || \
|
||||
LIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) == \
|
||||
SnapshotCheckerResult::kInSnapshot)))
|
||||
|
||||
#define DEFINITELY_NOT_IN_SNAPSHOT(seq, snapshot) \
|
||||
((seq) > (snapshot) || \
|
||||
(snapshot_checker_ != nullptr && \
|
||||
UNLIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) == \
|
||||
SnapshotCheckerResult::kNotInSnapshot)))
|
||||
|
||||
#define IN_EARLIEST_SNAPSHOT(seq) \
|
||||
((seq) <= earliest_snapshot_ && \
|
||||
(snapshot_checker_ == nullptr || LIKELY(IsInEarliestSnapshot(seq))))
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -45,6 +30,7 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: CompactionIterator(
|
||||
@@ -55,7 +41,8 @@ CompactionIterator::CompactionIterator(
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new RealCompaction(compaction) : nullptr),
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum,
|
||||
manual_compaction_paused, info_log, full_history_ts_low) {}
|
||||
manual_compaction_paused, manual_compaction_canceled, info_log,
|
||||
full_history_ts_low) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -70,12 +57,11 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: input_(
|
||||
input, cmp,
|
||||
compaction ==
|
||||
nullptr), // Now only need to count number of entries in flush.
|
||||
: input_(input, cmp,
|
||||
!compaction || compaction->DoesInputReferenceBlobFiles()),
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
snapshots_(snapshots),
|
||||
@@ -91,6 +77,7 @@ CompactionIterator::CompactionIterator(
|
||||
compaction_filter_(compaction_filter),
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
manual_compaction_canceled_(manual_compaction_canceled),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
info_log_(info_log),
|
||||
allow_data_in_errors_(allow_data_in_errors),
|
||||
@@ -446,6 +433,8 @@ void CompactionIterator::NextFromInput() {
|
||||
|
||||
has_outputted_key_ = false;
|
||||
|
||||
last_key_seq_zeroed_ = false;
|
||||
|
||||
current_key_committed_ = KeyCommitted(ikey_.sequence);
|
||||
|
||||
// Apply the compaction filter to the first committed version of the user
|
||||
@@ -509,11 +498,11 @@ void CompactionIterator::NextFromInput() {
|
||||
"Unexpected key type %d for compaction output",
|
||||
ikey_.type);
|
||||
}
|
||||
assert(current_user_key_snapshot_ == last_snapshot);
|
||||
if (current_user_key_snapshot_ != last_snapshot) {
|
||||
assert(current_user_key_snapshot_ >= last_snapshot);
|
||||
if (current_user_key_snapshot_ < last_snapshot) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"current_user_key_snapshot_ (%" PRIu64
|
||||
") != last_snapshot (%" PRIu64 ")",
|
||||
") < last_snapshot (%" PRIu64 ")",
|
||||
current_user_key_snapshot_, last_snapshot);
|
||||
}
|
||||
|
||||
@@ -533,6 +522,25 @@ void CompactionIterator::NextFromInput() {
|
||||
// 2) We've already returned a record in this snapshot -OR-
|
||||
// there are no earlier earliest_write_conflict_snapshot.
|
||||
//
|
||||
// A note about 2) above:
|
||||
// we try to determine whether there is any earlier write conflict
|
||||
// checking snapshot by calling DefinitelyInSnapshot() with seq and
|
||||
// earliest_write_conflict_snapshot as arguments. For write-prepared
|
||||
// and write-unprepared transactions, if earliest_write_conflict_snapshot
|
||||
// is evicted from WritePreparedTxnDB::commit_cache, then
|
||||
// DefinitelyInSnapshot(seq, earliest_write_conflict_snapshot) returns
|
||||
// false, even if the seq is actually visible within
|
||||
// earliest_write_conflict_snapshot. Consequently, CompactionIterator
|
||||
// may try to zero out its sequence number, thus hitting assertion error
|
||||
// in debug mode or cause incorrect DBIter return result.
|
||||
// We observe that earliest_write_conflict_snapshot >= earliest_snapshot,
|
||||
// and the seq zeroing logic depends on
|
||||
// DefinitelyInSnapshot(seq, earliest_snapshot). Therefore, if we cannot
|
||||
// determine whether seq is **definitely** in
|
||||
// earliest_write_conflict_snapshot, then we can additionally check if
|
||||
// seq is definitely in earliest_snapshot. If the latter holds, then the
|
||||
// former holds too.
|
||||
//
|
||||
// Rule 1 is needed for SingleDelete correctness. Rule 2 is needed to
|
||||
// allow Transactions to do write-conflict checking (if we compacted away
|
||||
// all keys, then we wouldn't know that a write happened in this
|
||||
@@ -557,9 +565,21 @@ void CompactionIterator::NextFromInput() {
|
||||
// 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.
|
||||
//
|
||||
// Note: If timestamp is enabled, then record will be eligible for
|
||||
// deletion, only if, along with above conditions (Rule 1 and Rule 2)
|
||||
// full_history_ts_low_ is specified and timestamp for that key is less
|
||||
// than *full_history_ts_low_. If it's not eligible for deletion, then we
|
||||
// will output the SingleDelete. For Optimization 3 also, if
|
||||
// full_history_ts_low_ is specified and timestamp for the key is less
|
||||
// than *full_history_ts_low_ then only optimization will be applied.
|
||||
|
||||
// The easiest way to process a SingleDelete during iteration is to peek
|
||||
// ahead at the next key.
|
||||
const bool is_timestamp_eligible_for_gc =
|
||||
(timestamp_size_ == 0 ||
|
||||
(full_history_ts_low_ && cmp_with_history_ts_low_ < 0));
|
||||
|
||||
ParsedInternalKey next_ikey;
|
||||
AdvanceInputIter();
|
||||
|
||||
@@ -568,24 +588,47 @@ void CompactionIterator::NextFromInput() {
|
||||
if (input_.Valid() &&
|
||||
ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok() &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
if (prev_snapshot == 0 ||
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot)) {
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
#ifndef NDEBUG
|
||||
const Compaction* c =
|
||||
compaction_ ? compaction_->real_compaction() : nullptr;
|
||||
#endif
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:SingleDelete:1",
|
||||
const_cast<Compaction*>(c));
|
||||
if (last_key_seq_zeroed_) {
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
assert(bottommost_level_);
|
||||
AdvanceInputIter();
|
||||
} else if (prev_snapshot == 0 ||
|
||||
DefinitelyNotInSnapshot(next_ikey.sequence, prev_snapshot)) {
|
||||
// Check whether the next key belongs to the same snapshot as the
|
||||
// SingleDelete.
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:SingleDelete:2", nullptr);
|
||||
if (next_ikey.type == kTypeSingleDeletion) {
|
||||
// We encountered two SingleDeletes in a row. This could be due to
|
||||
// unexpected user input.
|
||||
// Skip the first SingleDelete and let the next iteration decide how
|
||||
// to handle the second SingleDelete
|
||||
// We encountered two SingleDeletes for same key in a row. This
|
||||
// could be due to unexpected user input. Skip the first
|
||||
// SingleDelete and let the next iteration decide how to handle the
|
||||
// second SingleDelete
|
||||
|
||||
// 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 (!is_timestamp_eligible_for_gc) {
|
||||
// We cannot drop the SingleDelete as timestamp is enabled, and
|
||||
// timestamp of this key is greater than or equal to
|
||||
// *full_history_ts_low_. We will output the SingleDelete.
|
||||
valid_ = true;
|
||||
} else if (has_outputted_key_ ||
|
||||
DEFINITELY_IN_SNAPSHOT(
|
||||
ikey_.sequence, earliest_write_conflict_snapshot_)) {
|
||||
DefinitelyInSnapshot(ikey_.sequence,
|
||||
earliest_write_conflict_snapshot_) ||
|
||||
(earliest_snapshot_ < earliest_write_conflict_snapshot_ &&
|
||||
DefinitelyInSnapshot(ikey_.sequence,
|
||||
earliest_snapshot_))) {
|
||||
// Found a matching value, we can drop the single delete and the
|
||||
// value. It is safe to drop both records since we've already
|
||||
// outputted a key in this snapshot, or there is no earlier
|
||||
@@ -618,11 +661,17 @@ void CompactionIterator::NextFromInput() {
|
||||
// Set up the Put to be outputted in the next iteration.
|
||||
// (Optimization 3).
|
||||
clear_and_output_next_key_ = true;
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:KeepSDForWW",
|
||||
/*arg=*/nullptr);
|
||||
}
|
||||
} else {
|
||||
// We hit the next snapshot without hitting a put, so the iterator
|
||||
// returns the single delete.
|
||||
valid_ = true;
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:SingleDelete:3",
|
||||
const_cast<Compaction*>(c));
|
||||
}
|
||||
} else {
|
||||
// We are at the end of the input, could not parse the next key, or hit
|
||||
@@ -633,9 +682,11 @@ void CompactionIterator::NextFromInput() {
|
||||
// iteration. If the next key is corrupt, we return before the
|
||||
// comparison, so the value of has_current_user_key does not matter.
|
||||
has_current_user_key_ = false;
|
||||
if (compaction_ != nullptr && IN_EARLIEST_SNAPSHOT(ikey_.sequence) &&
|
||||
if (compaction_ != nullptr &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
&level_ptrs_) &&
|
||||
is_timestamp_eligible_for_gc) {
|
||||
// Key doesn't exist outside of this range.
|
||||
// Can compact out this SingleDelete.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
@@ -643,6 +694,11 @@ void CompactionIterator::NextFromInput() {
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
} else if (last_key_seq_zeroed_) {
|
||||
// Skip.
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
assert(bottommost_level_);
|
||||
} else {
|
||||
// Output SingleDelete
|
||||
valid_ = true;
|
||||
@@ -677,7 +733,7 @@ void CompactionIterator::NextFromInput() {
|
||||
(ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
IN_EARLIEST_SNAPSHOT(ikey_.sequence) &&
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
&level_ptrs_)) {
|
||||
@@ -720,6 +776,13 @@ void CompactionIterator::NextFromInput() {
|
||||
ikey_.user_key, &level_ptrs_));
|
||||
ParsedInternalKey next_ikey;
|
||||
AdvanceInputIter();
|
||||
#ifndef NDEBUG
|
||||
const Compaction* c =
|
||||
compaction_ ? compaction_->real_compaction() : nullptr;
|
||||
#endif
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"CompactionIterator::NextFromInput:BottommostDelete:1",
|
||||
const_cast<Compaction*>(c));
|
||||
// Skip over all versions of this key that happen to occur in the same
|
||||
// snapshot range as the delete.
|
||||
//
|
||||
@@ -732,7 +795,7 @@ void CompactionIterator::NextFromInput() {
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 ||
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot))) {
|
||||
DefinitelyNotInSnapshot(next_ikey.sequence, prev_snapshot))) {
|
||||
AdvanceInputIter();
|
||||
}
|
||||
// If you find you still need to output a row with this key, we need to output the
|
||||
@@ -752,13 +815,15 @@ void CompactionIterator::NextFromInput() {
|
||||
}
|
||||
|
||||
pinned_iters_mgr_.StartPinning();
|
||||
Version* version = compaction_ ? compaction_->input_version() : nullptr;
|
||||
|
||||
// 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
|
||||
// object to minimize change to the existing flow.
|
||||
Status s =
|
||||
merge_helper_->MergeUntil(&input_, range_del_agg_, prev_snapshot,
|
||||
bottommost_level_, allow_data_in_errors_);
|
||||
Status s = merge_helper_->MergeUntil(&input_, range_del_agg_,
|
||||
prev_snapshot, bottommost_level_,
|
||||
allow_data_in_errors_, version);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
|
||||
if (!s.ok() && !s.IsMergeInProgress()) {
|
||||
@@ -910,6 +975,9 @@ void CompactionIterator::GarbageCollectBlobIfNeeded() {
|
||||
++iter_stats_.num_blobs_read;
|
||||
iter_stats_.total_blob_bytes_read += bytes_read;
|
||||
|
||||
++iter_stats_.num_blobs_relocated;
|
||||
iter_stats_.total_blob_bytes_relocated += blob_index.size();
|
||||
|
||||
value_ = blob_value_;
|
||||
|
||||
if (ExtractLargeValueIfNeededImpl()) {
|
||||
@@ -973,14 +1041,22 @@ void CompactionIterator::PrepareOutput() {
|
||||
if (valid_ && compaction_ != nullptr &&
|
||||
!compaction_->allow_ingest_behind() &&
|
||||
ikeyNotNeededForIncrementalSnapshot() && bottommost_level_ &&
|
||||
IN_EARLIEST_SNAPSHOT(ikey_.sequence) && ikey_.type != kTypeMerge) {
|
||||
assert(ikey_.type != kTypeDeletion && ikey_.type != kTypeSingleDeletion);
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion) {
|
||||
DefinitelyInSnapshot(ikey_.sequence, earliest_snapshot_) &&
|
||||
ikey_.type != kTypeMerge) {
|
||||
assert(ikey_.type != kTypeDeletion);
|
||||
assert(ikey_.type != kTypeSingleDeletion ||
|
||||
(timestamp_size_ || full_history_ts_low_));
|
||||
if (ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeSingleDeletion &&
|
||||
(!timestamp_size_ || !full_history_ts_low_))) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"Unexpected key type %d for seq-zero optimization",
|
||||
ikey_.type);
|
||||
}
|
||||
ikey_.sequence = 0;
|
||||
last_key_seq_zeroed_ = true;
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator::PrepareOutput:ZeroingSeq",
|
||||
&ikey_);
|
||||
if (!timestamp_size_) {
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
} else if (full_history_ts_low_ && cmp_with_history_ts_low_ < 0) {
|
||||
@@ -1049,41 +1125,6 @@ inline bool CompactionIterator::ikeyNotNeededForIncrementalSnapshot() {
|
||||
(ikey_.sequence < preserve_deletes_seqnum_);
|
||||
}
|
||||
|
||||
bool CompactionIterator::IsInEarliestSnapshot(SequenceNumber sequence) {
|
||||
assert(snapshot_checker_ != nullptr);
|
||||
bool pre_condition = (earliest_snapshot_ == kMaxSequenceNumber ||
|
||||
(earliest_snapshot_iter_ != snapshots_->end() &&
|
||||
*earliest_snapshot_iter_ == earliest_snapshot_));
|
||||
assert(pre_condition);
|
||||
if (!pre_condition) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"Pre-Condition is not hold in IsInEarliestSnapshot");
|
||||
}
|
||||
auto in_snapshot =
|
||||
snapshot_checker_->CheckInSnapshot(sequence, earliest_snapshot_);
|
||||
while (UNLIKELY(in_snapshot == SnapshotCheckerResult::kSnapshotReleased)) {
|
||||
// Avoid the the current earliest_snapshot_ being return as
|
||||
// earliest visible snapshot for the next value. So if a value's sequence
|
||||
// is zero-ed out by PrepareOutput(), the next value will be compact out.
|
||||
released_snapshots_.insert(earliest_snapshot_);
|
||||
earliest_snapshot_iter_++;
|
||||
|
||||
if (earliest_snapshot_iter_ == snapshots_->end()) {
|
||||
earliest_snapshot_ = kMaxSequenceNumber;
|
||||
} else {
|
||||
earliest_snapshot_ = *earliest_snapshot_iter_;
|
||||
}
|
||||
in_snapshot =
|
||||
snapshot_checker_->CheckInSnapshot(sequence, earliest_snapshot_);
|
||||
}
|
||||
assert(in_snapshot != SnapshotCheckerResult::kSnapshotReleased);
|
||||
if (in_snapshot == SnapshotCheckerResult::kSnapshotReleased) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"Unexpected released snapshot in IsInEarliestSnapshot");
|
||||
}
|
||||
return in_snapshot == SnapshotCheckerResult::kInSnapshot;
|
||||
}
|
||||
|
||||
uint64_t CompactionIterator::ComputeBlobGarbageCollectionCutoffFileNumber(
|
||||
const CompactionProxy* compaction) {
|
||||
if (!compaction) {
|
||||
|
||||
@@ -30,7 +30,9 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
public:
|
||||
SequenceIterWrapper(InternalIterator* iter, const Comparator* cmp,
|
||||
bool need_count_entries)
|
||||
: cmp_(cmp), inner_iter_(iter), need_count_entries_(need_count_entries) {}
|
||||
: icmp_(cmp, /*named=*/false),
|
||||
inner_iter_(iter),
|
||||
need_count_entries_(need_count_entries) {}
|
||||
bool Valid() const override { return inner_iter_->Valid(); }
|
||||
Status status() const override { return inner_iter_->status(); }
|
||||
void Next() override {
|
||||
@@ -44,7 +46,7 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
// For flush cases, we need to count total number of entries, so we
|
||||
// do Next() rather than Seek().
|
||||
while (inner_iter_->Valid() &&
|
||||
cmp_->Compare(inner_iter_->key(), target) < 0) {
|
||||
icmp_.Compare(inner_iter_->key(), target) < 0) {
|
||||
Next();
|
||||
}
|
||||
}
|
||||
@@ -61,7 +63,7 @@ class SequenceIterWrapper : public InternalIterator {
|
||||
uint64_t num_itered() const { return num_itered_; }
|
||||
|
||||
private:
|
||||
const Comparator* cmp_; // not owned
|
||||
InternalKeyComparator icmp_;
|
||||
InternalIterator* inner_iter_; // not owned
|
||||
uint64_t num_itered_ = 0;
|
||||
bool need_count_entries_;
|
||||
@@ -95,6 +97,10 @@ class CompactionIterator {
|
||||
virtual double blob_garbage_collection_age_cutoff() const = 0;
|
||||
|
||||
virtual Version* input_version() const = 0;
|
||||
|
||||
virtual bool DoesInputReferenceBlobFiles() const = 0;
|
||||
|
||||
virtual const Compaction* real_compaction() const = 0;
|
||||
};
|
||||
|
||||
class RealCompaction : public CompactionProxy {
|
||||
@@ -102,7 +108,7 @@ class CompactionIterator {
|
||||
explicit RealCompaction(const Compaction* compaction)
|
||||
: compaction_(compaction) {
|
||||
assert(compaction_);
|
||||
assert(compaction_->immutable_cf_options());
|
||||
assert(compaction_->immutable_options());
|
||||
assert(compaction_->mutable_cf_options());
|
||||
}
|
||||
|
||||
@@ -124,11 +130,11 @@ class CompactionIterator {
|
||||
}
|
||||
|
||||
bool allow_ingest_behind() const override {
|
||||
return compaction_->immutable_cf_options()->allow_ingest_behind;
|
||||
return compaction_->immutable_options()->allow_ingest_behind;
|
||||
}
|
||||
|
||||
bool preserve_deletes() const override {
|
||||
return compaction_->immutable_cf_options()->preserve_deletes;
|
||||
return compaction_->immutable_options()->preserve_deletes;
|
||||
}
|
||||
|
||||
bool enable_blob_garbage_collection() const override {
|
||||
@@ -144,44 +150,50 @@ class CompactionIterator {
|
||||
return compaction_->input_version();
|
||||
}
|
||||
|
||||
bool DoesInputReferenceBlobFiles() const override {
|
||||
return compaction_->DoesInputReferenceBlobFiles();
|
||||
}
|
||||
|
||||
const Compaction* real_compaction() const override { return compaction_; }
|
||||
|
||||
private:
|
||||
const Compaction* compaction_;
|
||||
};
|
||||
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
bool allow_data_in_errors,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
const Compaction* compaction = nullptr,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
|
||||
// Constructor with custom CompactionProxy, used for tests.
|
||||
CompactionIterator(InternalIterator* input, const Comparator* cmp,
|
||||
MergeHelper* merge_helper, SequenceNumber last_sequence,
|
||||
std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder,
|
||||
bool allow_data_in_errors,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker, Env* env,
|
||||
bool report_detailed_time, bool expect_valid_internal_key,
|
||||
CompactionRangeDelAggregator* range_del_agg,
|
||||
BlobFileBuilder* blob_file_builder, bool allow_data_in_errors,
|
||||
std::unique_ptr<CompactionProxy> compaction,
|
||||
const CompactionFilter* compaction_filter = nullptr,
|
||||
const std::atomic<bool>* shutting_down = nullptr,
|
||||
const SequenceNumber preserve_deletes_seqnum = 0,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::shared_ptr<Logger> info_log = nullptr,
|
||||
const std::string* full_history_ts_low = nullptr);
|
||||
|
||||
~CompactionIterator();
|
||||
|
||||
@@ -259,7 +271,9 @@ class CompactionIterator {
|
||||
SnapshotCheckerResult::kInSnapshot;
|
||||
}
|
||||
|
||||
bool IsInEarliestSnapshot(SequenceNumber sequence);
|
||||
bool DefinitelyInSnapshot(SequenceNumber seq, SequenceNumber snapshot);
|
||||
|
||||
bool DefinitelyNotInSnapshot(SequenceNumber seq, SequenceNumber snapshot);
|
||||
|
||||
// Extract user-defined timestamp from user key if possible and compare it
|
||||
// with *full_history_ts_low_ if applicable.
|
||||
@@ -301,6 +315,7 @@ class CompactionIterator {
|
||||
const CompactionFilter* compaction_filter_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<int>* manual_compaction_paused_;
|
||||
const std::atomic<bool>* manual_compaction_canceled_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
bool bottommost_level_;
|
||||
bool valid_ = false;
|
||||
@@ -386,6 +401,10 @@ class CompactionIterator {
|
||||
|
||||
const int level_;
|
||||
|
||||
// True if the previous internal key (same user key)'s sequence number has
|
||||
// just been zeroed out during bottommost compaction.
|
||||
bool last_key_seq_zeroed_{false};
|
||||
|
||||
void AdvanceInputIter() { input_.Next(); }
|
||||
|
||||
void SkipUntil(const Slice& skip_until) { input_.Seek(skip_until); }
|
||||
@@ -397,8 +416,27 @@ class CompactionIterator {
|
||||
|
||||
bool IsPausingManualCompaction() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
return manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0;
|
||||
return (manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0) ||
|
||||
(manual_compaction_canceled_ &&
|
||||
manual_compaction_canceled_->load(std::memory_order_relaxed));
|
||||
}
|
||||
};
|
||||
|
||||
inline bool CompactionIterator::DefinitelyInSnapshot(SequenceNumber seq,
|
||||
SequenceNumber snapshot) {
|
||||
return ((seq) <= (snapshot) &&
|
||||
(snapshot_checker_ == nullptr ||
|
||||
LIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) ==
|
||||
SnapshotCheckerResult::kInSnapshot)));
|
||||
}
|
||||
|
||||
inline bool CompactionIterator::DefinitelyNotInSnapshot(
|
||||
SequenceNumber seq, SequenceNumber snapshot) {
|
||||
return ((seq) > (snapshot) ||
|
||||
(snapshot_checker_ != nullptr &&
|
||||
UNLIKELY(snapshot_checker_->CheckInSnapshot((seq), (snapshot)) ==
|
||||
SnapshotCheckerResult::kNotInSnapshot)));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -3,15 +3,17 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "port/port.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
#include "util/vector_iterator.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -86,7 +88,7 @@ class FilterAllKeysCompactionFilter : public CompactionFilter {
|
||||
const char* Name() const override { return "AllKeysCompactionFilter"; }
|
||||
};
|
||||
|
||||
class LoggingForwardVectorIterator : public InternalIterator {
|
||||
class LoggingForwardVectorIterator : public VectorIterator {
|
||||
public:
|
||||
struct Action {
|
||||
enum class Type {
|
||||
@@ -108,22 +110,19 @@ class LoggingForwardVectorIterator : public InternalIterator {
|
||||
|
||||
LoggingForwardVectorIterator(const std::vector<std::string>& keys,
|
||||
const std::vector<std::string>& values)
|
||||
: keys_(keys), values_(values), current_(keys.size()) {
|
||||
assert(keys_.size() == values_.size());
|
||||
: VectorIterator(keys, values) {
|
||||
current_ = keys_.size();
|
||||
}
|
||||
|
||||
bool Valid() const override { return current_ < keys_.size(); }
|
||||
|
||||
void SeekToFirst() override {
|
||||
log.emplace_back(Action::Type::SEEK_TO_FIRST);
|
||||
current_ = 0;
|
||||
VectorIterator::SeekToFirst();
|
||||
}
|
||||
void SeekToLast() override { assert(false); }
|
||||
|
||||
void Seek(const Slice& target) override {
|
||||
log.emplace_back(Action::Type::SEEK, target.ToString());
|
||||
current_ = std::lower_bound(keys_.begin(), keys_.end(), target.ToString()) -
|
||||
keys_.begin();
|
||||
VectorIterator::Seek(target);
|
||||
}
|
||||
|
||||
void SeekForPrev(const Slice& /*target*/) override { assert(false); }
|
||||
@@ -131,27 +130,20 @@ class LoggingForwardVectorIterator : public InternalIterator {
|
||||
void Next() override {
|
||||
assert(Valid());
|
||||
log.emplace_back(Action::Type::NEXT);
|
||||
current_++;
|
||||
VectorIterator::Next();
|
||||
}
|
||||
void Prev() override { assert(false); }
|
||||
|
||||
Slice key() const override {
|
||||
assert(Valid());
|
||||
return Slice(keys_[current_]);
|
||||
return VectorIterator::key();
|
||||
}
|
||||
Slice value() const override {
|
||||
assert(Valid());
|
||||
return Slice(values_[current_]);
|
||||
return VectorIterator::value();
|
||||
}
|
||||
|
||||
Status status() const override { return Status::OK(); }
|
||||
|
||||
std::vector<Action> log;
|
||||
|
||||
private:
|
||||
std::vector<std::string> keys_;
|
||||
std::vector<std::string> values_;
|
||||
size_t current_;
|
||||
};
|
||||
|
||||
class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
@@ -182,6 +174,10 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
|
||||
Version* input_version() const override { return nullptr; }
|
||||
|
||||
bool DoesInputReferenceBlobFiles() const override { return false; }
|
||||
|
||||
const Compaction* real_compaction() const override { return nullptr; }
|
||||
|
||||
bool key_not_exists_beyond_output_level = false;
|
||||
|
||||
bool is_bottommost_level = false;
|
||||
@@ -240,7 +236,7 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
bool key_not_exists_beyond_output_level = false,
|
||||
const std::string* full_history_ts_low = nullptr) {
|
||||
std::unique_ptr<InternalIterator> unfragmented_range_del_iter(
|
||||
new test::VectorIterator(range_del_ks, range_del_vs));
|
||||
new VectorIterator(range_del_ks, range_del_vs, &icmp_));
|
||||
auto tombstone_list = std::make_shared<FragmentedRangeTombstoneList>(
|
||||
std::move(unfragmented_range_del_iter), icmp_);
|
||||
std::unique_ptr<FragmentedRangeTombstoneIterator> range_del_iter(
|
||||
@@ -282,7 +278,8 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
range_del_agg_.get(), nullptr /* blob_file_builder */,
|
||||
true /*allow_data_in_errors*/, std::move(compaction), filter,
|
||||
&shutting_down_, /*preserve_deletes_seqnum=*/0,
|
||||
/*manual_compaction_paused=*/nullptr, /*info_log=*/nullptr,
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, /*info_log=*/nullptr,
|
||||
full_history_ts_low));
|
||||
}
|
||||
|
||||
@@ -1253,6 +1250,101 @@ TEST_P(CompactionIteratorTsGcTest, RewriteTs) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, SingleDeleteNoKeyEligibleForGC) {
|
||||
constexpr char user_key[][2] = {{'a', '\0'}, {'b', '\0'}};
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/104, user_key[0], /*seq=*/4, kTypeSingleDeletion),
|
||||
test::KeyStr(/*ts=*/103, user_key[0], /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/102, user_key[1], /*seq=*/2, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a3", "b2"};
|
||||
std::string full_history_ts_low;
|
||||
// All keys' timestamps are newer than or equal to 102, thus none of them
|
||||
// will be eligible for GC.
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
const std::vector<std::string>& expected_keys = input_keys;
|
||||
const std::vector<std::string>& expected_values = input_values;
|
||||
const std::vector<std::pair<bool, bool>> params = {
|
||||
{false, false}, {false, true}, {true, true}};
|
||||
for (const std::pair<bool, bool>& param : params) {
|
||||
const bool bottommost_level = param.first;
|
||||
const bool key_not_exists_beyond_output_level = param.second;
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
bottommost_level,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
key_not_exists_beyond_output_level, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, SingleDeleteDropTombstones) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key, /*seq=*/4, kTypeSingleDeletion),
|
||||
test::KeyStr(/*ts=*/102, user_key, /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key, /*seq=*/2, kTypeSingleDeletion),
|
||||
test::KeyStr(/*ts=*/100, user_key, /*seq=*/1, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "", "a0"};
|
||||
const std::vector<std::string> expected_keys = {input_keys[0], input_keys[1]};
|
||||
const std::vector<std::string> expected_values = {"", "a2"};
|
||||
|
||||
// Take a snapshot at seq 2.
|
||||
AddSnapshot(2);
|
||||
{
|
||||
const std::vector<std::pair<bool, bool>> params = {
|
||||
{false, false}, {false, true}, {true, true}};
|
||||
for (const std::pair<bool, bool>& param : params) {
|
||||
const bool bottommost_level = param.first;
|
||||
const bool key_not_exists_beyond_output_level = param.second;
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
bottommost_level,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
key_not_exists_beyond_output_level, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, SingleDeleteAllKeysOlderThanThreshold) {
|
||||
constexpr char user_key[][2] = {{'a', '\0'}, {'b', '\0'}};
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key[0], /*seq=*/4, kTypeSingleDeletion),
|
||||
test::KeyStr(/*ts=*/102, user_key[0], /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/104, user_key[1], /*seq=*/5, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "b5"};
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, std::numeric_limits<uint64_t>::max());
|
||||
{
|
||||
// With a snapshot at seq 3, both the deletion marker and the key at 3 must
|
||||
// be preserved.
|
||||
AddSnapshot(3);
|
||||
const std::vector<std::string> expected_keys = {
|
||||
input_keys[0], input_keys[1], input_keys[2]};
|
||||
const std::vector<std::string> expected_values = {"", "a2", "b5"};
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
ClearSnapshots();
|
||||
}
|
||||
{
|
||||
// No snapshot.
|
||||
const std::vector<std::string> expected_keys = {input_keys[2]};
|
||||
const std::vector<std::string> expected_values = {"b5"};
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_seq=*/kMaxSequenceNumber,
|
||||
/*merge_operator=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionIteratorTsGcTestInstance,
|
||||
CompactionIteratorTsGcTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
+370
-105
@@ -20,9 +20,12 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_counting_iterator.h"
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/blob/blob_garbage_meter.h"
|
||||
#include "db/builder.h"
|
||||
#include "db/compaction/clipping_iterator.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_iter.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -104,6 +107,10 @@ const char* GetCompactionReasonString(CompactionReason compaction_reason) {
|
||||
return "ExternalSstIngestion";
|
||||
case CompactionReason::kPeriodicCompaction:
|
||||
return "PeriodicCompaction";
|
||||
case CompactionReason::kChangeTemperature:
|
||||
return "ChangeTemperature";
|
||||
case CompactionReason::kForcedBlobGC:
|
||||
return "ForcedBlobGC";
|
||||
case CompactionReason::kNumOfReasons:
|
||||
// fall through
|
||||
default:
|
||||
@@ -146,6 +153,7 @@ struct CompactionJob::SubcompactionState {
|
||||
// State kept for output being generated
|
||||
std::vector<Output> outputs;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
std::unique_ptr<BlobGarbageMeter> blob_garbage_meter;
|
||||
std::unique_ptr<WritableFileWriter> outfile;
|
||||
std::unique_ptr<TableBuilder> builder;
|
||||
|
||||
@@ -162,6 +170,13 @@ struct CompactionJob::SubcompactionState {
|
||||
}
|
||||
}
|
||||
|
||||
// Some identified files with old oldest ancester time and the range should be
|
||||
// isolated out so that the output file(s) in that range can be merged down
|
||||
// for TTL and clear the timestamps for the range.
|
||||
std::vector<FileMetaData*> files_to_cut_for_ttl;
|
||||
int cur_files_to_cut_for_ttl = -1;
|
||||
int next_files_to_cut_for_ttl = 0;
|
||||
|
||||
uint64_t current_output_file_size = 0;
|
||||
|
||||
// State during the subcompaction
|
||||
@@ -176,9 +191,17 @@ struct CompactionJob::SubcompactionState {
|
||||
uint64_t overlapped_bytes = 0;
|
||||
// A flag determine whether the key has been seen in ShouldStopBefore()
|
||||
bool seen_key = false;
|
||||
// sub compaction job id, which is used to identify different sub-compaction
|
||||
// within the same compaction job.
|
||||
const uint32_t sub_job_id;
|
||||
|
||||
SubcompactionState(Compaction* c, Slice* _start, Slice* _end, uint64_t size)
|
||||
: compaction(c), start(_start), end(_end), approx_size(size) {
|
||||
SubcompactionState(Compaction* c, Slice* _start, Slice* _end, uint64_t size,
|
||||
uint32_t _sub_job_id)
|
||||
: compaction(c),
|
||||
start(_start),
|
||||
end(_end),
|
||||
approx_size(size),
|
||||
sub_job_id(_sub_job_id) {
|
||||
assert(compaction != nullptr);
|
||||
}
|
||||
|
||||
@@ -196,6 +219,8 @@ struct CompactionJob::SubcompactionState {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
void FillFilesToCutForTtl();
|
||||
|
||||
// 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) {
|
||||
@@ -203,6 +228,7 @@ struct CompactionJob::SubcompactionState {
|
||||
&compaction->column_family_data()->internal_comparator();
|
||||
const std::vector<FileMetaData*>& grandparents = compaction->grandparents();
|
||||
|
||||
bool grandparant_file_switched = false;
|
||||
// Scan to find earliest grandparent file that contains key.
|
||||
while (grandparent_index < grandparents.size() &&
|
||||
icmp->Compare(internal_key,
|
||||
@@ -210,6 +236,7 @@ struct CompactionJob::SubcompactionState {
|
||||
0) {
|
||||
if (seen_key) {
|
||||
overlapped_bytes += grandparents[grandparent_index]->fd.GetFileSize();
|
||||
grandparant_file_switched = true;
|
||||
}
|
||||
assert(grandparent_index + 1 >= grandparents.size() ||
|
||||
icmp->Compare(
|
||||
@@ -219,17 +246,99 @@ struct CompactionJob::SubcompactionState {
|
||||
}
|
||||
seen_key = true;
|
||||
|
||||
if (overlapped_bytes + curr_file_size >
|
||||
compaction->max_compaction_bytes()) {
|
||||
if (grandparant_file_switched && overlapped_bytes + curr_file_size >
|
||||
compaction->max_compaction_bytes()) {
|
||||
// Too much overlap for current output; start new output
|
||||
overlapped_bytes = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!files_to_cut_for_ttl.empty()) {
|
||||
if (cur_files_to_cut_for_ttl != -1) {
|
||||
// Previous key is inside the range of a file
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl[cur_files_to_cut_for_ttl]
|
||||
->largest.Encode()) > 0) {
|
||||
next_files_to_cut_for_ttl = cur_files_to_cut_for_ttl + 1;
|
||||
cur_files_to_cut_for_ttl = -1;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Look for the key position
|
||||
while (next_files_to_cut_for_ttl <
|
||||
static_cast<int>(files_to_cut_for_ttl.size())) {
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl[next_files_to_cut_for_ttl]
|
||||
->smallest.Encode()) >= 0) {
|
||||
if (icmp->Compare(internal_key,
|
||||
files_to_cut_for_ttl[next_files_to_cut_for_ttl]
|
||||
->largest.Encode()) <= 0) {
|
||||
// With in the current file
|
||||
cur_files_to_cut_for_ttl = next_files_to_cut_for_ttl;
|
||||
return true;
|
||||
}
|
||||
// Beyond the current file
|
||||
next_files_to_cut_for_ttl++;
|
||||
} else {
|
||||
// Still fall into the gap
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
Status ProcessOutFlowIfNeeded(const Slice& key, const Slice& value) {
|
||||
if (!blob_garbage_meter) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
return blob_garbage_meter->ProcessOutFlow(key, value);
|
||||
}
|
||||
};
|
||||
|
||||
void CompactionJob::SubcompactionState::FillFilesToCutForTtl() {
|
||||
if (compaction->immutable_options()->compaction_style !=
|
||||
CompactionStyle::kCompactionStyleLevel ||
|
||||
compaction->immutable_options()->compaction_pri !=
|
||||
CompactionPri::kMinOverlappingRatio ||
|
||||
compaction->mutable_cf_options()->ttl == 0 ||
|
||||
compaction->num_input_levels() < 2 || compaction->bottommost_level()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We define new file with oldest ancestor time to be younger than 1/4 TTL,
|
||||
// and an old one to be older than 1/2 TTL time.
|
||||
int64_t temp_current_time;
|
||||
auto get_time_status = compaction->immutable_options()->clock->GetCurrentTime(
|
||||
&temp_current_time);
|
||||
if (!get_time_status.ok()) {
|
||||
return;
|
||||
}
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
if (current_time < compaction->mutable_cf_options()->ttl) {
|
||||
return;
|
||||
}
|
||||
uint64_t old_age_thres =
|
||||
current_time - compaction->mutable_cf_options()->ttl / 2;
|
||||
|
||||
const std::vector<FileMetaData*>& olevel =
|
||||
*(compaction->inputs(compaction->num_input_levels() - 1));
|
||||
for (FileMetaData* file : olevel) {
|
||||
// Worth filtering out by start and end?
|
||||
uint64_t oldest_ancester_time = file->TryGetOldestAncesterTime();
|
||||
// We put old files if they are not too small to prevent a flood
|
||||
// of small files.
|
||||
if (oldest_ancester_time < old_age_thres &&
|
||||
file->fd.GetFileSize() >
|
||||
compaction->mutable_cf_options()->target_file_size_base / 2) {
|
||||
files_to_cut_for_ttl.push_back(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Maintains state for the entire compaction
|
||||
struct CompactionJob::CompactionState {
|
||||
Compaction* const compaction;
|
||||
@@ -313,9 +422,10 @@ CompactionJob::CompactionJob(
|
||||
EventLogger* event_logger, bool paranoid_file_checks, bool measure_io_stats,
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<int>* manual_compaction_paused, const std::string& db_id,
|
||||
const std::string& db_session_id, std::string full_history_ts_low,
|
||||
BlobFileCompletionCallback* blob_callback)
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
std::string full_history_ts_low, BlobFileCompletionCallback* blob_callback)
|
||||
: compact_(new CompactionState(compaction)),
|
||||
compaction_stats_(compaction->compaction_reason(), 1),
|
||||
db_options_(db_options),
|
||||
@@ -339,6 +449,7 @@ CompactionJob::CompactionJob(
|
||||
versions_(versions),
|
||||
shutting_down_(shutting_down),
|
||||
manual_compaction_paused_(manual_compaction_paused),
|
||||
manual_compaction_canceled_(manual_compaction_canceled),
|
||||
preserve_deletes_seqnum_(preserve_deletes_seqnum),
|
||||
db_directory_(db_directory),
|
||||
blob_output_directory_(blob_output_directory),
|
||||
@@ -435,7 +546,8 @@ void CompactionJob::Prepare() {
|
||||
for (size_t i = 0; i <= boundaries_.size(); i++) {
|
||||
Slice* start = i == 0 ? nullptr : &boundaries_[i - 1];
|
||||
Slice* end = i == boundaries_.size() ? nullptr : &boundaries_[i];
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, sizes_[i]);
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, sizes_[i],
|
||||
static_cast<uint32_t>(i));
|
||||
}
|
||||
RecordInHistogram(stats_, NUM_SUBCOMPACTIONS_SCHEDULED,
|
||||
compact_->sub_compact_states.size());
|
||||
@@ -444,7 +556,8 @@ void CompactionJob::Prepare() {
|
||||
constexpr Slice* end = nullptr;
|
||||
constexpr uint64_t size = 0;
|
||||
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, size);
|
||||
compact_->sub_compact_states.emplace_back(c, start, end, size,
|
||||
/*sub_job_id*/ 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,9 +663,10 @@ void CompactionJob::GenSubcompactionBoundaries() {
|
||||
int base_level = v->storage_info()->base_level();
|
||||
uint64_t max_output_files = static_cast<uint64_t>(std::ceil(
|
||||
sum / min_file_fill_percent /
|
||||
MaxFileSizeForLevel(*(c->mutable_cf_options()), out_lvl,
|
||||
c->immutable_cf_options()->compaction_style, base_level,
|
||||
c->immutable_cf_options()->level_compaction_dynamic_level_bytes)));
|
||||
MaxFileSizeForLevel(
|
||||
*(c->mutable_cf_options()), out_lvl,
|
||||
c->immutable_options()->compaction_style, base_level,
|
||||
c->immutable_options()->level_compaction_dynamic_level_bytes)));
|
||||
uint64_t subcompactions =
|
||||
std::min({static_cast<uint64_t>(ranges.size()),
|
||||
static_cast<uint64_t>(c->max_subcompactions()),
|
||||
@@ -649,12 +763,16 @@ Status CompactionJob::Run() {
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
|
||||
if (output_directory_) {
|
||||
io_s = output_directory_->Fsync(IOOptions(), dbg);
|
||||
io_s = output_directory_->FsyncWithDirOptions(
|
||||
IOOptions(), dbg,
|
||||
DirFsyncOptions(DirFsyncOptions::FsyncReason::kNewFileSynced));
|
||||
}
|
||||
|
||||
if (io_s.ok() && wrote_new_blob_files && blob_output_directory_ &&
|
||||
blob_output_directory_ != output_directory_) {
|
||||
io_s = blob_output_directory_->Fsync(IOOptions(), dbg);
|
||||
io_s = blob_output_directory_->FsyncWithDirOptions(
|
||||
IOOptions(), dbg,
|
||||
DirFsyncOptions(DirFsyncOptions::FsyncReason::kNewFileSynced));
|
||||
}
|
||||
}
|
||||
if (io_status_.ok()) {
|
||||
@@ -751,7 +869,7 @@ Status CompactionJob::Run() {
|
||||
for (const auto& state : compact_->sub_compact_states) {
|
||||
for (const auto& output : state.outputs) {
|
||||
auto fn =
|
||||
TableFileName(state.compaction->immutable_cf_options()->cf_paths,
|
||||
TableFileName(state.compaction->immutable_options()->cf_paths,
|
||||
output.meta.fd.GetNumber(), output.meta.fd.GetPathId());
|
||||
tp[fn] = output.table_properties;
|
||||
}
|
||||
@@ -854,7 +972,7 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
|
||||
UpdateCompactionJobStats(stats);
|
||||
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_);
|
||||
auto stream = event_logger_->LogToBuffer(log_buffer_, 8192);
|
||||
stream << "job" << job_id_ << "event"
|
||||
<< "compaction_finished"
|
||||
<< "compaction_time_micros" << stats.micros
|
||||
@@ -905,7 +1023,8 @@ Status CompactionJob::Install(const MutableCFOptions& mutable_cf_options) {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
CompactionServiceJobStatus
|
||||
CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
SubcompactionState* sub_compact) {
|
||||
assert(sub_compact);
|
||||
assert(sub_compact->compaction);
|
||||
@@ -942,7 +1061,7 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
Status s = compaction_input.Write(&compaction_input_binary);
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
std::ostringstream input_files_oss;
|
||||
@@ -957,38 +1076,77 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
"[%s] [JOB %d] Starting remote compaction (output level: %d): %s",
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
compaction_input.output_level, input_files_oss.str().c_str());
|
||||
CompactionServiceJobInfo info(dbname_, db_id_, db_session_id_,
|
||||
GetCompactionId(sub_compact), thread_pri_);
|
||||
CompactionServiceJobStatus compaction_status =
|
||||
db_options_.compaction_service->Start(compaction_input_binary, job_id_);
|
||||
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
|
||||
sub_compact->status =
|
||||
Status::Incomplete("CompactionService failed to start compaction job.");
|
||||
return;
|
||||
db_options_.compaction_service->StartV2(info, compaction_input_binary);
|
||||
switch (compaction_status) {
|
||||
case CompactionServiceJobStatus::kSuccess:
|
||||
break;
|
||||
case CompactionServiceJobStatus::kFailure:
|
||||
sub_compact->status = Status::Incomplete(
|
||||
"CompactionService failed to start compaction job.");
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction failed to start.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
return compaction_status;
|
||||
case CompactionServiceJobStatus::kUseLocal:
|
||||
ROCKS_LOG_INFO(
|
||||
db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction fallback to local by API Start.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
return compaction_status;
|
||||
default:
|
||||
assert(false); // unknown status
|
||||
break;
|
||||
}
|
||||
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Waiting for remote compaction...",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
std::string compaction_result_binary;
|
||||
compaction_status = db_options_.compaction_service->WaitForComplete(
|
||||
job_id_, &compaction_result_binary);
|
||||
compaction_status = db_options_.compaction_service->WaitForCompleteV2(
|
||||
info, &compaction_result_binary);
|
||||
|
||||
if (compaction_status == CompactionServiceJobStatus::kUseLocal) {
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction fallback to local by API "
|
||||
"WaitForComplete.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
CompactionServiceResult compaction_result;
|
||||
s = CompactionServiceResult::Read(compaction_result_binary,
|
||||
&compaction_result);
|
||||
if (compaction_status != CompactionServiceJobStatus::kSuccess) {
|
||||
sub_compact->status =
|
||||
s.ok() ? compaction_result.status
|
||||
: Status::Incomplete(
|
||||
"CompactionService failed to run compaction job.");
|
||||
compaction_result.status.PermitUncheckedError();
|
||||
|
||||
if (compaction_status == CompactionServiceJobStatus::kFailure) {
|
||||
if (s.ok()) {
|
||||
if (compaction_result.status.ok()) {
|
||||
sub_compact->status = Status::Incomplete(
|
||||
"CompactionService failed to run the compaction job (even though "
|
||||
"the internal status is okay).");
|
||||
} else {
|
||||
// set the current sub compaction status with the status returned from
|
||||
// remote
|
||||
sub_compact->status = compaction_result.status;
|
||||
}
|
||||
} else {
|
||||
sub_compact->status = Status::Incomplete(
|
||||
"CompactionService failed to run the compaction job (and no valid "
|
||||
"result is returned).");
|
||||
compaction_result.status.PermitUncheckedError();
|
||||
}
|
||||
ROCKS_LOG_WARN(db_options_.info_log,
|
||||
"[%s] [JOB %d] Remote compaction failed, status: %s",
|
||||
compaction_input.column_family.name.c_str(), job_id_,
|
||||
s.ToString().c_str());
|
||||
return;
|
||||
"[%s] [JOB %d] Remote compaction failed.",
|
||||
compaction_input.column_family.name.c_str(), job_id_);
|
||||
return compaction_status;
|
||||
}
|
||||
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
compaction_result.status.PermitUncheckedError();
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
sub_compact->status = compaction_result.status;
|
||||
|
||||
@@ -1008,18 +1166,18 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
for (const auto& file : compaction_result.output_files) {
|
||||
uint64_t file_num = versions_->NewFileNumber();
|
||||
auto src_file = compaction_result.output_path + "/" + file.file_name;
|
||||
auto tgt_file = TableFileName(compaction->immutable_cf_options()->cf_paths,
|
||||
auto tgt_file = TableFileName(compaction->immutable_options()->cf_paths,
|
||||
file_num, compaction->output_path_id());
|
||||
s = fs_->RenameFile(src_file, tgt_file, IOOptions(), nullptr);
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
|
||||
FileMetaData meta;
|
||||
@@ -1027,7 +1185,7 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
s = fs_->GetFileSize(tgt_file, IOOptions(), &file_size, nullptr);
|
||||
if (!s.ok()) {
|
||||
sub_compact->status = s;
|
||||
return;
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
meta.fd = FileDescriptor(file_num, compaction->output_path_id(), file_size,
|
||||
file.smallest_seqno, file.largest_seqno);
|
||||
@@ -1046,8 +1204,10 @@ void CompactionJob::ProcessKeyValueCompactionWithCompactionService(
|
||||
sub_compact->num_output_records = compaction_result.num_output_records;
|
||||
sub_compact->approx_size = compaction_input.approx_size; // is this used?
|
||||
sub_compact->total_bytes = compaction_result.total_bytes;
|
||||
IOSTATS_ADD(bytes_written, compaction_result.bytes_written);
|
||||
IOSTATS_ADD(bytes_read, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_READ_BYTES, compaction_result.bytes_read);
|
||||
RecordTick(stats_, REMOTE_COMPACT_WRITE_BYTES,
|
||||
compaction_result.bytes_written);
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -1057,7 +1217,14 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
if (db_options_.compaction_service) {
|
||||
return ProcessKeyValueCompactionWithCompactionService(sub_compact);
|
||||
CompactionServiceJobStatus comp_status =
|
||||
ProcessKeyValueCompactionWithCompactionService(sub_compact);
|
||||
if (comp_status == CompactionServiceJobStatus::kSuccess ||
|
||||
comp_status == CompactionServiceJobStatus::kFailure) {
|
||||
return;
|
||||
}
|
||||
// fallback to local compaction
|
||||
assert(comp_status == CompactionServiceJobStatus::kUseLocal);
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
@@ -1084,6 +1251,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
CompactionRangeDelAggregator range_del_agg(&cfd->internal_comparator(),
|
||||
existing_snapshots_);
|
||||
|
||||
const Slice* const start = sub_compact->start;
|
||||
const Slice* const end = sub_compact->end;
|
||||
|
||||
ReadOptions read_options;
|
||||
read_options.verify_checksums = true;
|
||||
read_options.fill_cache = false;
|
||||
@@ -1093,11 +1264,51 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
// (b) CompactionFilter::Decision::kRemoveAndSkipUntil.
|
||||
read_options.total_order_seek = true;
|
||||
|
||||
// Note: if we're going to support subcompactions for user-defined timestamps,
|
||||
// the timestamp part will have to be stripped from the bounds here.
|
||||
assert((!start && !end) || cfd->user_comparator()->timestamp_size() == 0);
|
||||
read_options.iterate_lower_bound = start;
|
||||
read_options.iterate_upper_bound = end;
|
||||
|
||||
// Although the v2 aggregator is what the level iterator(s) know about,
|
||||
// the AddTombstones calls will be propagated down to the v1 aggregator.
|
||||
std::unique_ptr<InternalIterator> input(
|
||||
std::unique_ptr<InternalIterator> raw_input(
|
||||
versions_->MakeInputIterator(read_options, sub_compact->compaction,
|
||||
&range_del_agg, file_options_for_read_));
|
||||
InternalIterator* input = raw_input.get();
|
||||
|
||||
IterKey start_ikey;
|
||||
IterKey end_ikey;
|
||||
Slice start_slice;
|
||||
Slice end_slice;
|
||||
|
||||
if (start) {
|
||||
start_ikey.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
start_slice = start_ikey.GetInternalKey();
|
||||
}
|
||||
if (end) {
|
||||
end_ikey.SetInternalKey(*end, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
end_slice = end_ikey.GetInternalKey();
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> clip;
|
||||
if (start || end) {
|
||||
clip.reset(new ClippingIterator(
|
||||
raw_input.get(), start ? &start_slice : nullptr,
|
||||
end ? &end_slice : nullptr, &cfd->internal_comparator()));
|
||||
input = clip.get();
|
||||
}
|
||||
|
||||
std::unique_ptr<InternalIterator> blob_counter;
|
||||
|
||||
if (sub_compact->compaction->DoesInputReferenceBlobFiles()) {
|
||||
sub_compact->blob_garbage_meter.reset(new BlobGarbageMeter);
|
||||
blob_counter.reset(
|
||||
new BlobCountingIterator(input, sub_compact->blob_garbage_meter.get()));
|
||||
input = blob_counter.get();
|
||||
}
|
||||
|
||||
input->SeekToFirst();
|
||||
|
||||
AutoThreadOperationStageUpdater stage_updater(
|
||||
ThreadStatus::STAGE_COMPACTION_PROCESS_KV);
|
||||
@@ -1137,13 +1348,13 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
mutable_cf_options->enable_blob_files
|
||||
? new BlobFileBuilder(versions_, fs_.get(),
|
||||
sub_compact->compaction->immutable_cf_options(),
|
||||
mutable_cf_options, &file_options_, job_id_,
|
||||
cfd->GetID(), cfd->GetName(),
|
||||
Env::IOPriority::IO_LOW, write_hint_,
|
||||
io_tracer_, blob_callback_, &blob_file_paths,
|
||||
&sub_compact->blob_file_additions)
|
||||
? new BlobFileBuilder(
|
||||
versions_, fs_.get(),
|
||||
sub_compact->compaction->immutable_options(),
|
||||
mutable_cf_options, &file_options_, job_id_, cfd->GetID(),
|
||||
cfd->GetName(), Env::IOPriority::IO_LOW, write_hint_,
|
||||
io_tracer_, blob_callback_, BlobFileCreationReason::kCompaction,
|
||||
&blob_file_paths, &sub_compact->blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
TEST_SYNC_POINT("CompactionJob::Run():Inprogress");
|
||||
@@ -1152,31 +1363,22 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
reinterpret_cast<void*>(
|
||||
const_cast<std::atomic<int>*>(manual_compaction_paused_)));
|
||||
|
||||
Slice* start = sub_compact->start;
|
||||
Slice* end = sub_compact->end;
|
||||
if (start != nullptr) {
|
||||
IterKey start_iter;
|
||||
start_iter.SetInternalKey(*start, kMaxSequenceNumber, kValueTypeForSeek);
|
||||
input->Seek(start_iter.GetInternalKey());
|
||||
} else {
|
||||
input->SeekToFirst();
|
||||
}
|
||||
|
||||
Status status;
|
||||
const std::string* const full_history_ts_low =
|
||||
full_history_ts_low_.empty() ? nullptr : &full_history_ts_low_;
|
||||
sub_compact->c_iter.reset(new CompactionIterator(
|
||||
input.get(), cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
input, cfd->user_comparator(), &merge, versions_->LastSequence(),
|
||||
&existing_snapshots_, earliest_write_conflict_snapshot_,
|
||||
snapshot_checker_, env_, ShouldReportDetailedTime(env_, stats_),
|
||||
/*expect_valid_internal_key=*/true, &range_del_agg,
|
||||
blob_file_builder.get(), db_options_.allow_data_in_errors,
|
||||
sub_compact->compaction, compaction_filter, shutting_down_,
|
||||
preserve_deletes_seqnum_, manual_compaction_paused_, db_options_.info_log,
|
||||
full_history_ts_low));
|
||||
preserve_deletes_seqnum_, manual_compaction_paused_,
|
||||
manual_compaction_canceled_, db_options_.info_log, full_history_ts_low));
|
||||
auto c_iter = sub_compact->c_iter.get();
|
||||
c_iter->SeekToFirst();
|
||||
if (c_iter->Valid() && sub_compact->compaction->output_level() != 0) {
|
||||
sub_compact->FillFilesToCutForTtl();
|
||||
// ShouldStopBefore() maintains state based on keys processed so far. The
|
||||
// compaction loop always calls it on the "next" key, thus won't tell it the
|
||||
// first key. So we do that here.
|
||||
@@ -1197,12 +1399,9 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
const Slice& key = c_iter->key();
|
||||
const Slice& value = c_iter->value();
|
||||
|
||||
// If an end key (exclusive) is specified, check if the current key is
|
||||
// >= than it and exit if it is because the iterator is out of its range
|
||||
if (end != nullptr &&
|
||||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) >= 0) {
|
||||
break;
|
||||
}
|
||||
assert(!end ||
|
||||
cfd->user_comparator()->Compare(c_iter->user_key(), *end) < 0);
|
||||
|
||||
if (c_iter_stats.num_input_records % kRecordStatsEvery ==
|
||||
kRecordStatsEvery - 1) {
|
||||
RecordDroppedKeys(c_iter_stats, &sub_compact->compaction_job_stats);
|
||||
@@ -1222,6 +1421,11 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
break;
|
||||
}
|
||||
|
||||
status = sub_compact->ProcessOutFlowIfNeeded(key, value);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
|
||||
sub_compact->current_output_file_size =
|
||||
sub_compact->builder->EstimatedFileSize();
|
||||
const ParsedInternalKey& ikey = c_iter->ikey();
|
||||
@@ -1305,6 +1509,16 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
|
||||
RecordTick(stats_, FILTER_OPERATION_TOTAL_TIME,
|
||||
c_iter_stats.total_filter_time);
|
||||
|
||||
if (c_iter_stats.num_blobs_relocated > 0) {
|
||||
RecordTick(stats_, BLOB_DB_GC_NUM_KEYS_RELOCATED,
|
||||
c_iter_stats.num_blobs_relocated);
|
||||
}
|
||||
if (c_iter_stats.total_blob_bytes_relocated > 0) {
|
||||
RecordTick(stats_, BLOB_DB_GC_BYTES_RELOCATED,
|
||||
c_iter_stats.total_blob_bytes_relocated);
|
||||
}
|
||||
|
||||
RecordDroppedKeys(c_iter_stats, &sub_compact->compaction_job_stats);
|
||||
RecordCompactionIOStats();
|
||||
|
||||
@@ -1317,8 +1531,10 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
status = Status::ShutdownInProgress("Database shutdown");
|
||||
}
|
||||
if ((status.ok() || status.IsColumnFamilyDropped()) &&
|
||||
(manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0)) {
|
||||
((manual_compaction_paused_ &&
|
||||
manual_compaction_paused_->load(std::memory_order_relaxed) > 0) ||
|
||||
(manual_compaction_canceled_ &&
|
||||
manual_compaction_canceled_->load(std::memory_order_relaxed)))) {
|
||||
status = Status::Incomplete(Status::SubCode::kManualCompactionPaused);
|
||||
}
|
||||
if (status.ok()) {
|
||||
@@ -1350,7 +1566,7 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
if (status.ok()) {
|
||||
status = blob_file_builder->Finish();
|
||||
} else {
|
||||
blob_file_builder->Abandon();
|
||||
blob_file_builder->Abandon(status);
|
||||
}
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
@@ -1387,10 +1603,16 @@ void CompactionJob::ProcessKeyValueCompaction(SubcompactionState* sub_compact) {
|
||||
#endif // ROCKSDB_ASSERT_STATUS_CHECKED
|
||||
|
||||
sub_compact->c_iter.reset();
|
||||
input.reset();
|
||||
blob_counter.reset();
|
||||
clip.reset();
|
||||
raw_input.reset();
|
||||
sub_compact->status = status;
|
||||
}
|
||||
|
||||
uint64_t CompactionJob::GetCompactionId(SubcompactionState* sub_compact) {
|
||||
return (uint64_t)job_id_ << 32 | sub_compact->sub_job_id;
|
||||
}
|
||||
|
||||
void CompactionJob::RecordDroppedKeys(
|
||||
const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats) {
|
||||
@@ -1605,7 +1827,6 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
meta->UpdateBoundariesForRange(smallest_candidate, largest_candidate,
|
||||
tombstone.seq_,
|
||||
cfd->internal_comparator());
|
||||
|
||||
// The smallest key in a file is used for range tombstone truncation, so
|
||||
// it cannot have a seqnum of 0 (unless the smallest data key in a file
|
||||
// has a seqnum of 0). Otherwise, the truncated tombstone may expose
|
||||
@@ -1629,6 +1850,23 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
if (s.ok()) {
|
||||
meta->fd.file_size = current_bytes;
|
||||
meta->marked_for_compaction = sub_compact->builder->NeedCompact();
|
||||
// With accurate smallest and largest key, we can get a slightly more
|
||||
// accurate oldest ancester time.
|
||||
// This makes oldest ancester time in manifest more accurate than in
|
||||
// table properties. Not sure how to resolve it.
|
||||
if (meta->smallest.size() > 0 && meta->largest.size() > 0) {
|
||||
uint64_t refined_oldest_ancester_time;
|
||||
Slice new_smallest = meta->smallest.user_key();
|
||||
Slice new_largest = meta->largest.user_key();
|
||||
if (!new_largest.empty() && !new_smallest.empty()) {
|
||||
refined_oldest_ancester_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime(
|
||||
&(meta->smallest), &(meta->largest));
|
||||
if (refined_oldest_ancester_time != port::kMaxUint64) {
|
||||
meta->oldest_ancester_time = refined_oldest_ancester_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
sub_compact->current_output()->finished = true;
|
||||
sub_compact->total_bytes += current_bytes;
|
||||
@@ -1670,7 +1908,7 @@ Status CompactionJob::FinishCompactionOutputFile(
|
||||
// This happens when the output level is bottom level, at the same time
|
||||
// the sub_compact output nothing.
|
||||
std::string fname =
|
||||
TableFileName(sub_compact->compaction->immutable_cf_options()->cf_paths,
|
||||
TableFileName(sub_compact->compaction->immutable_options()->cf_paths,
|
||||
meta->fd.GetNumber(), meta->fd.GetPathId());
|
||||
|
||||
// TODO(AR) it is not clear if there are any larger implications if
|
||||
@@ -1755,19 +1993,6 @@ Status CompactionJob::InstallCompactionResults(
|
||||
auto* compaction = compact_->compaction;
|
||||
assert(compaction);
|
||||
|
||||
// paranoia: verify that the files that we started with
|
||||
// still exist in the current version and in the same original level.
|
||||
// This ensures that a concurrent compaction did not erroneously
|
||||
// pick the same files to compact_.
|
||||
if (!versions_->VerifyCompactionFileConsistency(compaction)) {
|
||||
Compaction::InputLevelSummaryBuffer inputs_summary;
|
||||
|
||||
ROCKS_LOG_ERROR(db_options_.info_log, "[%s] [JOB %d] Compaction %s aborted",
|
||||
compaction->column_family_data()->GetName().c_str(),
|
||||
job_id_, compaction->InputLevelSummary(&inputs_summary));
|
||||
return Status::Corruption("Compaction input files inconsistent");
|
||||
}
|
||||
|
||||
{
|
||||
Compaction::InputLevelSummaryBuffer inputs_summary;
|
||||
ROCKS_LOG_INFO(db_options_.info_log,
|
||||
@@ -1783,6 +2008,8 @@ Status CompactionJob::InstallCompactionResults(
|
||||
// Add compaction inputs
|
||||
compaction->AddInputDeletions(edit);
|
||||
|
||||
std::unordered_map<uint64_t, BlobGarbageMeter::BlobStats> blob_total_garbage;
|
||||
|
||||
for (const auto& sub_compact : compact_->sub_compact_states) {
|
||||
for (const auto& out : sub_compact.outputs) {
|
||||
edit->AddFile(compaction->output_level(), out.meta);
|
||||
@@ -1791,6 +2018,29 @@ Status CompactionJob::InstallCompactionResults(
|
||||
for (const auto& blob : sub_compact.blob_file_additions) {
|
||||
edit->AddBlobFile(blob);
|
||||
}
|
||||
|
||||
if (sub_compact.blob_garbage_meter) {
|
||||
const auto& flows = sub_compact.blob_garbage_meter->flows();
|
||||
|
||||
for (const auto& pair : flows) {
|
||||
const uint64_t blob_file_number = pair.first;
|
||||
const BlobGarbageMeter::BlobInOutFlow& flow = pair.second;
|
||||
|
||||
assert(flow.IsValid());
|
||||
if (flow.HasGarbage()) {
|
||||
blob_total_garbage[blob_file_number].Add(flow.GetGarbageCount(),
|
||||
flow.GetGarbageBytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& pair : blob_total_garbage) {
|
||||
const uint64_t blob_file_number = pair.first;
|
||||
const BlobGarbageMeter::BlobStats& stats = pair.second;
|
||||
|
||||
edit->AddBlobFileGarbage(blob_file_number, stats.GetCount(),
|
||||
stats.GetBytes());
|
||||
}
|
||||
|
||||
return versions_->LogAndApply(compaction->column_family_data(),
|
||||
@@ -1845,11 +2095,12 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
|
||||
// Pass temperature of botommost files to FileSystem.
|
||||
FileOptions fo_copy = file_options_;
|
||||
Temperature temperature = Temperature::kUnknown;
|
||||
if (bottommost_level_) {
|
||||
fo_copy.temperature = temperature =
|
||||
Temperature temperature = sub_compact->compaction->output_temperature();
|
||||
if (temperature == Temperature::kUnknown && bottommost_level_) {
|
||||
temperature =
|
||||
sub_compact->compaction->mutable_cf_options()->bottommost_temperature;
|
||||
}
|
||||
fo_copy.temperature = temperature;
|
||||
|
||||
Status s;
|
||||
IOStatus io_s = NewWritableFile(fs_.get(), fname, &writable_file, fo_copy);
|
||||
@@ -1886,8 +2137,17 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
get_time_status.ToString().c_str());
|
||||
}
|
||||
uint64_t current_time = static_cast<uint64_t>(temp_current_time);
|
||||
InternalKey tmp_start, tmp_end;
|
||||
if (sub_compact->start != nullptr) {
|
||||
tmp_start.SetMinPossibleForUserKey(*(sub_compact->start));
|
||||
}
|
||||
if (sub_compact->end != nullptr) {
|
||||
tmp_end.SetMinPossibleForUserKey(*(sub_compact->end));
|
||||
}
|
||||
uint64_t oldest_ancester_time =
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime();
|
||||
sub_compact->compaction->MinInputFileOldestAncesterTime(
|
||||
(sub_compact->start != nullptr) ? &tmp_start : nullptr,
|
||||
(sub_compact->end != nullptr) ? &tmp_end : nullptr);
|
||||
if (oldest_ancester_time == port::kMaxUint64) {
|
||||
oldest_ancester_time = current_time;
|
||||
}
|
||||
@@ -1914,12 +2174,12 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
writable_file->SetPreallocationBlockSize(static_cast<size_t>(
|
||||
sub_compact->compaction->OutputFilePreallocationSize()));
|
||||
const auto& listeners =
|
||||
sub_compact->compaction->immutable_cf_options()->listeners;
|
||||
sub_compact->compaction->immutable_options()->listeners;
|
||||
sub_compact->outfile.reset(new WritableFileWriter(
|
||||
std::move(writable_file), fname, file_options_, db_options_.clock,
|
||||
io_tracer_, db_options_.stats, listeners,
|
||||
db_options_.file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kTableFile)));
|
||||
tmp_set.Contains(FileType::kTableFile), false));
|
||||
|
||||
TableBuilderOptions tboptions(
|
||||
*cfd->ioptions(), *(sub_compact->compaction->mutable_cf_options()),
|
||||
@@ -1929,7 +2189,8 @@ Status CompactionJob::OpenCompactionOutputFile(
|
||||
cfd->GetName(), sub_compact->compaction->output_level(),
|
||||
bottommost_level_, TableFileCreationReason::kCompaction,
|
||||
oldest_ancester_time, 0 /* oldest_key_time */, current_time, db_id_,
|
||||
db_session_id_, sub_compact->compaction->max_output_file_size());
|
||||
db_session_id_, sub_compact->compaction->max_output_file_size(),
|
||||
file_number);
|
||||
sub_compact->builder.reset(
|
||||
NewTableBuilder(tboptions, sub_compact->outfile.get()));
|
||||
LogFlush(db_options_.info_log);
|
||||
@@ -2095,7 +2356,7 @@ void CompactionJob::LogCompaction() {
|
||||
}
|
||||
|
||||
std::string CompactionJob::GetTableFileName(uint64_t file_number) {
|
||||
return TableFileName(compact_->compaction->immutable_cf_options()->cf_paths,
|
||||
return TableFileName(compact_->compaction->immutable_options()->cf_paths,
|
||||
file_number, compact_->compaction->output_path_id());
|
||||
}
|
||||
|
||||
@@ -2105,6 +2366,12 @@ std::string CompactionServiceCompactionJob::GetTableFileName(
|
||||
return MakeTableFileName(output_path_, file_number);
|
||||
}
|
||||
|
||||
void CompactionServiceCompactionJob::RecordCompactionIOStats() {
|
||||
compaction_result_->bytes_read += IOSTATS(bytes_read);
|
||||
compaction_result_->bytes_written += IOSTATS(bytes_written);
|
||||
CompactionJob::RecordCompactionIOStats();
|
||||
}
|
||||
|
||||
CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options, const FileOptions& file_options,
|
||||
@@ -2126,7 +2393,7 @@ CompactionServiceCompactionJob::CompactionServiceCompactionJob(
|
||||
compaction->mutable_cf_options()->paranoid_file_checks,
|
||||
compaction->mutable_cf_options()->report_bg_io_stats, dbname,
|
||||
&(compaction_service_result->stats), Env::Priority::USER, io_tracer,
|
||||
nullptr, db_id, db_session_id,
|
||||
nullptr, nullptr, db_id, db_session_id,
|
||||
compaction->column_family_data()->GetFullHistoryTsLow()),
|
||||
output_path_(output_path),
|
||||
compaction_input_(compaction_service_input),
|
||||
@@ -2149,8 +2416,8 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
Slice end = compaction_input_.end;
|
||||
compact_->sub_compact_states.emplace_back(
|
||||
c, compaction_input_.has_begin ? &begin : nullptr,
|
||||
compaction_input_.has_end ? &end : nullptr,
|
||||
compaction_input_.approx_size);
|
||||
compaction_input_.has_end ? &end : nullptr, compaction_input_.approx_size,
|
||||
/*sub_job_id*/ 0);
|
||||
|
||||
log_buffer_->FlushBufferToLog();
|
||||
LogCompaction();
|
||||
@@ -2179,7 +2446,8 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
|
||||
if (output_directory_) {
|
||||
io_s = output_directory_->Fsync(IOOptions(), dbg);
|
||||
io_s = output_directory_->FsyncWithDirOptions(IOOptions(), dbg,
|
||||
DirFsyncOptions());
|
||||
}
|
||||
}
|
||||
if (io_status_.ok()) {
|
||||
@@ -2189,15 +2457,12 @@ Status CompactionServiceCompactionJob::Run() {
|
||||
status = io_s;
|
||||
}
|
||||
if (status.ok()) {
|
||||
// TODO: Add verify_table() and VerifyCompactionFileConsistency()
|
||||
// TODO: Add verify_table()
|
||||
}
|
||||
|
||||
// Finish up all book-keeping to unify the subcompaction results
|
||||
AggregateStatistics();
|
||||
UpdateCompactionStats();
|
||||
|
||||
compaction_result_->bytes_written = IOSTATS(bytes_written);
|
||||
compaction_result_->bytes_read = IOSTATS(bytes_read);
|
||||
RecordCompactionIOStats();
|
||||
|
||||
LogFlush(db_options_.info_log);
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
#include "db/flush_scheduler.h"
|
||||
#include "db/internal_stats.h"
|
||||
#include "db/job_context.h"
|
||||
@@ -81,6 +80,7 @@ class CompactionJob {
|
||||
const std::string& dbname, CompactionJobStats* compaction_job_stats,
|
||||
Env::Priority thread_pri, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::atomic<int>* manual_compaction_paused = nullptr,
|
||||
const std::atomic<bool>* manual_compaction_canceled = nullptr,
|
||||
const std::string& db_id = "", const std::string& db_session_id = "",
|
||||
std::string full_history_ts_low = "",
|
||||
BlobFileCompletionCallback* blob_callback = nullptr);
|
||||
@@ -116,7 +116,7 @@ class CompactionJob {
|
||||
void AggregateStatistics();
|
||||
void UpdateCompactionStats();
|
||||
void LogCompaction();
|
||||
void RecordCompactionIOStats();
|
||||
virtual void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
@@ -145,7 +145,7 @@ class CompactionJob {
|
||||
// consecutive groups such that each group has a similar size.
|
||||
void GenSubcompactionBoundaries();
|
||||
|
||||
void ProcessKeyValueCompactionWithCompactionService(
|
||||
CompactionServiceJobStatus ProcessKeyValueCompactionWithCompactionService(
|
||||
SubcompactionState* sub_compact);
|
||||
|
||||
// update the thread status for starting a compaction.
|
||||
@@ -167,7 +167,7 @@ class CompactionJob {
|
||||
void UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level);
|
||||
|
||||
int job_id_;
|
||||
uint32_t job_id_;
|
||||
|
||||
CompactionJobStats* compaction_job_stats_;
|
||||
|
||||
@@ -185,6 +185,7 @@ class CompactionJob {
|
||||
VersionSet* versions_;
|
||||
const std::atomic<bool>* shutting_down_;
|
||||
const std::atomic<int>* manual_compaction_paused_;
|
||||
const std::atomic<bool>* manual_compaction_canceled_;
|
||||
const SequenceNumber preserve_deletes_seqnum_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* blob_output_directory_;
|
||||
@@ -217,6 +218,8 @@ class CompactionJob {
|
||||
std::string full_history_ts_low_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
|
||||
uint64_t GetCompactionId(SubcompactionState* sub_compact);
|
||||
|
||||
// Get table file name in where it's outputting to, which should also be in
|
||||
// `output_directory_`.
|
||||
virtual std::string GetTableFileName(uint64_t file_number);
|
||||
@@ -300,10 +303,10 @@ struct CompactionServiceResult {
|
||||
std::string output_path;
|
||||
|
||||
// some statistics about the compaction
|
||||
uint64_t num_output_records;
|
||||
uint64_t total_bytes;
|
||||
uint64_t bytes_read;
|
||||
uint64_t bytes_written;
|
||||
uint64_t num_output_records = 0;
|
||||
uint64_t total_bytes = 0;
|
||||
uint64_t bytes_read = 0;
|
||||
uint64_t bytes_written = 0;
|
||||
CompactionJobStats stats;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
@@ -343,6 +346,9 @@ class CompactionServiceCompactionJob : private CompactionJob {
|
||||
|
||||
IOStatus io_status() const { return CompactionJob::io_status(); }
|
||||
|
||||
protected:
|
||||
void RecordCompactionIOStats() override;
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
@@ -269,10 +268,10 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
if (cf == 0) {
|
||||
// default cfd
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(level), &property));
|
||||
"rocksdb.num-files-at-level" + ToString(level), &property));
|
||||
} else {
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
handles_[cf], "rocksdb.num-files-at-level" + NumberToString(level),
|
||||
handles_[cf], "rocksdb.num-files-at-level" + ToString(level),
|
||||
&property));
|
||||
}
|
||||
return atoi(property.c_str());
|
||||
|
||||
@@ -82,10 +82,11 @@ class CompactionJobTestBase : public testing::Test {
|
||||
mutable_db_options_(),
|
||||
table_cache_(NewLRUCache(50000, 16)),
|
||||
write_buffer_manager_(db_options_.db_write_buffer_size),
|
||||
versions_(new VersionSet(
|
||||
dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr)),
|
||||
versions_(new VersionSet(dbname_, &db_options_, env_options_,
|
||||
table_cache_.get(), &write_buffer_manager_,
|
||||
&write_controller_,
|
||||
/*block_cache_tracer=*/nullptr,
|
||||
/*io_tracer=*/nullptr, /*db_session_id*/ "")),
|
||||
shutting_down_(false),
|
||||
preserve_deletes_seqnum_(0),
|
||||
mock_table_factory_(new mock::MockTableFactory()),
|
||||
@@ -269,7 +270,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
versions_.reset(
|
||||
new VersionSet(dbname_, &db_options_, env_options_, table_cache_.get(),
|
||||
&write_buffer_manager_, &write_controller_,
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr));
|
||||
/*block_cache_tracer=*/nullptr, /*io_tracer=*/nullptr,
|
||||
/*db_session_id*/ ""));
|
||||
compaction_job_stats_.Reset();
|
||||
ASSERT_OK(SetIdentityFile(env_, dbname_));
|
||||
|
||||
@@ -332,8 +334,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
cfd->current()->storage_info(), *cfd->ioptions(),
|
||||
*cfd->GetLatestMutableCFOptions(), mutable_db_options_,
|
||||
compaction_input_files, output_level, 1024 * 1024, 10 * 1024 * 1024, 0,
|
||||
kNoCompression, cfd->GetLatestMutableCFOptions()->compression_opts, 0,
|
||||
{}, true);
|
||||
kNoCompression, cfd->GetLatestMutableCFOptions()->compression_opts,
|
||||
Temperature::kUnknown, 0, {}, true);
|
||||
compaction.SetInputVersion(cfd->current());
|
||||
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, db_options_.info_log.get());
|
||||
@@ -350,7 +352,8 @@ class CompactionJobTestBase : public testing::Test {
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_,
|
||||
Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_paused=*/nullptr, /*db_id=*/"",
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, /*db_id=*/"",
|
||||
/*db_session_id=*/"", full_history_ts_low_);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
@@ -1117,7 +1120,9 @@ TEST_F(CompactionJobTest, InputSerialization) {
|
||||
input.snapshots.emplace_back(rnd64.Uniform(UINT64_MAX));
|
||||
}
|
||||
while (!rnd.OneIn(10)) {
|
||||
input.input_files.emplace_back(rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
input.input_files.emplace_back(rnd.RandomString(
|
||||
rnd.Uniform(kStrMaxLen - 1) +
|
||||
1)); // input file name should have at least one character
|
||||
}
|
||||
input.output_level = 4;
|
||||
input.has_begin = rnd.OneIn(2);
|
||||
@@ -1288,13 +1293,16 @@ TEST_F(CompactionJobTimestampTest, GCDisabled) {
|
||||
auto file1 =
|
||||
mock::MakeMockFile({{KeyStr("a", 10, ValueType::kTypeValue, 100), "a10"},
|
||||
{KeyStr("a", 9, ValueType::kTypeValue, 99), "a9"},
|
||||
{KeyStr("b", 8, ValueType::kTypeValue, 98), "b8"}});
|
||||
{KeyStr("b", 8, ValueType::kTypeValue, 98), "b8"},
|
||||
{KeyStr("d", 7, ValueType::kTypeValue, 97), "d7"}});
|
||||
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile(
|
||||
{{KeyStr("b", 7, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("c", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 95), "c5"}});
|
||||
{{KeyStr("b", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeDeletionWithTimestamp, 95), ""},
|
||||
{KeyStr("c", 4, ValueType::kTypeValue, 94), "c5"},
|
||||
{KeyStr("d", 3, ValueType::kTypeSingleDeletion, 93), ""}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(10);
|
||||
@@ -1303,9 +1311,11 @@ TEST_F(CompactionJobTimestampTest, GCDisabled) {
|
||||
{{KeyStr("a", 10, ValueType::kTypeValue, 100), "a10"},
|
||||
{KeyStr("a", 9, ValueType::kTypeValue, 99), "a9"},
|
||||
{KeyStr("b", 8, ValueType::kTypeValue, 98), "b8"},
|
||||
{KeyStr("b", 7, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("c", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 95), "c5"}});
|
||||
{KeyStr("b", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeDeletionWithTimestamp, 95), ""},
|
||||
{KeyStr("c", 4, ValueType::kTypeValue, 94), "c5"},
|
||||
{KeyStr("d", 7, ValueType::kTypeValue, 97), "d7"},
|
||||
{KeyStr("d", 3, ValueType::kTypeSingleDeletion, 93), ""}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
@@ -1343,19 +1353,21 @@ TEST_F(CompactionJobTimestampTest, AllKeysExpired) {
|
||||
|
||||
auto file1 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 5, ValueType::kTypeDeletionWithTimestamp, 100), ""},
|
||||
{KeyStr("b", 6, ValueType::kTypeValue, 99), "b6"}});
|
||||
{KeyStr("b", 6, ValueType::kTypeSingleDeletion, 99), ""},
|
||||
{KeyStr("c", 7, ValueType::kTypeValue, 98), "c7"}});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 4, ValueType::kTypeValue, 98), "a4"},
|
||||
{KeyStr("b", 3, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("b", 2, ValueType::kTypeValue, 96), "b2"}});
|
||||
{{KeyStr("a", 4, ValueType::kTypeValue, 97), "a4"},
|
||||
{KeyStr("b", 3, ValueType::kTypeValue, 96), "b3"},
|
||||
{KeyStr("c", 2, ValueType::kTypeDeletionWithTimestamp, 95), ""},
|
||||
{KeyStr("c", 1, ValueType::kTypeValue, 94), "c1"}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(6);
|
||||
SetLastSequence(7);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("b", 0, ValueType::kTypeValue, 0), "b6"}});
|
||||
mock::MakeMockFile({{KeyStr("c", 0, ValueType::kTypeValue, 0), "c7"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
full_history_ts_low_ = encode_u64_ts_(std::numeric_limits<uint64_t>::max());
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
@@ -358,7 +360,7 @@ Compaction* CompactionPicker::CompactFiles(
|
||||
output_level, compact_options.output_file_size_limit,
|
||||
mutable_cf_options.max_compaction_bytes, output_path_id, compression_type,
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_options.max_subcompactions,
|
||||
Temperature::kUnknown, compact_options.max_subcompactions,
|
||||
/* grandparents */ {}, true);
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
@@ -552,10 +554,14 @@ void CompactionPicker::GetGrandparents(
|
||||
InternalKey start, limit;
|
||||
GetRange(inputs, output_level_inputs, &start, &limit);
|
||||
// Compute the set of grandparent files that overlap this compaction
|
||||
// (parent == level+1; grandparent == level+2)
|
||||
if (output_level_inputs.level + 1 < NumberLevels()) {
|
||||
vstorage->GetOverlappingInputs(output_level_inputs.level + 1, &start,
|
||||
&limit, grandparents);
|
||||
// (parent == level+1; grandparent == level+2 or the first
|
||||
// level after that has overlapping files)
|
||||
for (int level = output_level_inputs.level + 1; level < NumberLevels();
|
||||
level++) {
|
||||
vstorage->GetOverlappingInputs(level, &start, &limit, grandparents);
|
||||
if (!grandparents->empty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,7 +640,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_range_options.max_subcompactions, /* grandparents */ {},
|
||||
Temperature::kUnknown, compact_range_options.max_subcompactions,
|
||||
/* grandparents */ {},
|
||||
/* is manual */ true);
|
||||
RegisterCompaction(c);
|
||||
vstorage->ComputeCompactionScore(ioptions_, mutable_cf_options);
|
||||
@@ -812,7 +819,8 @@ Compaction* CompactionPicker::CompactRange(
|
||||
GetCompressionType(ioptions_, vstorage, mutable_cf_options, output_level,
|
||||
vstorage->base_level()),
|
||||
GetCompressionOptions(mutable_cf_options, vstorage, output_level),
|
||||
compact_range_options.max_subcompactions, std::move(grandparents),
|
||||
Temperature::kUnknown, compact_range_options.max_subcompactions,
|
||||
std::move(grandparents),
|
||||
/* is manual compaction */ true);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::CompactRange:Return", compaction);
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
#include <cinttypes>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -111,7 +113,7 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
|
||||
std::move(inputs), 0, 0, 0, 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOTtl);
|
||||
@@ -154,7 +156,8 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
{comp_inputs}, 0, 16 * 1024 * 1024 /* output file size limit */,
|
||||
0 /* max compaction bytes, not applicable */,
|
||||
0 /* output path ID */, mutable_cf_options.compression,
|
||||
mutable_cf_options.compression_opts, 0 /* max_subcompactions */, {},
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
0 /* max_subcompactions */, {},
|
||||
/* is manual */ false, vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false,
|
||||
CompactionReason::kFIFOReduceNumFiles);
|
||||
@@ -203,13 +206,119 @@ Compaction* FIFOCompactionPicker::PickSizeCompaction(
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
|
||||
std::move(inputs), 0, 0, 0, 0, kNoCompression,
|
||||
mutable_cf_options.compression_opts,
|
||||
mutable_cf_options.compression_opts, Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ true, CompactionReason::kFIFOMaxSize);
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* FIFOCompactionPicker::PickCompactionToWarm(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
LogBuffer* log_buffer) {
|
||||
if (mutable_cf_options.compaction_options_fifo.age_for_warm == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int kLevel0 = 0;
|
||||
const std::vector<FileMetaData*>& level_files = vstorage->LevelFiles(kLevel0);
|
||||
|
||||
int64_t _current_time;
|
||||
auto status = ioptions_.clock->GetCurrentTime(&_current_time);
|
||||
if (!status.ok()) {
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: Couldn't get current time: %s. "
|
||||
"Not doing compactions based on warm threshold. ",
|
||||
cf_name.c_str(), status.ToString().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
const uint64_t current_time = static_cast<uint64_t>(_current_time);
|
||||
|
||||
if (!level0_compactions_in_progress_.empty()) {
|
||||
ROCKS_LOG_BUFFER(
|
||||
log_buffer,
|
||||
"[%s] FIFO compaction: Already executing compaction. Parallel "
|
||||
"compactions are not supported",
|
||||
cf_name.c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs;
|
||||
inputs.emplace_back();
|
||||
inputs[0].level = 0;
|
||||
|
||||
// avoid underflow
|
||||
if (current_time > mutable_cf_options.compaction_options_fifo.age_for_warm) {
|
||||
uint64_t create_time_threshold =
|
||||
current_time - mutable_cf_options.compaction_options_fifo.age_for_warm;
|
||||
uint64_t compaction_size = 0;
|
||||
// We will ideally identify a file qualifying for warm tier by knowing
|
||||
// the timestamp for the youngest entry in the file. However, right now
|
||||
// we don't have the information. We infer it by looking at timestamp
|
||||
// of the next file's (which is just younger) oldest entry's timestamp.
|
||||
FileMetaData* prev_file = nullptr;
|
||||
for (auto ritr = level_files.rbegin(); ritr != level_files.rend(); ++ritr) {
|
||||
FileMetaData* f = *ritr;
|
||||
assert(f);
|
||||
if (f->being_compacted) {
|
||||
// Right now this probably won't happen as we never try to schedule
|
||||
// two compactions in parallel, so here we just simply don't schedule
|
||||
// anything.
|
||||
return nullptr;
|
||||
}
|
||||
uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time == kUnknownOldestAncesterTime) {
|
||||
// Older files might not have enough information. It is possible to
|
||||
// handle these files by looking at newer files, but maintaining the
|
||||
// logic isn't worth it.
|
||||
break;
|
||||
}
|
||||
if (oldest_ancester_time > create_time_threshold) {
|
||||
// The previous file (which has slightly older data) doesn't qualify
|
||||
// for warm tier.
|
||||
break;
|
||||
}
|
||||
if (prev_file != nullptr) {
|
||||
compaction_size += prev_file->fd.GetFileSize();
|
||||
if (compaction_size > mutable_cf_options.max_compaction_bytes) {
|
||||
break;
|
||||
}
|
||||
inputs[0].files.push_back(prev_file);
|
||||
ROCKS_LOG_BUFFER(log_buffer,
|
||||
"[%s] FIFO compaction: picking file %" PRIu64
|
||||
" with next file's oldest time %" PRIu64 " for warm",
|
||||
cf_name.c_str(), prev_file->fd.GetNumber(),
|
||||
oldest_ancester_time);
|
||||
}
|
||||
if (f->temperature == Temperature::kUnknown ||
|
||||
f->temperature == Temperature::kHot) {
|
||||
prev_file = f;
|
||||
} else if (!inputs[0].files.empty()) {
|
||||
// A warm file newer than files picked.
|
||||
break;
|
||||
} else {
|
||||
assert(prev_file == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (inputs[0].files.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Compaction* c = new Compaction(
|
||||
vstorage, ioptions_, mutable_cf_options, mutable_db_options,
|
||||
std::move(inputs), 0, 0 /* output file size limit */,
|
||||
0 /* max compaction bytes, not applicable */, 0 /* output path ID */,
|
||||
mutable_cf_options.compression, mutable_cf_options.compression_opts,
|
||||
Temperature::kWarm,
|
||||
/* max_subcompactions */ 0, {}, /* is manual */ false,
|
||||
vstorage->CompactionScore(0),
|
||||
/* is deletion compaction */ false, CompactionReason::kChangeTemperature);
|
||||
return c;
|
||||
}
|
||||
|
||||
Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
@@ -225,6 +334,10 @@ Compaction* FIFOCompactionPicker::PickCompaction(
|
||||
c = PickSizeCompaction(cf_name, mutable_cf_options, mutable_db_options,
|
||||
vstorage, log_buffer);
|
||||
}
|
||||
if (c == nullptr) {
|
||||
c = PickCompactionToWarm(cf_name, mutable_cf_options, mutable_db_options,
|
||||
vstorage, log_buffer);
|
||||
}
|
||||
RegisterCompaction(c);
|
||||
return c;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,12 @@ class FIFOCompactionPicker : public CompactionPicker {
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* version,
|
||||
LogBuffer* log_buffer);
|
||||
|
||||
Compaction* PickCompactionToWarm(const std::string& cf_name,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
VersionStorageInfo* version,
|
||||
LogBuffer* log_buffer);
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -31,6 +31,9 @@ bool LevelCompactionPicker::NeedsCompaction(
|
||||
if (!vstorage->FilesMarkedForCompaction().empty()) {
|
||||
return true;
|
||||
}
|
||||
if (!vstorage->FilesMarkedForForcedBlobGC().empty()) {
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i <= vstorage->MaxInputLevel(); i++) {
|
||||
if (vstorage->CompactionScore(i) >= 1) {
|
||||
return true;
|
||||
@@ -248,6 +251,13 @@ void LevelCompactionBuilder::SetupInitialFiles() {
|
||||
compaction_reason_ = CompactionReason::kPeriodicCompaction;
|
||||
return;
|
||||
}
|
||||
|
||||
// Forced blob garbage collection
|
||||
PickFileToCompact(vstorage_->FilesMarkedForForcedBlobGC(), false);
|
||||
if (!start_level_inputs_.empty()) {
|
||||
compaction_reason_ = CompactionReason::kForcedBlobGC;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool LevelCompactionBuilder::SetupOtherL0FilesIfNeeded() {
|
||||
@@ -336,6 +346,7 @@ Compaction* LevelCompactionBuilder::GetCompaction() {
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level_, vstorage_->base_level()),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level_),
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, std::move(grandparents_), is_manual_,
|
||||
start_level_score_, false /* deletion_compaction */, compaction_reason_);
|
||||
|
||||
@@ -384,7 +395,7 @@ uint32_t LevelCompactionBuilder::GetPathId(
|
||||
if (ioptions.level_compaction_dynamic_level_bytes) {
|
||||
// Currently, level_compaction_dynamic_level_bytes is ignored when
|
||||
// multiple db paths are specified. https://github.com/facebook/
|
||||
// rocksdb/blob/master/db/column_family.cc.
|
||||
// rocksdb/blob/main/db/column_family.cc.
|
||||
// Still, adding this check to avoid accidentally using
|
||||
// max_bytes_for_level_multiplier_additional
|
||||
level_size = static_cast<uint64_t>(
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "db/compaction/compaction.h"
|
||||
#include "db/compaction/compaction_picker_fifo.h"
|
||||
#include "db/compaction/compaction_picker_level.h"
|
||||
#include "db/compaction/compaction_picker_universal.h"
|
||||
|
||||
#include "db/compaction/file_pri.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -98,7 +98,9 @@ class CompactionPickerTest : public testing::Test {
|
||||
void Add(int level, uint32_t file_number, const char* smallest,
|
||||
const char* largest, uint64_t file_size = 1, uint32_t path_id = 0,
|
||||
SequenceNumber smallest_seq = 100, SequenceNumber largest_seq = 100,
|
||||
size_t compensated_file_size = 0, bool marked_for_compact = false) {
|
||||
size_t compensated_file_size = 0, bool marked_for_compact = false,
|
||||
Temperature temperature = Temperature::kUnknown,
|
||||
uint64_t oldest_ancestor_time = kUnknownOldestAncesterTime) {
|
||||
VersionStorageInfo* vstorage;
|
||||
if (temp_vstorage_) {
|
||||
vstorage = temp_vstorage_.get();
|
||||
@@ -115,6 +117,8 @@ class CompactionPickerTest : public testing::Test {
|
||||
kUnknownFileChecksum, kUnknownFileChecksumFuncName);
|
||||
f->compensated_file_size =
|
||||
(compensated_file_size != 0) ? compensated_file_size : file_size;
|
||||
f->temperature = temperature;
|
||||
f->oldest_ancester_time = oldest_ancestor_time;
|
||||
vstorage->AddFile(level, f);
|
||||
files_.emplace_back(f);
|
||||
file_map_.insert({file_number, {f, level}});
|
||||
@@ -145,7 +149,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
vstorage_ = std::move(temp_vstorage_);
|
||||
}
|
||||
vstorage_->CalculateBaseBytes(ioptions_, mutable_cf_options_);
|
||||
vstorage_->UpdateFilesByCompactionPri(ioptions_.compaction_pri);
|
||||
vstorage_->UpdateFilesByCompactionPri(ioptions_, mutable_cf_options_);
|
||||
vstorage_->UpdateNumNonEmptyLevels();
|
||||
vstorage_->GenerateFileIndexer();
|
||||
vstorage_->GenerateLevelFilesBrief();
|
||||
@@ -729,6 +733,221 @@ TEST_F(CompactionPickerTest, UniversalPeriodicCompaction6) {
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace1) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 555555;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
Add(3, 5U, "310", "380", kFileSize, 0, 200, 251);
|
||||
Add(3, 6U, "410", "880", kFileSize, 0, 200, 251);
|
||||
Add(3, 7U, "910", "980", 1, 0, 200, 251);
|
||||
Add(4, 10U, "201", "250", kFileSize, 0, 101, 150);
|
||||
Add(4, 11U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(4, 12U, "401", "450", kFileSize, 0, 101, 150);
|
||||
Add(4, 13U, "501", "750", kFileSize, 0, 101, 150);
|
||||
Add(4, 14U, "801", "850", kFileSize, 0, 101, 150);
|
||||
Add(4, 15U, "901", "950", kFileSize, 0, 101, 150);
|
||||
// Add(4, 15U, "960", "970", kFileSize, 0, 101, 150);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(3, compaction->start_level());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(5U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(6U, compaction->input(0, 1)->fd.GetNumber());
|
||||
// ASSERT_EQ(4U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(11U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(12U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(13U, compaction->input(1, 2)->fd.GetNumber());
|
||||
ASSERT_EQ(14U, compaction->input(1, 3)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace2) {
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 400000;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(1, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
Add(2, 5U, "310", "380", kFileSize, 0, 200, 251);
|
||||
Add(2, 6U, "410", "880", kFileSize, 0, 200, 251);
|
||||
Add(2, 7U, "910", "980", kFileSize, 0, 200, 251);
|
||||
Add(4, 10U, "201", "250", kFileSize, 0, 101, 150);
|
||||
Add(4, 11U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(4, 12U, "401", "450", kFileSize, 0, 101, 150);
|
||||
Add(4, 13U, "501", "750", kFileSize, 0, 101, 150);
|
||||
Add(4, 14U, "801", "850", kFileSize, 0, 101, 150);
|
||||
Add(4, 15U, "901", "950", kFileSize, 0, 101, 150);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(2, compaction->start_level());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(7U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(15U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace3) {
|
||||
// Test bottom level files falling between gaps between two upper level
|
||||
// files
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 300000;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
Add(3, 5U, "000", "180", kFileSize, 0, 200, 251);
|
||||
Add(3, 6U, "181", "190", kFileSize, 0, 200, 251);
|
||||
Add(3, 7U, "710", "810", kFileSize, 0, 200, 251);
|
||||
Add(3, 8U, "820", "830", kFileSize, 0, 200, 251);
|
||||
Add(3, 9U, "900", "991", kFileSize, 0, 200, 251);
|
||||
Add(4, 10U, "201", "250", kFileSize, 0, 101, 150);
|
||||
Add(4, 11U, "301", "350", kFileSize, 0, 101, 150);
|
||||
Add(4, 12U, "401", "450", kFileSize, 0, 101, 150);
|
||||
Add(4, 13U, "501", "750", kFileSize, 0, 101, 150);
|
||||
Add(4, 14U, "801", "850", kFileSize, 0, 101, 150);
|
||||
Add(4, 15U, "901", "950", kFileSize, 0, 101, 150);
|
||||
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(2, compaction->start_level());
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->num_input_files(1));
|
||||
ASSERT_EQ(5U, compaction->input(1, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(6U, compaction->input(1, 1)->fd.GetNumber());
|
||||
ASSERT_EQ(0, compaction->num_input_files(2));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace4) {
|
||||
// Test compaction candidates always cover many files.
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 3200000;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
|
||||
// Generate files like following:
|
||||
// L3: (1101, 1180) (1201, 1280) ... (7901, 7908)
|
||||
// L4: (1130, 1150) (1160, 1210) (1230, 1250) (1260 1310) ... (7960, 8010)
|
||||
for (int i = 11; i < 79; i++) {
|
||||
Add(3, 100 + i * 3, ToString(i * 100).c_str(),
|
||||
ToString(i * 100 + 80).c_str(), kFileSize, 0, 200, 251);
|
||||
// Add a tie breaker
|
||||
if (i == 66) {
|
||||
Add(3, 10000U, "6690", "6699", kFileSize, 0, 200, 251);
|
||||
}
|
||||
|
||||
Add(4, 100 + i * 3 + 1, ToString(i * 100 + 30).c_str(),
|
||||
ToString(i * 100 + 50).c_str(), kFileSize, 0, 200, 251);
|
||||
Add(4, 100 + i * 3 + 2, ToString(i * 100 + 60).c_str(),
|
||||
ToString(i * 100 + 110).c_str(), kFileSize, 0, 200, 251);
|
||||
}
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(3, compaction->start_level());
|
||||
ASSERT_EQ(6U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(100 + 62U * 3, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(10000U, compaction->input(0, 5)->fd.GetNumber());
|
||||
ASSERT_EQ(11, compaction->num_input_files(1));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalIncrementalSpace5) {
|
||||
// Test compaction candidates always cover many files with some single
|
||||
// files larger than size threshold.
|
||||
const uint64_t kFileSize = 100000;
|
||||
|
||||
mutable_cf_options_.max_compaction_bytes = 3200000;
|
||||
mutable_cf_options_.compaction_options_universal.incremental = true;
|
||||
mutable_cf_options_.compaction_options_universal
|
||||
.max_size_amplification_percent = 30;
|
||||
UniversalCompactionPicker universal_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
NewVersionStorage(5, kCompactionStyleUniversal);
|
||||
|
||||
Add(0, 1U, "150", "200", kFileSize, 0, 500, 550);
|
||||
Add(2, 2U, "010", "080", kFileSize, 0, 200, 251);
|
||||
|
||||
// Generate files like following:
|
||||
// L3: (1101, 1180) (1201, 1280) ... (7901, 7908)
|
||||
// L4: (1130, 1150) (1160, 1210) (1230, 1250) (1260 1310) ... (7960, 8010)
|
||||
for (int i = 11; i < 70; i++) {
|
||||
Add(3, 100 + i * 3, ToString(i * 100).c_str(),
|
||||
ToString(i * 100 + 80).c_str(),
|
||||
i % 10 == 9 ? kFileSize * 100 : kFileSize, 0, 200, 251);
|
||||
|
||||
Add(4, 100 + i * 3 + 1, ToString(i * 100 + 30).c_str(),
|
||||
ToString(i * 100 + 50).c_str(), kFileSize, 0, 200, 251);
|
||||
Add(4, 100 + i * 3 + 2, ToString(i * 100 + 60).c_str(),
|
||||
ToString(i * 100 + 110).c_str(), kFileSize, 0, 200, 251);
|
||||
}
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
std::unique_ptr<Compaction> compaction(
|
||||
universal_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction);
|
||||
ASSERT_EQ(4, compaction->output_level());
|
||||
ASSERT_EQ(3, compaction->start_level());
|
||||
ASSERT_EQ(6U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(100 + 14 * 3, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(100 + 19 * 3, compaction->input(0, 5)->fd.GetNumber());
|
||||
ASSERT_EQ(13, compaction->num_input_files(1));
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const int kFileCount =
|
||||
@@ -757,6 +976,245 @@ TEST_F(CompactionPickerTest, NeedsCompactionFIFO) {
|
||||
vstorage_->CompactionScore(0) >= 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarm1) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(3U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarm2) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmMaxSize) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 9;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 5000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(1U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(2U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithExistingWarm) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kWarm, threshold_time - 5000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(2U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
ASSERT_EQ(3U, compaction->input(0, 1)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithOngoing) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kWarm, threshold_time - 5000);
|
||||
file_map_[2].first->being_compacted = true;
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
// Stop if a file is being compacted
|
||||
ASSERT_TRUE(compaction.get() == nullptr);
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FIFOToWarmWithHotBetweenWarms) {
|
||||
NewVersionStorage(1, kCompactionStyleFIFO);
|
||||
const uint64_t kFileSize = 100000;
|
||||
const uint64_t kMaxSize = kFileSize * 100000;
|
||||
uint64_t kWarmThreshold = 2000;
|
||||
|
||||
fifo_options_.max_table_files_size = kMaxSize;
|
||||
fifo_options_.age_for_warm = kWarmThreshold;
|
||||
mutable_cf_options_.compaction_options_fifo = fifo_options_;
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
mutable_cf_options_.max_compaction_bytes = kFileSize * 100;
|
||||
FIFOCompactionPicker fifo_compaction_picker(ioptions_, &icmp_);
|
||||
|
||||
int64_t current_time = 0;
|
||||
ASSERT_OK(Env::Default()->GetCurrentTime(¤t_time));
|
||||
uint64_t threshold_time =
|
||||
static_cast<uint64_t>(current_time) - kWarmThreshold;
|
||||
Add(0, 6U, "240", "290", 2 * kFileSize, 0, 2900, 3000, 0, true,
|
||||
Temperature::kUnknown, static_cast<uint64_t>(current_time) - 100);
|
||||
Add(0, 5U, "240", "290", 2 * kFileSize, 0, 2700, 2800, 0, true,
|
||||
Temperature::kUnknown, threshold_time + 100);
|
||||
Add(0, 4U, "260", "300", 1 * kFileSize, 0, 2500, 2600, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 2000);
|
||||
Add(0, 3U, "200", "300", 4 * kFileSize, 0, 2300, 2400, 0, true,
|
||||
Temperature::kWarm, threshold_time - 3000);
|
||||
Add(0, 2U, "200", "300", 4 * kFileSize, 0, 2100, 2200, 0, true,
|
||||
Temperature::kUnknown, threshold_time - 4000);
|
||||
Add(0, 1U, "200", "300", 4 * kFileSize, 0, 2000, 2100, 0, true,
|
||||
Temperature::kWarm, threshold_time - 5000);
|
||||
UpdateVersionStorageInfo();
|
||||
|
||||
ASSERT_EQ(fifo_compaction_picker.NeedsCompaction(vstorage_.get()), true);
|
||||
std::unique_ptr<Compaction> compaction(fifo_compaction_picker.PickCompaction(
|
||||
cf_name_, mutable_cf_options_, mutable_db_options_, vstorage_.get(),
|
||||
&log_buffer_));
|
||||
// Stop if a file is being compacted
|
||||
ASSERT_TRUE(compaction.get() != nullptr);
|
||||
ASSERT_EQ(1U, compaction->num_input_files(0));
|
||||
ASSERT_EQ(2U, compaction->input(0, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(CompactionPickerTest, CompactionPriMinOverlapping1) {
|
||||
@@ -1215,6 +1673,66 @@ TEST_F(CompactionPickerTest, OverlappingUserKeys11) {
|
||||
ASSERT_EQ(7U, compaction->input(1, 0)->fd.GetNumber());
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, FileTtlBooster) {
|
||||
// Set TTL to 2048
|
||||
// TTL boosting for all levels starts at 1024,
|
||||
// Whole TTL range is 2048 * 31 / 32 - 1024 = 1984 - 1024 = 960.
|
||||
// From second last level (L5), range starts at
|
||||
// 1024 + 480, 1024 + 240, 1024 + 120 (which is L3).
|
||||
// Boosting step 124 / 16 = 7.75 -> 7
|
||||
//
|
||||
const uint64_t kCurrentTime = 1000000;
|
||||
FileMetaData meta;
|
||||
|
||||
{
|
||||
FileTtlBooster booster(kCurrentTime, 2048, 7, 3);
|
||||
|
||||
// Not triggering if the file is younger than ttl/2
|
||||
meta.oldest_ancester_time = kCurrentTime - 1023;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - 1024;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime + 10;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
|
||||
// Within one boosting step
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 6);
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
|
||||
// One boosting step
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 7);
|
||||
ASSERT_EQ(2, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 8);
|
||||
ASSERT_EQ(2, booster.GetBoostScore(&meta));
|
||||
|
||||
// Multiple boosting steps
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 30);
|
||||
ASSERT_EQ(5, booster.GetBoostScore(&meta));
|
||||
|
||||
// Very high boosting steps
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 120 + 700);
|
||||
ASSERT_EQ(101, booster.GetBoostScore(&meta));
|
||||
}
|
||||
{
|
||||
// Test second last level
|
||||
FileTtlBooster booster(kCurrentTime, 2048, 7, 5);
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 480);
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 480 + 60);
|
||||
ASSERT_EQ(3, booster.GetBoostScore(&meta));
|
||||
}
|
||||
{
|
||||
// Test last level
|
||||
FileTtlBooster booster(kCurrentTime, 2048, 7, 6);
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 480);
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - (1024 + 480 + 60);
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
meta.oldest_ancester_time = kCurrentTime - 3000;
|
||||
ASSERT_EQ(1, booster.GetBoostScore(&meta));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri1) {
|
||||
NewVersionStorage(6, kCompactionStyleLevel);
|
||||
mutable_cf_options_.level0_file_num_compaction_trigger = 2;
|
||||
|
||||
@@ -15,9 +15,11 @@
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "db/column_family.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/log_buffer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/random.h"
|
||||
@@ -87,6 +89,14 @@ class UniversalCompactionBuilder {
|
||||
// Pick Universal compaction to limit space amplification.
|
||||
Compaction* PickCompactionToReduceSizeAmp();
|
||||
|
||||
// Try to pick incremental compaction to reduce space amplification.
|
||||
// It will return null if it cannot find a fanout within the threshold.
|
||||
// Fanout is defined as
|
||||
// total size of files to compact at output level
|
||||
// --------------------------------------------------
|
||||
// total size of files to compact at other levels
|
||||
Compaction* PickIncrementalForReduceSizeAmp(double fanout_threshold);
|
||||
|
||||
Compaction* PickDeleteTriggeredCompaction();
|
||||
|
||||
// Form a compaction from the sorted run indicated by start_index to the
|
||||
@@ -102,12 +112,14 @@ class UniversalCompactionBuilder {
|
||||
// because some files are being compacted.
|
||||
Compaction* PickPeriodicCompaction();
|
||||
|
||||
// Used in universal compaction when the enabled_trivial_move
|
||||
// Used in universal compaction when the allow_trivial_move
|
||||
// option is set. Checks whether there are any overlapping files
|
||||
// in the input. Returns true if the input files are non
|
||||
// overlapping.
|
||||
bool IsInputFilesNonOverlapping(Compaction* c);
|
||||
|
||||
uint64_t GetMaxOverlappingBytes() const;
|
||||
|
||||
const ImmutableOptions& ioptions_;
|
||||
const InternalKeyComparator* icmp_;
|
||||
double score_;
|
||||
@@ -157,9 +169,9 @@ struct SmallestKeyHeapComparator {
|
||||
const Comparator* ucmp_;
|
||||
};
|
||||
|
||||
typedef std::priority_queue<InputFileInfo, std::vector<InputFileInfo>,
|
||||
SmallestKeyHeapComparator>
|
||||
SmallestKeyHeap;
|
||||
using SmallestKeyHeap =
|
||||
std::priority_queue<InputFileInfo, std::vector<InputFileInfo>,
|
||||
SmallestKeyHeapComparator>;
|
||||
|
||||
// This function creates the heap that is used to find if the files are
|
||||
// overlapping during universal compaction when the allow_trivial_move
|
||||
@@ -712,6 +724,19 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
cf_name_.c_str(), file_num_buf);
|
||||
}
|
||||
|
||||
std::vector<FileMetaData*> grandparents;
|
||||
// Include grandparents for potential file cutting in incremental
|
||||
// mode. It is for aligning file cutting boundaries across levels,
|
||||
// so that subsequent compactions can pick files with aligned
|
||||
// buffer.
|
||||
// Single files are only picked up in incremental mode, so that
|
||||
// there is no need for full range.
|
||||
if (mutable_cf_options_.compaction_options_universal.incremental &&
|
||||
first_index_after < sorted_runs_.size() &&
|
||||
sorted_runs_[first_index_after].level > 1) {
|
||||
grandparents = vstorage_->LevelFiles(sorted_runs_[first_index_after].level);
|
||||
}
|
||||
|
||||
CompactionReason compaction_reason;
|
||||
if (max_number_of_files_to_compact == UINT_MAX) {
|
||||
compaction_reason = CompactionReason::kUniversalSizeRatio;
|
||||
@@ -723,13 +748,14 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
LLONG_MAX, path_id,
|
||||
GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_, start_level,
|
||||
1, enable_compression),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, start_level,
|
||||
enable_compression),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */, compaction_reason);
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, grandparents, /* is manual */ false, score_,
|
||||
false /* deletion_compaction */, compaction_reason);
|
||||
}
|
||||
|
||||
// Look at overall size amplification. If size amplification
|
||||
@@ -785,6 +811,8 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
for (size_t loop = start_index; loop + 1 < sorted_runs_.size(); loop++) {
|
||||
sr = &sorted_runs_[loop];
|
||||
if (sr->being_compacted) {
|
||||
// TODO with incremental compaction is supported, we might want to
|
||||
// schedule some incremental compactions in parallel if needed.
|
||||
char file_num_buf[kFormatFileNumberBufSize];
|
||||
sr->Dump(file_num_buf, sizeof(file_num_buf), true);
|
||||
ROCKS_LOG_BUFFER(
|
||||
@@ -818,16 +846,250 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSizeAmp() {
|
||||
" earliest-file-size %" PRIu64,
|
||||
cf_name_.c_str(), candidate_size, earliest_file_size);
|
||||
}
|
||||
// Since incremental compaction can't include more than second last
|
||||
// level, it can introduce penalty, compared to full compaction. We
|
||||
// hard code the pentalty to be 80%. If we end up with a compaction
|
||||
// fanout higher than 80% of full level compactions, we fall back
|
||||
// to full level compaction.
|
||||
// The 80% threshold is arbitrary and can be adjusted or made
|
||||
// configurable in the future.
|
||||
// This also prevent the case when compaction falls behind and we
|
||||
// need to compact more levels for compactions to catch up.
|
||||
if (mutable_cf_options_.compaction_options_universal.incremental) {
|
||||
double fanout_threshold = static_cast<double>(earliest_file_size) /
|
||||
static_cast<double>(candidate_size) * 1.8;
|
||||
Compaction* picked = PickIncrementalForReduceSizeAmp(fanout_threshold);
|
||||
if (picked != nullptr) {
|
||||
// As the feature is still incremental, picking incremental compaction
|
||||
// might fail and we will fall bck to compacting full level.
|
||||
return picked;
|
||||
}
|
||||
}
|
||||
return PickCompactionToOldest(start_index,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
}
|
||||
|
||||
Compaction* UniversalCompactionBuilder::PickIncrementalForReduceSizeAmp(
|
||||
double fanout_threshold) {
|
||||
// Try find all potential compactions with total size just over
|
||||
// options.max_compaction_size / 2, and take the one with the lowest
|
||||
// fanout (defined in declaration of the function).
|
||||
// This is done by having a sliding window of the files at the second
|
||||
// lowest level, and keep expanding while finding overlapping in the
|
||||
// last level. Once total size exceeds the size threshold, calculate
|
||||
// the fanout value. And then shrinking from the small side of the
|
||||
// window. Keep doing it until the end.
|
||||
// Finally, we try to include upper level files if they fall into
|
||||
// the range.
|
||||
//
|
||||
// Note that it is a similar problem as leveled compaction's
|
||||
// kMinOverlappingRatio priority, but instead of picking single files
|
||||
// we expand to a target compaction size. The reason is that in
|
||||
// leveled compaction, actual fanout value tends to high, e.g. 10, so
|
||||
// even with single file in down merging level, the extra size
|
||||
// compacted in boundary files is at a lower ratio. But here users
|
||||
// often have size of second last level size to be 1/4, 1/3 or even
|
||||
// 1/2 of the bottommost level, so picking single file in second most
|
||||
// level will cause significant waste, which is not desirable.
|
||||
//
|
||||
// This algorithm has lots of room to improve to pick more efficient
|
||||
// compactions.
|
||||
assert(sorted_runs_.size() >= 2);
|
||||
int second_last_level = sorted_runs_[sorted_runs_.size() - 2].level;
|
||||
if (second_last_level == 0) {
|
||||
// Can't split Level 0.
|
||||
return nullptr;
|
||||
}
|
||||
int output_level = sorted_runs_.back().level;
|
||||
const std::vector<FileMetaData*>& bottom_files =
|
||||
vstorage_->LevelFiles(output_level);
|
||||
const std::vector<FileMetaData*>& files =
|
||||
vstorage_->LevelFiles(second_last_level);
|
||||
assert(!bottom_files.empty());
|
||||
assert(!files.empty());
|
||||
|
||||
// std::unordered_map<uint64_t, uint64_t> file_to_order;
|
||||
|
||||
int picked_start_idx = 0;
|
||||
int picked_end_idx = 0;
|
||||
double picked_fanout = fanout_threshold;
|
||||
|
||||
// Use half target compaction bytes as anchor to stop growing second most
|
||||
// level files, and reserve growing space for more overlapping bottom level,
|
||||
// clean cut, files from other levels, etc.
|
||||
uint64_t comp_thres_size = mutable_cf_options_.max_compaction_bytes / 2;
|
||||
int start_idx = 0;
|
||||
int bottom_end_idx = 0;
|
||||
int bottom_start_idx = 0;
|
||||
uint64_t non_bottom_size = 0;
|
||||
uint64_t bottom_size = 0;
|
||||
bool end_bottom_size_counted = false;
|
||||
for (int end_idx = 0; end_idx < static_cast<int>(files.size()); end_idx++) {
|
||||
FileMetaData* end_file = files[end_idx];
|
||||
|
||||
// Include bottom most level files smaller than the current second
|
||||
// last level file.
|
||||
int num_skipped = 0;
|
||||
while (bottom_end_idx < static_cast<int>(bottom_files.size()) &&
|
||||
icmp_->Compare(bottom_files[bottom_end_idx]->largest,
|
||||
end_file->smallest) < 0) {
|
||||
if (!end_bottom_size_counted) {
|
||||
bottom_size += bottom_files[bottom_end_idx]->fd.file_size;
|
||||
}
|
||||
bottom_end_idx++;
|
||||
end_bottom_size_counted = false;
|
||||
num_skipped++;
|
||||
}
|
||||
|
||||
if (num_skipped > 1) {
|
||||
// At least a file in the bottom most level falls into the file gap. No
|
||||
// reason to include the file. We cut the range and start a new sliding
|
||||
// window.
|
||||
start_idx = end_idx;
|
||||
}
|
||||
|
||||
if (start_idx == end_idx) {
|
||||
// new sliding window.
|
||||
non_bottom_size = 0;
|
||||
bottom_size = 0;
|
||||
bottom_start_idx = bottom_end_idx;
|
||||
end_bottom_size_counted = false;
|
||||
}
|
||||
|
||||
non_bottom_size += end_file->fd.file_size;
|
||||
|
||||
// Include all overlapping files in bottom level.
|
||||
while (bottom_end_idx < static_cast<int>(bottom_files.size()) &&
|
||||
icmp_->Compare(bottom_files[bottom_end_idx]->smallest,
|
||||
end_file->largest) < 0) {
|
||||
if (!end_bottom_size_counted) {
|
||||
bottom_size += bottom_files[bottom_end_idx]->fd.file_size;
|
||||
end_bottom_size_counted = true;
|
||||
}
|
||||
if (icmp_->Compare(bottom_files[bottom_end_idx]->largest,
|
||||
end_file->largest) > 0) {
|
||||
// next level file cross large boundary of current file.
|
||||
break;
|
||||
}
|
||||
bottom_end_idx++;
|
||||
end_bottom_size_counted = false;
|
||||
}
|
||||
|
||||
if ((non_bottom_size + bottom_size > comp_thres_size ||
|
||||
end_idx == static_cast<int>(files.size()) - 1) &&
|
||||
non_bottom_size > 0) { // Do we alow 0 size file at all?
|
||||
// If it is a better compaction, remember it in picked* variables.
|
||||
double fanout = static_cast<double>(bottom_size) /
|
||||
static_cast<double>(non_bottom_size);
|
||||
if (fanout < picked_fanout) {
|
||||
picked_start_idx = start_idx;
|
||||
picked_end_idx = end_idx;
|
||||
picked_fanout = fanout;
|
||||
}
|
||||
// Shrink from the start end to under comp_thres_size
|
||||
while (non_bottom_size + bottom_size > comp_thres_size &&
|
||||
start_idx <= end_idx) {
|
||||
non_bottom_size -= files[start_idx]->fd.file_size;
|
||||
start_idx++;
|
||||
if (start_idx < static_cast<int>(files.size())) {
|
||||
while (bottom_start_idx <= bottom_end_idx &&
|
||||
icmp_->Compare(bottom_files[bottom_start_idx]->largest,
|
||||
files[start_idx]->smallest) < 0) {
|
||||
bottom_size -= bottom_files[bottom_start_idx]->fd.file_size;
|
||||
bottom_start_idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (picked_fanout >= fanout_threshold) {
|
||||
assert(picked_fanout == fanout_threshold);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<CompactionInputFiles> inputs;
|
||||
CompactionInputFiles bottom_level_inputs;
|
||||
CompactionInputFiles second_last_level_inputs;
|
||||
second_last_level_inputs.level = second_last_level;
|
||||
bottom_level_inputs.level = output_level;
|
||||
for (int i = picked_start_idx; i <= picked_end_idx; i++) {
|
||||
if (files[i]->being_compacted) {
|
||||
return nullptr;
|
||||
}
|
||||
second_last_level_inputs.files.push_back(files[i]);
|
||||
}
|
||||
assert(!second_last_level_inputs.empty());
|
||||
if (!picker_->ExpandInputsToCleanCut(cf_name_, vstorage_,
|
||||
&second_last_level_inputs,
|
||||
/*next_smallest=*/nullptr)) {
|
||||
return nullptr;
|
||||
}
|
||||
// We might be able to avoid this binary search if we save and expand
|
||||
// from bottom_start_idx and bottom_end_idx, but for now, we use
|
||||
// SetupOtherInputs() for simplicity.
|
||||
int parent_index = -1; // Create and use bottom_start_idx?
|
||||
if (!picker_->SetupOtherInputs(cf_name_, mutable_cf_options_, vstorage_,
|
||||
&second_last_level_inputs,
|
||||
&bottom_level_inputs, &parent_index,
|
||||
/*base_index=*/-1)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Try to include files in upper levels if they fall into the range.
|
||||
// Since we need to go from lower level up and this is in the reverse
|
||||
// order, compared to level order, we first write to an reversed
|
||||
// data structure and finally copy them to compaction inputs.
|
||||
InternalKey smallest, largest;
|
||||
picker_->GetRange(second_last_level_inputs, &smallest, &largest);
|
||||
std::vector<CompactionInputFiles> inputs_reverse;
|
||||
for (auto it = ++(++sorted_runs_.rbegin()); it != sorted_runs_.rend(); it++) {
|
||||
SortedRun& sr = *it;
|
||||
if (sr.level == 0) {
|
||||
break;
|
||||
}
|
||||
std::vector<FileMetaData*> level_inputs;
|
||||
vstorage_->GetCleanInputsWithinInterval(sr.level, &smallest, &largest,
|
||||
&level_inputs);
|
||||
if (!level_inputs.empty()) {
|
||||
inputs_reverse.push_back({});
|
||||
inputs_reverse.back().level = sr.level;
|
||||
inputs_reverse.back().files = level_inputs;
|
||||
picker_->GetRange(inputs_reverse.back(), &smallest, &largest);
|
||||
}
|
||||
}
|
||||
for (auto it = inputs_reverse.rbegin(); it != inputs_reverse.rend(); it++) {
|
||||
inputs.push_back(*it);
|
||||
}
|
||||
|
||||
inputs.push_back(second_last_level_inputs);
|
||||
inputs.push_back(bottom_level_inputs);
|
||||
|
||||
// TODO support multi paths?
|
||||
uint32_t path_id = 0;
|
||||
return new Compaction(
|
||||
vstorage_, ioptions_, mutable_cf_options_, mutable_db_options_,
|
||||
std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level, 1, true /* enable_compression */),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */,
|
||||
CompactionReason::kUniversalSizeAmplification);
|
||||
}
|
||||
|
||||
// Pick files marked for compaction. Typically, files are marked by
|
||||
// CompactOnDeleteCollector due to the presence of tombstones.
|
||||
Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
CompactionInputFiles start_level_inputs;
|
||||
int output_level;
|
||||
std::vector<CompactionInputFiles> inputs;
|
||||
std::vector<FileMetaData*> grandparents;
|
||||
|
||||
if (vstorage_->num_levels() == 1) {
|
||||
// This is single level universal. Since we're basically trying to reclaim
|
||||
@@ -934,6 +1196,9 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
if (picker_->FilesRangeOverlapWithCompaction(inputs, output_level)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
picker_->GetGrandparents(vstorage_, start_level_inputs,
|
||||
output_level_inputs, &grandparents);
|
||||
} else {
|
||||
inputs.push_back(start_level_inputs);
|
||||
}
|
||||
@@ -951,12 +1216,13 @@ Compaction* UniversalCompactionBuilder::PickDeleteTriggeredCompaction() {
|
||||
std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
/* max_grandparent_overlap_bytes */ LLONG_MAX, path_id,
|
||||
/* max_grandparent_overlap_bytes */ GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level, 1),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level),
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */,
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, grandparents, /* is manual */ false, score_,
|
||||
false /* deletion_compaction */,
|
||||
CompactionReason::kFilesMarkedForCompaction);
|
||||
}
|
||||
|
||||
@@ -1024,11 +1290,12 @@ Compaction* UniversalCompactionBuilder::PickCompactionToOldest(
|
||||
std::move(inputs), output_level,
|
||||
MaxFileSizeForLevel(mutable_cf_options_, output_level,
|
||||
kCompactionStyleUniversal),
|
||||
LLONG_MAX, path_id,
|
||||
GetMaxOverlappingBytes(), path_id,
|
||||
GetCompressionType(ioptions_, vstorage_, mutable_cf_options_,
|
||||
output_level, 1, true /* enable_compression */),
|
||||
GetCompressionOptions(mutable_cf_options_, vstorage_, output_level,
|
||||
true /* enable_compression */),
|
||||
Temperature::kUnknown,
|
||||
/* max_subcompactions */ 0, /* grandparents */ {}, /* is manual */ false,
|
||||
score_, false /* deletion_compaction */, compaction_reason);
|
||||
}
|
||||
@@ -1098,6 +1365,17 @@ Compaction* UniversalCompactionBuilder::PickPeriodicCompaction() {
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
uint64_t UniversalCompactionBuilder::GetMaxOverlappingBytes() const {
|
||||
if (!mutable_cf_options_.compaction_options_universal.incremental) {
|
||||
return port::kMaxUint64;
|
||||
} else {
|
||||
// Try to align cutting boundary with files at the next level if the
|
||||
// file isn't end up with 1/2 of target size, or it would overlap
|
||||
// with two full size files at the next level.
|
||||
return mutable_cf_options_.target_file_size_base / 2 * 3;
|
||||
}
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // !ROCKSDB_LITE
|
||||
@@ -10,23 +10,70 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class MyTestCompactionService : public CompactionService {
|
||||
class TestCompactionServiceBase {
|
||||
public:
|
||||
MyTestCompactionService(const std::string& db_path,
|
||||
std::shared_ptr<FileSystem> fs, Options& options)
|
||||
: db_path_(db_path), fs_(fs), options_(options) {}
|
||||
virtual int GetCompactionNum() = 0;
|
||||
|
||||
void OverrideStartStatus(CompactionServiceJobStatus s) {
|
||||
is_override_start_status = true;
|
||||
override_start_status = s;
|
||||
}
|
||||
|
||||
void OverrideWaitStatus(CompactionServiceJobStatus s) {
|
||||
is_override_wait_status = true;
|
||||
override_wait_status = s;
|
||||
}
|
||||
|
||||
void OverrideWaitResult(std::string str) {
|
||||
is_override_wait_result = true;
|
||||
override_wait_result = std::move(str);
|
||||
}
|
||||
|
||||
void ResetOverride() {
|
||||
is_override_wait_result = false;
|
||||
is_override_start_status = false;
|
||||
is_override_wait_status = false;
|
||||
}
|
||||
|
||||
virtual ~TestCompactionServiceBase() = default;
|
||||
|
||||
protected:
|
||||
bool is_override_start_status = false;
|
||||
CompactionServiceJobStatus override_start_status =
|
||||
CompactionServiceJobStatus::kFailure;
|
||||
bool is_override_wait_status = false;
|
||||
CompactionServiceJobStatus override_wait_status =
|
||||
CompactionServiceJobStatus::kFailure;
|
||||
bool is_override_wait_result = false;
|
||||
std::string override_wait_result;
|
||||
};
|
||||
|
||||
class MyTestCompactionServiceLegacy : public CompactionService,
|
||||
public TestCompactionServiceBase {
|
||||
public:
|
||||
MyTestCompactionServiceLegacy(std::string db_path, Options& options,
|
||||
std::shared_ptr<Statistics>& statistics)
|
||||
: db_path_(std::move(db_path)),
|
||||
options_(options),
|
||||
statistics_(statistics) {}
|
||||
|
||||
static const char* kClassName() { return "MyTestCompactionServiceLegacy"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
CompactionServiceJobStatus Start(const std::string& compaction_service_input,
|
||||
int job_id) override {
|
||||
uint64_t job_id) override {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
jobs_.emplace(job_id, compaction_service_input);
|
||||
CompactionServiceJobStatus s = CompactionServiceJobStatus::kSuccess;
|
||||
TEST_SYNC_POINT_CALLBACK("MyTestCompactionService::Start::End", &s);
|
||||
if (is_override_start_status) {
|
||||
return override_start_status;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus WaitForComplete(
|
||||
int job_id, std::string* compaction_service_result) override {
|
||||
uint64_t job_id, std::string* compaction_service_result) override {
|
||||
std::string compaction_input;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
@@ -38,6 +85,10 @@ class MyTestCompactionService : public CompactionService {
|
||||
jobs_.erase(i);
|
||||
}
|
||||
|
||||
if (is_override_wait_status) {
|
||||
return override_wait_status;
|
||||
}
|
||||
|
||||
CompactionServiceOptionsOverride options_override;
|
||||
options_override.env = options_.env;
|
||||
options_override.file_checksum_gen_factory =
|
||||
@@ -50,12 +101,14 @@ class MyTestCompactionService : public CompactionService {
|
||||
options_override.prefix_extractor = options_.prefix_extractor;
|
||||
options_override.table_factory = options_.table_factory;
|
||||
options_override.sst_partitioner_factory = options_.sst_partitioner_factory;
|
||||
options_override.statistics = statistics_;
|
||||
|
||||
Status s = DB::OpenAndCompact(db_path_, db_path_ + "/" + ToString(job_id),
|
||||
compaction_input, compaction_service_result,
|
||||
options_override);
|
||||
TEST_SYNC_POINT_CALLBACK("MyTestCompactionService::WaitForComplete::End",
|
||||
compaction_service_result);
|
||||
Status s = DB::OpenAndCompact(
|
||||
db_path_, db_path_ + "/" + ROCKSDB_NAMESPACE::ToString(job_id),
|
||||
compaction_input, compaction_service_result, options_override);
|
||||
if (is_override_wait_result) {
|
||||
*compaction_service_result = override_wait_result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
@@ -64,23 +117,155 @@ class MyTestCompactionService : public CompactionService {
|
||||
}
|
||||
}
|
||||
|
||||
int GetCompactionNum() { return compaction_num_.load(); }
|
||||
int GetCompactionNum() override { return compaction_num_.load(); }
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic_int compaction_num_{0};
|
||||
std::map<int, std::string> jobs_;
|
||||
std::map<uint64_t, std::string> jobs_;
|
||||
const std::string db_path_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
Options options_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
};
|
||||
|
||||
class CompactionServiceTest : public DBTestBase {
|
||||
class MyTestCompactionService : public CompactionService,
|
||||
public TestCompactionServiceBase {
|
||||
public:
|
||||
MyTestCompactionService(std::string db_path, Options& options,
|
||||
std::shared_ptr<Statistics>& statistics)
|
||||
: db_path_(std::move(db_path)),
|
||||
options_(options),
|
||||
statistics_(statistics),
|
||||
start_info_("na", "na", "na", 0, Env::TOTAL),
|
||||
wait_info_("na", "na", "na", 0, Env::TOTAL) {}
|
||||
|
||||
static const char* kClassName() { return "MyTestCompactionService"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
CompactionServiceJobStatus StartV2(
|
||||
const CompactionServiceJobInfo& info,
|
||||
const std::string& compaction_service_input) override {
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
start_info_ = info;
|
||||
assert(info.db_name == db_path_);
|
||||
jobs_.emplace(info.job_id, compaction_service_input);
|
||||
CompactionServiceJobStatus s = CompactionServiceJobStatus::kSuccess;
|
||||
if (is_override_start_status) {
|
||||
return override_start_status;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus WaitForCompleteV2(
|
||||
const CompactionServiceJobInfo& info,
|
||||
std::string* compaction_service_result) override {
|
||||
std::string compaction_input;
|
||||
assert(info.db_name == db_path_);
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
wait_info_ = info;
|
||||
auto i = jobs_.find(info.job_id);
|
||||
if (i == jobs_.end()) {
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
compaction_input = std::move(i->second);
|
||||
jobs_.erase(i);
|
||||
}
|
||||
|
||||
if (is_override_wait_status) {
|
||||
return override_wait_status;
|
||||
}
|
||||
|
||||
CompactionServiceOptionsOverride options_override;
|
||||
options_override.env = options_.env;
|
||||
options_override.file_checksum_gen_factory =
|
||||
options_.file_checksum_gen_factory;
|
||||
options_override.comparator = options_.comparator;
|
||||
options_override.merge_operator = options_.merge_operator;
|
||||
options_override.compaction_filter = options_.compaction_filter;
|
||||
options_override.compaction_filter_factory =
|
||||
options_.compaction_filter_factory;
|
||||
options_override.prefix_extractor = options_.prefix_extractor;
|
||||
options_override.table_factory = options_.table_factory;
|
||||
options_override.sst_partitioner_factory = options_.sst_partitioner_factory;
|
||||
options_override.statistics = statistics_;
|
||||
|
||||
Status s = DB::OpenAndCompact(
|
||||
db_path_, db_path_ + "/" + ROCKSDB_NAMESPACE::ToString(info.job_id),
|
||||
compaction_input, compaction_service_result, options_override);
|
||||
if (is_override_wait_result) {
|
||||
*compaction_service_result = override_wait_result;
|
||||
}
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
} else {
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
}
|
||||
|
||||
int GetCompactionNum() override { return compaction_num_.load(); }
|
||||
|
||||
CompactionServiceJobInfo GetCompactionInfoForStart() { return start_info_; }
|
||||
CompactionServiceJobInfo GetCompactionInfoForWait() { return wait_info_; }
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic_int compaction_num_{0};
|
||||
std::map<uint64_t, std::string> jobs_;
|
||||
const std::string db_path_;
|
||||
Options options_;
|
||||
std::shared_ptr<Statistics> statistics_;
|
||||
CompactionServiceJobInfo start_info_;
|
||||
CompactionServiceJobInfo wait_info_;
|
||||
};
|
||||
|
||||
// This is only for listing test classes
|
||||
enum TestCompactionServiceType {
|
||||
MyTestCompactionServiceType,
|
||||
MyTestCompactionServiceLegacyType,
|
||||
};
|
||||
|
||||
class CompactionServiceTest
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<TestCompactionServiceType> {
|
||||
public:
|
||||
explicit CompactionServiceTest()
|
||||
: DBTestBase("compaction_service_test", true) {}
|
||||
|
||||
protected:
|
||||
void ReopenWithCompactionService(Options* options) {
|
||||
options->env = env_;
|
||||
primary_statistics_ = CreateDBStatistics();
|
||||
options->statistics = primary_statistics_;
|
||||
compactor_statistics_ = CreateDBStatistics();
|
||||
TestCompactionServiceType cs_type = GetParam();
|
||||
switch (cs_type) {
|
||||
case MyTestCompactionServiceType:
|
||||
compaction_service_ = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, *options, compactor_statistics_);
|
||||
break;
|
||||
case MyTestCompactionServiceLegacyType:
|
||||
compaction_service_ = std::make_shared<MyTestCompactionServiceLegacy>(
|
||||
dbname_, *options, compactor_statistics_);
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
options->compaction_service = compaction_service_;
|
||||
DestroyAndReopen(*options);
|
||||
}
|
||||
|
||||
Statistics* GetCompactorStatistics() { return compactor_statistics_.get(); }
|
||||
|
||||
Statistics* GetPrimaryStatistics() { return primary_statistics_.get(); }
|
||||
|
||||
TestCompactionServiceBase* GetCompactionService() {
|
||||
CompactionService* cs = compaction_service_.get();
|
||||
return dynamic_cast<TestCompactionServiceBase*>(cs);
|
||||
}
|
||||
|
||||
void GenerateTestData() {
|
||||
// Generate 20 files @ L2
|
||||
for (int i = 0; i < 20; i++) {
|
||||
@@ -114,15 +299,19 @@ class CompactionServiceTest : public DBTestBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Statistics> compactor_statistics_;
|
||||
std::shared_ptr<Statistics> primary_statistics_;
|
||||
std::shared_ptr<CompactionService> compaction_service_;
|
||||
};
|
||||
|
||||
TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
TEST_P(CompactionServiceTest, BasicCompactions) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
Statistics* primary_statistics = GetPrimaryStatistics();
|
||||
Statistics* compactor_statistics = GetCompactorStatistics();
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
@@ -150,15 +339,32 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
|
||||
// make sure the compaction statistics is only recorded on the remote side
|
||||
ASSERT_GE(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES), 1);
|
||||
ASSERT_GE(compactor_statistics->getTickerCount(COMPACT_READ_BYTES), 1);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES), 0);
|
||||
// even with remote compaction, primary host still needs to read SST files to
|
||||
// `verify_table()`.
|
||||
ASSERT_GE(primary_statistics->getTickerCount(COMPACT_READ_BYTES), 1);
|
||||
// all the compaction write happens on the remote side
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES),
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES));
|
||||
ASSERT_GE(primary_statistics->getTickerCount(REMOTE_COMPACT_READ_BYTES), 1);
|
||||
ASSERT_GT(primary_statistics->getTickerCount(COMPACT_READ_BYTES),
|
||||
primary_statistics->getTickerCount(REMOTE_COMPACT_READ_BYTES));
|
||||
// compactor is already the remote side, which doesn't have remote
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(REMOTE_COMPACT_READ_BYTES), 0);
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES),
|
||||
0);
|
||||
|
||||
// Test failed compaction
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
|
||||
// override job status
|
||||
Status* s = static_cast<Status*>(status);
|
||||
auto s = static_cast<Status*>(status);
|
||||
*s = Status::Aborted("MyTestCompactionService failed to compact!");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
@@ -187,17 +393,13 @@ TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
ASSERT_TRUE(s.IsAborted());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
TEST_P(CompactionServiceTest, ManualCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
@@ -228,22 +430,15 @@ TEST_F(CompactionServiceTest, ManualCompaction) {
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, FailedToStart) {
|
||||
TEST_P(CompactionServiceTest, FailedToStart) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
GenerateTestData();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MyTestCompactionService::Start::End", [&](void* status) {
|
||||
// override job status
|
||||
auto s = static_cast<CompactionServiceJobStatus*>(status);
|
||||
*s = CompactionServiceJobStatus::kFailure;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto my_cs = GetCompactionService();
|
||||
my_cs->OverrideStartStatus(CompactionServiceJobStatus::kFailure);
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
@@ -253,22 +448,15 @@ TEST_F(CompactionServiceTest, FailedToStart) {
|
||||
ASSERT_TRUE(s.IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, InvalidResult) {
|
||||
TEST_P(CompactionServiceTest, InvalidResult) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
GenerateTestData();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MyTestCompactionService::WaitForComplete::End", [&](void* result) {
|
||||
// override job status
|
||||
auto result_str = static_cast<std::string*>(result);
|
||||
*result_str = "Invalid Str";
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
auto my_cs = GetCompactionService();
|
||||
my_cs->OverrideWaitResult("Invalid Str");
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
@@ -278,22 +466,27 @@ TEST_F(CompactionServiceTest, InvalidResult) {
|
||||
ASSERT_FALSE(s.ok());
|
||||
}
|
||||
|
||||
// TODO: support sub-compaction
|
||||
TEST_F(CompactionServiceTest, DISABLED_SubCompaction) {
|
||||
TEST_P(CompactionServiceTest, SubCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.max_subcompactions = 10;
|
||||
options.target_file_size_base = 1 << 10; // 1KB
|
||||
options.disable_auto_compactions = true;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
GenerateTestData();
|
||||
VerifyTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
int compaction_num_before = my_cs->GetCompactionNum();
|
||||
|
||||
auto cro = CompactRangeOptions();
|
||||
cro.max_subcompactions = 10;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
Status s = db_->CompactRange(cro, nullptr, nullptr);
|
||||
ASSERT_OK(s);
|
||||
VerifyTestData();
|
||||
int compaction_num = my_cs->GetCompactionNum() - compaction_num_before;
|
||||
// make sure there's sub-compaction by checking the compaction number
|
||||
ASSERT_GE(compaction_num, 2);
|
||||
}
|
||||
|
||||
class PartialDeleteCompactionFilter : public CompactionFilter {
|
||||
@@ -312,15 +505,12 @@ class PartialDeleteCompactionFilter : public CompactionFilter {
|
||||
const char* Name() const override { return "PartialDeleteCompactionFilter"; }
|
||||
};
|
||||
|
||||
TEST_F(CompactionServiceTest, CompactionFilter) {
|
||||
TEST_P(CompactionServiceTest, CompactionFilter) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
auto delete_comp_filter = PartialDeleteCompactionFilter();
|
||||
options.compaction_filter = &delete_comp_filter;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
std::unique_ptr<CompactionFilter> delete_comp_filter(
|
||||
new PartialDeleteCompactionFilter());
|
||||
options.compaction_filter = delete_comp_filter.get();
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
@@ -352,18 +542,13 @@ TEST_F(CompactionServiceTest, CompactionFilter) {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, Snapshot) {
|
||||
TEST_P(CompactionServiceTest, Snapshot) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "value1"));
|
||||
ASSERT_OK(Put(Key(2), "value1"));
|
||||
@@ -375,23 +560,18 @@ TEST_F(CompactionServiceTest, Snapshot) {
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
ASSERT_EQ("value1", Get(Key(1), s1));
|
||||
ASSERT_EQ("value2", Get(Key(1)));
|
||||
db_->ReleaseSnapshot(s1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, ConcurrentCompaction) {
|
||||
TEST_P(CompactionServiceTest, ConcurrentCompaction) {
|
||||
Options options = CurrentOptions();
|
||||
options.level0_file_num_compaction_trigger = 100;
|
||||
options.env = env_;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
options.max_background_jobs = 20;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ReopenWithCompactionService(&options);
|
||||
GenerateTestData();
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
@@ -419,12 +599,211 @@ TEST_F(CompactionServiceTest, ConcurrentCompaction) {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
auto my_cs = GetCompactionService();
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), 10);
|
||||
ASSERT_EQ(FilesPerLevel(), "0,0,10");
|
||||
}
|
||||
|
||||
TEST_P(CompactionServiceTest, CompactionInfo) {
|
||||
// only test compaction info for new compaction service interface
|
||||
if (GetParam() != MyTestCompactionServiceType) {
|
||||
return;
|
||||
}
|
||||
|
||||
Options options = CurrentOptions();
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 20 + j * 2;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
auto my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_GE(comp_num, 1);
|
||||
|
||||
CompactionServiceJobInfo info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(dbname_, info.db_name);
|
||||
std::string db_id, db_session_id;
|
||||
ASSERT_OK(db_->GetDbIdentity(db_id));
|
||||
ASSERT_EQ(db_id, info.db_id);
|
||||
ASSERT_OK(db_->GetDbSessionId(db_session_id));
|
||||
ASSERT_EQ(db_session_id, info.db_session_id);
|
||||
ASSERT_EQ(Env::LOW, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(dbname_, info.db_name);
|
||||
ASSERT_EQ(db_id, info.db_id);
|
||||
ASSERT_EQ(db_session_id, info.db_session_id);
|
||||
ASSERT_EQ(Env::LOW, info.priority);
|
||||
|
||||
// Test priority USER
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
SstFileMetaData file = meta.levels[1].files[0];
|
||||
ASSERT_OK(db_->CompactFiles(CompactionOptions(),
|
||||
{file.db_path + "/" + file.name}, 2));
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::USER, info.priority);
|
||||
|
||||
// Test priority BOTTOM
|
||||
env_->SetBackgroundThreads(1, Env::BOTTOM);
|
||||
options.num_levels = 2;
|
||||
ReopenWithCompactionService(&options);
|
||||
my_cs =
|
||||
static_cast_with_check<MyTestCompactionService>(GetCompactionService());
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 20 + j * 2;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
info = my_cs->GetCompactionInfoForStart();
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
info = my_cs->GetCompactionInfoForWait();
|
||||
ASSERT_EQ(Env::BOTTOM, info.priority);
|
||||
}
|
||||
|
||||
TEST_P(CompactionServiceTest, FallbackLocalAuto) {
|
||||
Options options = CurrentOptions();
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
Statistics* compactor_statistics = GetCompactorStatistics();
|
||||
Statistics* primary_statistics = GetPrimaryStatistics();
|
||||
uint64_t compactor_write_bytes =
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
uint64_t primary_write_bytes =
|
||||
primary_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
|
||||
my_cs->OverrideStartStatus(CompactionServiceJobStatus::kUseLocal);
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 10 + j;
|
||||
ASSERT_OK(Put(Key(key_id), "value" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 20 + j * 2;
|
||||
ASSERT_OK(Put(Key(key_id), "value_new" + ToString(key_id)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// verify result
|
||||
for (int i = 0; i < 200; i++) {
|
||||
auto result = Get(Key(i));
|
||||
if (i % 2) {
|
||||
ASSERT_EQ(result, "value" + ToString(i));
|
||||
} else {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), 0);
|
||||
|
||||
// make sure the compaction statistics is only recorded on the local side
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
compactor_write_bytes);
|
||||
ASSERT_GT(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
primary_write_bytes);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_READ_BYTES), 0);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES), 0);
|
||||
}
|
||||
|
||||
TEST_P(CompactionServiceTest, FallbackLocalManual) {
|
||||
Options options = CurrentOptions();
|
||||
options.disable_auto_compactions = true;
|
||||
ReopenWithCompactionService(&options);
|
||||
|
||||
GenerateTestData();
|
||||
VerifyTestData();
|
||||
|
||||
auto my_cs = GetCompactionService();
|
||||
Statistics* compactor_statistics = GetCompactorStatistics();
|
||||
Statistics* primary_statistics = GetPrimaryStatistics();
|
||||
uint64_t compactor_write_bytes =
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
uint64_t primary_write_bytes =
|
||||
primary_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
|
||||
// re-enable remote compaction
|
||||
my_cs->ResetOverride();
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
uint64_t comp_num = my_cs->GetCompactionNum();
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
// make sure the compaction statistics is only recorded on the remote side
|
||||
ASSERT_GT(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
compactor_write_bytes);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES),
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES));
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
primary_write_bytes);
|
||||
|
||||
// return run local again with API WaitForComplete
|
||||
my_cs->OverrideWaitStatus(CompactionServiceJobStatus::kUseLocal);
|
||||
start_str = Key(120);
|
||||
start = start_str;
|
||||
comp_num = my_cs->GetCompactionNum();
|
||||
compactor_write_bytes =
|
||||
compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
primary_write_bytes = primary_statistics->getTickerCount(COMPACT_WRITE_BYTES);
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, nullptr));
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(),
|
||||
comp_num); // no remote compaction is run
|
||||
// make sure the compaction statistics is only recorded on the local side
|
||||
ASSERT_EQ(compactor_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
compactor_write_bytes);
|
||||
ASSERT_GT(primary_statistics->getTickerCount(COMPACT_WRITE_BYTES),
|
||||
primary_write_bytes);
|
||||
ASSERT_EQ(primary_statistics->getTickerCount(REMOTE_COMPACT_WRITE_BYTES),
|
||||
compactor_write_bytes);
|
||||
|
||||
// verify result after 2 manual compactions
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
CompactionServiceTest, CompactionServiceTest,
|
||||
::testing::Values(
|
||||
TestCompactionServiceType::MyTestCompactionServiceType,
|
||||
TestCompactionServiceType::MyTestCompactionServiceLegacyType));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// This source code is licensed under both the GPLv2 (found in the
|
||||
// COPYING file in the root directory) and Apache 2.0 License
|
||||
// (found in the LICENSE.Apache file in the root directory).
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
|
||||
#include "db/version_edit.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
// We boost files that are closer to TTL limit. This boosting could be
|
||||
// through FileMetaData.compensated_file_size but this compensated size
|
||||
// is widely used as something similar to file size so dramatically boost
|
||||
// the value might cause unintended consequences.
|
||||
//
|
||||
// This boosting algorithm can go very fancy, but here we use a simple
|
||||
// formula which can satisify:
|
||||
// (1) Different levels are triggered slightly differently to avoid
|
||||
// too many cascading cases
|
||||
// (2) Files in the same level get boosting more when TTL gets closer.
|
||||
//
|
||||
// Don't do any boosting before TTL has past by half. This is to make
|
||||
// sure lower write amp for most of the case. And all levels should be
|
||||
// fully boosted when total TTL compaction threshold triggers.
|
||||
// Differientiate boosting ranges of each level by 1/2. This will make
|
||||
// range for each level exponentially increasing. We could do it by
|
||||
// having them to be equal, or go even fancier. We can adjust it after
|
||||
// we observe the behavior in production.
|
||||
// The threshold starting boosting:
|
||||
// +------------------------------------------------------------------ +
|
||||
// ^ ^ ^ ^ ^ ^
|
||||
// Age 0 ... | | second last level thresold
|
||||
// | |
|
||||
// | third last level
|
||||
// |
|
||||
// forth last level
|
||||
//
|
||||
// We arbitrarily set with 0 when a file is aged boost_age_start and
|
||||
// grow linearly. The ratio is arbitrarily set so that when the next level
|
||||
// starts to boost, the previous level's boosting amount is 16.
|
||||
class FileTtlBooster {
|
||||
public:
|
||||
FileTtlBooster(uint64_t current_time, uint64_t ttl, int num_non_empty_levels,
|
||||
int level)
|
||||
: current_time_(current_time) {
|
||||
if (ttl == 0 || level == 0 || level >= num_non_empty_levels - 1) {
|
||||
enabled_ = false;
|
||||
boost_age_start_ = 0;
|
||||
boost_step_ = 1;
|
||||
} else {
|
||||
enabled_ = true;
|
||||
uint64_t all_boost_start_age = ttl / 2;
|
||||
uint64_t all_boost_age_range = (ttl / 32) * 31 - all_boost_start_age;
|
||||
uint64_t boost_age_range =
|
||||
all_boost_age_range >> (num_non_empty_levels - level - 1);
|
||||
boost_age_start_ = all_boost_start_age + boost_age_range;
|
||||
const uint64_t kBoostRatio = 16;
|
||||
// prevent 0 value to avoid divide 0 error.
|
||||
boost_step_ = std::max(boost_age_range / kBoostRatio, uint64_t{1});
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t GetBoostScore(FileMetaData* f) {
|
||||
if (!enabled_) {
|
||||
return 1;
|
||||
}
|
||||
uint64_t oldest_ancester_time = f->TryGetOldestAncesterTime();
|
||||
if (oldest_ancester_time >= current_time_) {
|
||||
return 1;
|
||||
}
|
||||
uint64_t age = current_time_ - oldest_ancester_time;
|
||||
if (age > boost_age_start_) {
|
||||
// Use integer just for convenience.
|
||||
// We could make all file_to_order double if we want.
|
||||
// Technically this can overflow if users override timing and
|
||||
// give a very high current time. Ignore the case for simplicity.
|
||||
// Boosting is addition to current value, so +1. This will effectively
|
||||
// make boosting to kick in after the first boost_step_ is reached.
|
||||
return (age - boost_age_start_) / boost_step_ + 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
private:
|
||||
bool enabled_;
|
||||
uint64_t current_time_;
|
||||
uint64_t boost_age_start_;
|
||||
uint64_t boost_step_;
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -8,7 +8,24 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "rocksdb/utilities/customizable_util.h"
|
||||
#include "rocksdb/utilities/object_registry.h"
|
||||
#include "rocksdb/utilities/options_type.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
static std::unordered_map<std::string, OptionTypeInfo>
|
||||
sst_fixed_prefix_type_info = {
|
||||
#ifndef ROCKSDB_LITE
|
||||
{"length",
|
||||
{0, OptionType::kSizeT, OptionVerificationType::kNormal,
|
||||
OptionTypeFlags::kNone}},
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
|
||||
SstPartitionerFixedPrefixFactory::SstPartitionerFixedPrefixFactory(size_t len)
|
||||
: len_(len) {
|
||||
RegisterOptions("Length", &len_, &sst_fixed_prefix_type_info);
|
||||
}
|
||||
|
||||
PartitionerResult SstPartitionerFixedPrefix::ShouldPartition(
|
||||
const PartitionerRequest& request) {
|
||||
@@ -41,4 +58,33 @@ std::shared_ptr<SstPartitionerFactory> NewSstPartitionerFixedPrefixFactory(
|
||||
return std::make_shared<SstPartitionerFixedPrefixFactory>(prefix_len);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
namespace {
|
||||
static int RegisterSstPartitionerFactories(ObjectLibrary& library,
|
||||
const std::string& /*arg*/) {
|
||||
library.Register<SstPartitionerFactory>(
|
||||
SstPartitionerFixedPrefixFactory::kClassName(),
|
||||
[](const std::string& /*uri*/,
|
||||
std::unique_ptr<SstPartitionerFactory>* guard,
|
||||
std::string* /* errmsg */) {
|
||||
guard->reset(new SstPartitionerFixedPrefixFactory(0));
|
||||
return guard->get();
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
} // namespace
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Status SstPartitionerFactory::CreateFromString(
|
||||
const ConfigOptions& options, const std::string& value,
|
||||
std::shared_ptr<SstPartitionerFactory>* result) {
|
||||
#ifndef ROCKSDB_LITE
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&]() {
|
||||
RegisterSstPartitionerFactories(*(ObjectLibrary::Default().get()), "");
|
||||
});
|
||||
#endif // ROCKSDB_LITE
|
||||
return LoadSharedObject<SstPartitionerFactory>(options, value, nullptr,
|
||||
result);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -55,14 +55,9 @@ class CorruptionTest : public testing::Test {
|
||||
// bug in recovery code. Keep it 4 for now to make the test passes.
|
||||
tiny_cache_ = NewLRUCache(100, 4);
|
||||
Env* base_env = Env::Default();
|
||||
#ifndef ROCKSDB_LITE
|
||||
const char* test_env_uri = getenv("TEST_ENV_URI");
|
||||
if (test_env_uri) {
|
||||
Status s = Env::LoadEnv(test_env_uri, &base_env, &env_guard_);
|
||||
EXPECT_OK(s);
|
||||
EXPECT_NE(Env::Default(), base_env);
|
||||
}
|
||||
#endif //! ROCKSDB_LITE
|
||||
EXPECT_OK(
|
||||
test::CreateEnvFromSystem(ConfigOptions(), &base_env, &env_guard_));
|
||||
EXPECT_NE(base_env, nullptr);
|
||||
env_ = new test::ErrorEnv(base_env);
|
||||
options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
|
||||
options_.env = env_;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "table/cuckoo/cuckoo_table_factory.h"
|
||||
@@ -94,8 +95,8 @@ class CuckooTableDBTest : public testing::Test {
|
||||
|
||||
int NumTableFilesAtLevel(int level) {
|
||||
std::string property;
|
||||
EXPECT_TRUE(db_->GetProperty(
|
||||
"rocksdb.num-files-at-level" + NumberToString(level), &property));
|
||||
EXPECT_TRUE(db_->GetProperty("rocksdb.num-files-at-level" + ToString(level),
|
||||
&property));
|
||||
return atoi(property.c_str());
|
||||
}
|
||||
|
||||
@@ -133,6 +134,7 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
VerifySstUniqueIds(ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
ASSERT_EQ(3U, ptc.begin()->second->num_entries);
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
@@ -149,6 +151,7 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
VerifySstUniqueIds(ptc);
|
||||
ASSERT_EQ(2U, ptc.size());
|
||||
auto row = ptc.begin();
|
||||
ASSERT_EQ(3U, row->second->num_entries);
|
||||
@@ -166,6 +169,7 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
ASSERT_OK(Delete("key4"));
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
VerifySstUniqueIds(ptc);
|
||||
ASSERT_EQ(3U, ptc.size());
|
||||
row = ptc.begin();
|
||||
ASSERT_EQ(3U, row->second->num_entries);
|
||||
@@ -190,6 +194,7 @@ TEST_F(CuckooTableDBTest, FlushWithDuplicateKeys) {
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
VerifySstUniqueIds(ptc);
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
ASSERT_EQ(2U, ptc.begin()->second->num_entries);
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
+75
-21
@@ -8,9 +8,9 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <cstring>
|
||||
#include <regex>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/flush_block_policy.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
@@ -31,7 +31,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBBasicTest : public DBTestBase {
|
||||
public:
|
||||
DBBasicTest() : DBTestBase("/db_basic_test", /*env_do_fsync=*/false) {}
|
||||
DBBasicTest() : DBTestBase("db_basic_test", /*env_do_fsync=*/false) {}
|
||||
};
|
||||
|
||||
TEST_F(DBBasicTest, OpenWhenOpen) {
|
||||
@@ -69,11 +69,10 @@ TEST_F(DBBasicTest, UniqueSession) {
|
||||
ASSERT_EQ(sid2, sid4);
|
||||
|
||||
// Expected compact format for session ids (see notes in implementation)
|
||||
std::regex expected("[0-9A-Z]{20}");
|
||||
const std::string match("match");
|
||||
EXPECT_EQ(match, std::regex_replace(sid1, expected, match));
|
||||
EXPECT_EQ(match, std::regex_replace(sid2, expected, match));
|
||||
EXPECT_EQ(match, std::regex_replace(sid3, expected, match));
|
||||
TestRegex expected("[0-9A-Z]{20}");
|
||||
EXPECT_MATCHES_REGEX(sid1, expected);
|
||||
EXPECT_MATCHES_REGEX(sid2, expected);
|
||||
EXPECT_MATCHES_REGEX(sid3, expected);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Close();
|
||||
@@ -976,13 +975,14 @@ TEST_F(DBBasicTest, MultiGetEmpty) {
|
||||
TEST_F(DBBasicTest, ChecksumTest) {
|
||||
BlockBasedTableOptions table_options;
|
||||
Options options = CurrentOptions();
|
||||
// change when new checksum type added
|
||||
int max_checksum = static_cast<int>(kxxHash64);
|
||||
const int kNumPerFile = 2;
|
||||
|
||||
const auto algs = GetSupportedChecksums();
|
||||
const int algs_size = static_cast<int>(algs.size());
|
||||
|
||||
// generate one table with each type of checksum
|
||||
for (int i = 0; i <= max_checksum; ++i) {
|
||||
table_options.checksum = static_cast<ChecksumType>(i);
|
||||
for (int i = 0; i < algs_size; ++i) {
|
||||
table_options.checksum = algs[i];
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
for (int j = 0; j < kNumPerFile; ++j) {
|
||||
@@ -992,15 +992,20 @@ TEST_F(DBBasicTest, ChecksumTest) {
|
||||
}
|
||||
|
||||
// with each valid checksum type setting...
|
||||
for (int i = 0; i <= max_checksum; ++i) {
|
||||
table_options.checksum = static_cast<ChecksumType>(i);
|
||||
for (int i = 0; i < algs_size; ++i) {
|
||||
table_options.checksum = algs[i];
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
// verify every type of checksum (should be regardless of that setting)
|
||||
for (int j = 0; j < (max_checksum + 1) * kNumPerFile; ++j) {
|
||||
for (int j = 0; j < algs_size * kNumPerFile; ++j) {
|
||||
ASSERT_EQ(Key(j), Get(Key(j)));
|
||||
}
|
||||
}
|
||||
|
||||
// Now test invalid checksum type
|
||||
table_options.checksum = static_cast<ChecksumType>(123);
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
ASSERT_TRUE(TryReopen(options).IsInvalidArgument());
|
||||
}
|
||||
|
||||
// On Windows you can have either memory mapped file or a file
|
||||
@@ -1132,9 +1137,15 @@ TEST_F(DBBasicTest, DBCloseFlushError) {
|
||||
ASSERT_OK(Put("key3", "value3"));
|
||||
fault_injection_env->SetFilesystemActive(false);
|
||||
Status s = dbfull()->Close();
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
ASSERT_NE(s, Status::OK());
|
||||
|
||||
// retry should return the same error
|
||||
s = dbfull()->Close();
|
||||
ASSERT_NE(s, Status::OK());
|
||||
fault_injection_env->SetFilesystemActive(true);
|
||||
// retry close() is no-op even the system is back. Could be improved if
|
||||
// Close() is retry-able: #9029
|
||||
s = dbfull()->Close();
|
||||
ASSERT_NE(s, Status::OK());
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
@@ -1362,6 +1373,28 @@ TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFSnapshot) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DBMultiGetTestWithParam, MultiGetMultiCFUnsorted) {
|
||||
Options options = CurrentOptions();
|
||||
CreateAndReopenWithCF({"one", "two"}, options);
|
||||
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
ASSERT_OK(Put(2, "baz", "xyz"));
|
||||
ASSERT_OK(Put(1, "abc", "def"));
|
||||
|
||||
// Note: keys for the same CF do not form a consecutive range
|
||||
std::vector<int> cfs{1, 2, 1};
|
||||
std::vector<std::string> keys{"foo", "baz", "abc"};
|
||||
std::vector<std::string> values;
|
||||
|
||||
values =
|
||||
MultiGet(cfs, keys, /* snapshot */ nullptr, /* batched */ GetParam());
|
||||
|
||||
ASSERT_EQ(values.size(), 3);
|
||||
ASSERT_EQ(values[0], "bar");
|
||||
ASSERT_EQ(values[1], "xyz");
|
||||
ASSERT_EQ(values[2], "def");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBMultiGetTestWithParam, DBMultiGetTestWithParam,
|
||||
testing::Bool());
|
||||
|
||||
@@ -2571,12 +2604,27 @@ TEST_F(DBBasicTest, ManifestChecksumMismatch) {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, ConcurrentlyCloseDB) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
std::vector<std::thread> workers;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
workers.push_back(std::thread([&]() {
|
||||
auto s = db_->Close();
|
||||
ASSERT_OK(s);
|
||||
}));
|
||||
}
|
||||
for (auto& w : workers) {
|
||||
w.join();
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class DBBasicTestTrackWal : public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBBasicTestTrackWal()
|
||||
: DBTestBase("/db_basic_test_track_wal", /*env_do_fsync=*/false) {}
|
||||
: DBTestBase("db_basic_test_track_wal", /*env_do_fsync=*/false) {}
|
||||
|
||||
int CountWalFiles() {
|
||||
VectorLogPtr log_files;
|
||||
@@ -2734,6 +2782,11 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
EXPECT_OK(dbfull()->Flush(FlushOptions(), handles_[cf]));
|
||||
}
|
||||
}
|
||||
// Clear compressed cache, which is always pre-populated
|
||||
if (compressed_cache_) {
|
||||
compressed_cache_->SetCapacity(0);
|
||||
compressed_cache_->SetCapacity(1048576);
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckValue(int i, const std::string& value) {
|
||||
@@ -3210,13 +3263,11 @@ INSTANTIATE_TEST_CASE_P(ParallelIO, DBBasicTestWithParallelIO,
|
||||
// Forward declaration
|
||||
class DeadlineFS;
|
||||
|
||||
class DeadlineRandomAccessFile : public FSRandomAccessFileWrapper {
|
||||
class DeadlineRandomAccessFile : public FSRandomAccessFileOwnerWrapper {
|
||||
public:
|
||||
DeadlineRandomAccessFile(DeadlineFS& fs,
|
||||
std::unique_ptr<FSRandomAccessFile>& file)
|
||||
: FSRandomAccessFileWrapper(file.get()),
|
||||
fs_(fs),
|
||||
file_(std::move(file)) {}
|
||||
: FSRandomAccessFileOwnerWrapper(std::move(file)), fs_(fs) {}
|
||||
|
||||
IOStatus Read(uint64_t offset, size_t len, const IOOptions& opts,
|
||||
Slice* result, char* scratch,
|
||||
@@ -3245,6 +3296,9 @@ class DeadlineFS : public FileSystemWrapper {
|
||||
ignore_deadline_(false),
|
||||
error_on_delay_(error_on_delay) {}
|
||||
|
||||
static const char* kClassName() { return "DeadlineFileSystem"; }
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
IOStatus NewRandomAccessFile(const std::string& fname,
|
||||
const FileOptions& opts,
|
||||
std::unique_ptr<FSRandomAccessFile>* result,
|
||||
|
||||
+559
-38
@@ -7,15 +7,20 @@
|
||||
// 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 <cstdlib>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "cache/cache_entry_roles.h"
|
||||
#include "cache/lru_cache.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/defer.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_fs.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -38,7 +43,7 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
const size_t kValueSize = 100;
|
||||
|
||||
DBBlockCacheTest()
|
||||
: DBTestBase("/db_block_cache_test", /*env_do_fsync=*/true) {}
|
||||
: DBTestBase("db_block_cache_test", /*env_do_fsync=*/true) {}
|
||||
|
||||
BlockBasedTableOptions GetTableOptions() {
|
||||
BlockBasedTableOptions table_options;
|
||||
@@ -152,12 +157,15 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const std::array<size_t, kNumCacheEntryRoles>& GetCacheEntryRoleCounts() {
|
||||
const std::array<size_t, kNumCacheEntryRoles> GetCacheEntryRoleCountsBg() {
|
||||
// Verify in cache entry role stats
|
||||
ColumnFamilyHandleImpl* cfh =
|
||||
static_cast<ColumnFamilyHandleImpl*>(dbfull()->DefaultColumnFamily());
|
||||
InternalStats* internal_stats_ptr = cfh->cfd()->internal_stats();
|
||||
return internal_stats_ptr->TEST_GetCacheEntryRoleStats().entry_counts;
|
||||
InternalStats::CacheEntryRoleStats stats;
|
||||
internal_stats_ptr->TEST_GetCacheEntryRoleStats(&stats,
|
||||
/*foreground=*/false);
|
||||
return stats.entry_counts;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
@@ -169,7 +177,13 @@ TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) {
|
||||
auto options = GetOptions(table_options);
|
||||
InitTable(options);
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 0;
|
||||
co.num_shard_bits = 0;
|
||||
co.strict_capacity_limit = false;
|
||||
// Needed not to count entry stats collector
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
table_options.block_cache = cache;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
@@ -193,7 +207,13 @@ TEST_F(DBBlockCacheTest, TestWithoutCompressedBlockCache) {
|
||||
auto options = GetOptions(table_options);
|
||||
InitTable(options);
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 0;
|
||||
co.num_shard_bits = 0;
|
||||
co.strict_capacity_limit = false;
|
||||
// Needed not to count entry stats collector
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
table_options.block_cache = cache;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
@@ -241,34 +261,54 @@ 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);
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(0, 0, false);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.block_cache_compressed = nullptr;
|
||||
table_options.block_size = 1;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(20));
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.compression = CompressionType::kSnappyCompression;
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
for (size_t i = 0; i < kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(ToString(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ReadOptions read_options;
|
||||
std::shared_ptr<Cache> compressed_cache = NewLRUCache(1 << 25, 0, false);
|
||||
LRUCacheOptions co;
|
||||
co.capacity = 0;
|
||||
co.num_shard_bits = 0;
|
||||
co.strict_capacity_limit = false;
|
||||
// Needed not to count entry stats collector
|
||||
co.metadata_charge_policy = kDontChargeCacheMetadata;
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(co);
|
||||
table_options.block_cache = cache;
|
||||
table_options.no_block_cache = false;
|
||||
table_options.block_cache_compressed = compressed_cache;
|
||||
table_options.max_auto_readahead_size = 0;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
RecordCacheCounters(options);
|
||||
|
||||
std::vector<std::unique_ptr<Iterator>> iterators(kNumBlocks - 1);
|
||||
Iterator* iter = nullptr;
|
||||
|
||||
// Load blocks into cache.
|
||||
for (size_t i = 0; i + 1 < kNumBlocks; i++) {
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(i));
|
||||
ASSERT_OK(iter->status());
|
||||
for (size_t i = 0; i < kNumBlocks - 1; i++) {
|
||||
ASSERT_EQ(value, Get(ToString(i)));
|
||||
CheckCacheCounters(options, 1, 0, 1, 0);
|
||||
CheckCompressedCacheCounters(options, 1, 0, 1, 0);
|
||||
iterators[i].reset(iter);
|
||||
}
|
||||
|
||||
size_t usage = cache->GetUsage();
|
||||
ASSERT_LT(0, usage);
|
||||
ASSERT_EQ(0, usage);
|
||||
ASSERT_EQ(usage, cache->GetPinnedUsage());
|
||||
size_t compressed_usage = compressed_cache->GetUsage();
|
||||
ASSERT_LT(0, compressed_usage);
|
||||
@@ -280,24 +320,21 @@ TEST_F(DBBlockCacheTest, TestWithCompressedBlockCache) {
|
||||
cache->SetCapacity(usage);
|
||||
cache->SetStrictCapacityLimit(true);
|
||||
ASSERT_EQ(usage, cache->GetPinnedUsage());
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(kNumBlocks - 1));
|
||||
ASSERT_TRUE(iter->status().IsIncomplete());
|
||||
|
||||
// Load last key block.
|
||||
ASSERT_EQ("Result incomplete: Insert failed due to LRU cache being full.",
|
||||
Get(ToString(kNumBlocks - 1)));
|
||||
// Failure will also record the miss counter.
|
||||
CheckCacheCounters(options, 1, 0, 0, 1);
|
||||
CheckCompressedCacheCounters(options, 1, 0, 1, 0);
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
|
||||
// Clear strict capacity limit flag. This time we shall hit compressed block
|
||||
// cache.
|
||||
// cache and load into block cache.
|
||||
cache->SetStrictCapacityLimit(false);
|
||||
iter = db_->NewIterator(read_options);
|
||||
iter->Seek(ToString(kNumBlocks - 1));
|
||||
ASSERT_OK(iter->status());
|
||||
// Load last key block.
|
||||
ASSERT_EQ(value, Get(ToString(kNumBlocks - 1)));
|
||||
CheckCacheCounters(options, 1, 0, 1, 0);
|
||||
CheckCompressedCacheCounters(options, 0, 1, 0, 0);
|
||||
delete iter;
|
||||
iter = nullptr;
|
||||
}
|
||||
#endif // SNAPPY
|
||||
|
||||
@@ -445,6 +482,136 @@ TEST_F(DBBlockCacheTest, IndexAndFilterBlocksStats) {
|
||||
// filter_bytes_insert);
|
||||
}
|
||||
|
||||
#if (defined OS_LINUX || defined OS_WIN)
|
||||
TEST_F(DBBlockCacheTest, WarmCacheWithDataBlocksDuringFlush) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewLRUCache(1 << 25, 0, false);
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
table_options.prepopulate_block_cache =
|
||||
BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
for (size_t i = 1; i <= kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(ToString(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(value, Get(ToString(i)));
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
// Verify compaction not counted
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
EXPECT_EQ(kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
}
|
||||
|
||||
// This test cache data, index and filter blocks during flush.
|
||||
TEST_F(DBBlockCacheTest, WarmCacheWithBlocksDuringFlush) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewLRUCache(1 << 25, 0, false);
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.prepopulate_block_cache =
|
||||
BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
for (size_t i = 1; i <= kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(ToString(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(ToString(i)));
|
||||
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(i, options.statistics->getTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_MISS));
|
||||
ASSERT_EQ(i * 3, options.statistics->getTickerCount(BLOCK_CACHE_INDEX_HIT));
|
||||
|
||||
ASSERT_EQ(0, options.statistics->getTickerCount(BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(i * 2,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_HIT));
|
||||
}
|
||||
// Verify compaction not counted
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
EXPECT_EQ(kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
// Index and filter blocks are automatically warmed when the new table file
|
||||
// is automatically opened at the end of compaction. This is not easily
|
||||
// disabled so results in the new index and filter blocks being warmed.
|
||||
EXPECT_EQ(1 + kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
EXPECT_EQ(1 + kNumBlocks,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, DynamicallyWarmCacheDuringFlush) {
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = NewLRUCache(1 << 25, 0, false);
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
table_options.prepopulate_block_cache =
|
||||
BlockBasedTableOptions::PrepopulateBlockCache::kFlushOnly;
|
||||
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
std::string value(kValueSize, 'a');
|
||||
|
||||
for (size_t i = 1; i <= 5; i++) {
|
||||
ASSERT_OK(Put(ToString(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(ToString(i)));
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(
|
||||
0, options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions(
|
||||
{{"block_based_table_factory", "{prepopulate_block_cache=kDisable;}"}}));
|
||||
|
||||
for (size_t i = 6; i <= kNumBlocks; i++) {
|
||||
ASSERT_OK(Put(ToString(i), value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
|
||||
ASSERT_EQ(value, Get(ToString(i)));
|
||||
ASSERT_EQ(1,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(
|
||||
1, options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_MISS));
|
||||
ASSERT_EQ(0,
|
||||
options.statistics->getAndResetTickerCount(BLOCK_CACHE_DATA_HIT));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// A mock cache wraps LRUCache, and record how many entries have been
|
||||
@@ -905,10 +1072,15 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
}
|
||||
|
||||
static void ClearCache(Cache* cache) {
|
||||
auto roles = CopyCacheDeleterRoleMap();
|
||||
std::deque<std::string> keys;
|
||||
Cache::ApplyToAllEntriesOptions opts;
|
||||
auto callback = [&](const Slice& key, void* /*value*/, size_t /*charge*/,
|
||||
Cache::DeleterFn /*deleter*/) {
|
||||
Cache::DeleterFn deleter) {
|
||||
if (roles.find(deleter) == roles.end()) {
|
||||
// Keep the stats collector
|
||||
return;
|
||||
}
|
||||
keys.push_back(key.ToString());
|
||||
};
|
||||
cache->ApplyToAllEntries(callback, opts);
|
||||
@@ -930,11 +1102,13 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
++iterations_tested;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
SetTimeElapseOnlySleepOnReopen(&options);
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.stats_dump_period_sec = 0;
|
||||
options.max_open_files = 13;
|
||||
options.table_cache_numshardbits = 0;
|
||||
// If this wakes up, it could interfere with test
|
||||
options.stats_dump_period_sec = 0;
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
@@ -970,7 +1144,9 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
std::array<size_t, kNumCacheEntryRoles> expected{};
|
||||
// For CacheEntryStatsCollector
|
||||
expected[static_cast<size_t>(CacheEntryRole::kMisc)] = 1;
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCounts());
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCountsBg());
|
||||
|
||||
std::array<size_t, kNumCacheEntryRoles> prev_expected = expected;
|
||||
|
||||
// First access only filters
|
||||
ASSERT_EQ("NOT_FOUND", Get("different from any key added"));
|
||||
@@ -978,7 +1154,14 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
if (partition) {
|
||||
expected[static_cast<size_t>(CacheEntryRole::kFilterMetaBlock)] += 2;
|
||||
}
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCounts());
|
||||
// Within some time window, we will get cached entry stats
|
||||
EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg());
|
||||
// Not enough to force a miss
|
||||
env_->MockSleepForSeconds(45);
|
||||
EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg());
|
||||
// Enough to force a miss
|
||||
env_->MockSleepForSeconds(601);
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCountsBg());
|
||||
|
||||
// Now access index and data block
|
||||
ASSERT_EQ("value", Get("foo"));
|
||||
@@ -988,7 +1171,22 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
expected[static_cast<size_t>(CacheEntryRole::kIndexBlock)]++;
|
||||
}
|
||||
expected[static_cast<size_t>(CacheEntryRole::kDataBlock)]++;
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCounts());
|
||||
// Enough to force a miss
|
||||
env_->MockSleepForSeconds(601);
|
||||
// But inject a simulated long scan so that we need a longer
|
||||
// interval to force a miss next time.
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CacheEntryStatsCollector::GetStats:AfterApplyToAllEntries",
|
||||
[this](void*) {
|
||||
// To spend no more than 0.2% of time scanning, we would need
|
||||
// interval of at least 10000s
|
||||
env_->MockSleepForSeconds(20);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCountsBg());
|
||||
prev_expected = expected;
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
// The same for other file
|
||||
ASSERT_EQ("value", Get("zfoo"));
|
||||
@@ -998,7 +1196,14 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
expected[static_cast<size_t>(CacheEntryRole::kIndexBlock)]++;
|
||||
}
|
||||
expected[static_cast<size_t>(CacheEntryRole::kDataBlock)]++;
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCounts());
|
||||
// Because of the simulated long scan, this is not enough to force
|
||||
// a miss
|
||||
env_->MockSleepForSeconds(601);
|
||||
EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg());
|
||||
// But this is enough
|
||||
env_->MockSleepForSeconds(10000);
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCountsBg());
|
||||
prev_expected = expected;
|
||||
|
||||
// Also check the GetProperty interface
|
||||
std::map<std::string, std::string> values;
|
||||
@@ -1014,8 +1219,99 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
EXPECT_EQ(
|
||||
ToString(expected[static_cast<size_t>(CacheEntryRole::kFilterBlock)]),
|
||||
values["count.filter-block"]);
|
||||
EXPECT_EQ(
|
||||
ToString(
|
||||
prev_expected[static_cast<size_t>(CacheEntryRole::kWriteBuffer)]),
|
||||
values["count.write-buffer"]);
|
||||
EXPECT_EQ(ToString(expected[static_cast<size_t>(CacheEntryRole::kMisc)]),
|
||||
values["count.misc"]);
|
||||
|
||||
// Add one for kWriteBuffer
|
||||
{
|
||||
WriteBufferManager wbm(size_t{1} << 20, cache);
|
||||
wbm.ReserveMem(1024);
|
||||
expected[static_cast<size_t>(CacheEntryRole::kWriteBuffer)]++;
|
||||
// Now we check that the GetProperty interface is more agressive about
|
||||
// re-scanning stats, but not totally aggressive.
|
||||
// Within some time window, we will get cached entry stats
|
||||
env_->MockSleepForSeconds(1);
|
||||
EXPECT_EQ(ToString(prev_expected[static_cast<size_t>(
|
||||
CacheEntryRole::kWriteBuffer)]),
|
||||
values["count.write-buffer"]);
|
||||
// Not enough for a "background" miss but enough for a "foreground" miss
|
||||
env_->MockSleepForSeconds(45);
|
||||
|
||||
ASSERT_TRUE(db_->GetMapProperty(DB::Properties::kBlockCacheEntryStats,
|
||||
&values));
|
||||
EXPECT_EQ(
|
||||
ToString(
|
||||
expected[static_cast<size_t>(CacheEntryRole::kWriteBuffer)]),
|
||||
values["count.write-buffer"]);
|
||||
}
|
||||
prev_expected = expected;
|
||||
|
||||
// With collector pinned in cache, we should be able to hit
|
||||
// even if the cache is full
|
||||
ClearCache(cache.get());
|
||||
Cache::Handle* h = nullptr;
|
||||
ASSERT_OK(cache->Insert("Fill-it-up", nullptr, capacity + 1,
|
||||
GetNoopDeleterForRole<CacheEntryRole::kMisc>(),
|
||||
&h, Cache::Priority::HIGH));
|
||||
ASSERT_GT(cache->GetUsage(), cache->GetCapacity());
|
||||
expected = {};
|
||||
// For CacheEntryStatsCollector
|
||||
expected[static_cast<size_t>(CacheEntryRole::kMisc)] = 1;
|
||||
// For Fill-it-up
|
||||
expected[static_cast<size_t>(CacheEntryRole::kMisc)]++;
|
||||
// Still able to hit on saved stats
|
||||
EXPECT_EQ(prev_expected, GetCacheEntryRoleCountsBg());
|
||||
// Enough to force a miss
|
||||
env_->MockSleepForSeconds(1000);
|
||||
EXPECT_EQ(expected, GetCacheEntryRoleCountsBg());
|
||||
|
||||
cache->Release(h);
|
||||
|
||||
// Now we test that the DB mutex is not held during scans, for the ways
|
||||
// we know how to (possibly) trigger them. Without a better good way to
|
||||
// check this, we simply inject an acquire & release of the DB mutex
|
||||
// deep in the stat collection code. If we were already holding the
|
||||
// mutex, that is UB that would at least be found by TSAN.
|
||||
int scan_count = 0;
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"CacheEntryStatsCollector::GetStats:AfterApplyToAllEntries",
|
||||
[this, &scan_count](void*) {
|
||||
dbfull()->TEST_LockMutex();
|
||||
dbfull()->TEST_UnlockMutex();
|
||||
++scan_count;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
// Different things that might trigger a scan, with mock sleeps to
|
||||
// force a miss.
|
||||
env_->MockSleepForSeconds(10000);
|
||||
dbfull()->DumpStats();
|
||||
ASSERT_EQ(scan_count, 1);
|
||||
|
||||
env_->MockSleepForSeconds(10000);
|
||||
ASSERT_TRUE(
|
||||
db_->GetMapProperty(DB::Properties::kBlockCacheEntryStats, &values));
|
||||
ASSERT_EQ(scan_count, 2);
|
||||
|
||||
env_->MockSleepForSeconds(10000);
|
||||
std::string value_str;
|
||||
ASSERT_TRUE(
|
||||
db_->GetProperty(DB::Properties::kBlockCacheEntryStats, &value_str));
|
||||
ASSERT_EQ(scan_count, 3);
|
||||
|
||||
env_->MockSleepForSeconds(10000);
|
||||
ASSERT_TRUE(db_->GetProperty(DB::Properties::kCFStats, &value_str));
|
||||
// To match historical speed, querying this property no longer triggers
|
||||
// a scan, even if results are old. But periodic dump stats should keep
|
||||
// things reasonably updated.
|
||||
ASSERT_EQ(scan_count, /*unchanged*/ 3);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
EXPECT_GE(iterations_tested, 1);
|
||||
}
|
||||
@@ -1023,13 +1319,238 @@ TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
class DBBlockCacheKeyTest
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<std::tuple<bool, bool>> {
|
||||
public:
|
||||
DBBlockCacheKeyTest()
|
||||
: DBTestBase("db_block_cache_test", /*env_do_fsync=*/false) {}
|
||||
|
||||
void SetUp() override {
|
||||
use_compressed_cache_ = std::get<0>(GetParam());
|
||||
exclude_file_numbers_ = std::get<1>(GetParam());
|
||||
}
|
||||
|
||||
bool use_compressed_cache_;
|
||||
bool exclude_file_numbers_;
|
||||
};
|
||||
|
||||
// Disable LinkFile so that we can physically copy a DB using Checkpoint.
|
||||
// Disable file GetUniqueId to enable stable cache keys.
|
||||
class StableCacheKeyTestFS : public FaultInjectionTestFS {
|
||||
public:
|
||||
explicit StableCacheKeyTestFS(const std::shared_ptr<FileSystem>& base)
|
||||
: FaultInjectionTestFS(base) {
|
||||
SetFailGetUniqueId(true);
|
||||
}
|
||||
|
||||
virtual ~StableCacheKeyTestFS() override {}
|
||||
|
||||
IOStatus LinkFile(const std::string&, const std::string&, const IOOptions&,
|
||||
IODebugContext*) override {
|
||||
return IOStatus::NotSupported("Disabled");
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(DBBlockCacheKeyTest, StableCacheKeys) {
|
||||
std::shared_ptr<StableCacheKeyTestFS> test_fs{
|
||||
new StableCacheKeyTestFS(env_->GetFileSystem())};
|
||||
std::unique_ptr<CompositeEnvWrapper> test_env{
|
||||
new CompositeEnvWrapper(env_, test_fs)};
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.env = test_env.get();
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
|
||||
int key_count = 0;
|
||||
uint64_t expected_stat = 0;
|
||||
|
||||
std::function<void()> verify_stats;
|
||||
if (use_compressed_cache_) {
|
||||
if (!Snappy_Supported()) {
|
||||
ROCKSDB_GTEST_SKIP("Compressed cache test requires snappy support");
|
||||
return;
|
||||
}
|
||||
options.compression = CompressionType::kSnappyCompression;
|
||||
table_options.no_block_cache = true;
|
||||
table_options.block_cache_compressed = NewLRUCache(1 << 25, 0, false);
|
||||
verify_stats = [&options, &expected_stat] {
|
||||
// One for ordinary SST file and one for external SST file
|
||||
ASSERT_EQ(expected_stat,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_COMPRESSED_ADD));
|
||||
};
|
||||
} else {
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.block_cache = NewLRUCache(1 << 25, 0, false);
|
||||
verify_stats = [&options, &expected_stat] {
|
||||
ASSERT_EQ(expected_stat,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_DATA_ADD));
|
||||
ASSERT_EQ(expected_stat,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_INDEX_ADD));
|
||||
ASSERT_EQ(expected_stat,
|
||||
options.statistics->getTickerCount(BLOCK_CACHE_FILTER_ADD));
|
||||
};
|
||||
}
|
||||
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"koko"}, options);
|
||||
|
||||
if (exclude_file_numbers_) {
|
||||
// Simulate something like old behavior without file numbers in properties.
|
||||
// This is a "control" side of the test that also ensures safely degraded
|
||||
// behavior on old files.
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
|
||||
"PropertyBlockBuilder::AddTableProperty:Start", [&](void* arg) {
|
||||
TableProperties* props = reinterpret_cast<TableProperties*>(arg);
|
||||
props->orig_file_number = 0;
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
}
|
||||
|
||||
std::function<void()> perform_gets = [&key_count, &expected_stat, this]() {
|
||||
if (exclude_file_numbers_) {
|
||||
// No cache key reuse should happen, because we can't rely on current
|
||||
// file number being stable
|
||||
expected_stat += key_count;
|
||||
} else {
|
||||
// Cache keys should be stable
|
||||
expected_stat = key_count;
|
||||
}
|
||||
for (int i = 0; i < key_count; ++i) {
|
||||
ASSERT_EQ(Get(1, Key(i)), "abc");
|
||||
}
|
||||
};
|
||||
|
||||
// Ordinary SST files with same session id
|
||||
const std::string something_compressible(500U, 'x');
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
ASSERT_OK(Put(1, Key(key_count), "abc"));
|
||||
ASSERT_OK(Put(1, Key(key_count) + "a", something_compressible));
|
||||
ASSERT_OK(Flush(1));
|
||||
++key_count;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Save an export of those ordinary SST files for later
|
||||
std::string export_files_dir = dbname_ + "/exported";
|
||||
ExportImportFilesMetaData* metadata_ptr_ = nullptr;
|
||||
Checkpoint* checkpoint;
|
||||
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
|
||||
ASSERT_OK(checkpoint->ExportColumnFamily(handles_[1], export_files_dir,
|
||||
&metadata_ptr_));
|
||||
ASSERT_NE(metadata_ptr_, nullptr);
|
||||
delete checkpoint;
|
||||
checkpoint = nullptr;
|
||||
|
||||
// External SST files with same session id
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options);
|
||||
std::vector<std::string> external;
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
std::string f = dbname_ + "/external" + ToString(i) + ".sst";
|
||||
external.push_back(f);
|
||||
ASSERT_OK(sst_file_writer.Open(f));
|
||||
ASSERT_OK(sst_file_writer.Put(Key(key_count), "abc"));
|
||||
ASSERT_OK(
|
||||
sst_file_writer.Put(Key(key_count) + "a", something_compressible));
|
||||
++key_count;
|
||||
ExternalSstFileInfo external_info;
|
||||
ASSERT_OK(sst_file_writer.Finish(&external_info));
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[1], {f}, ingest_opts));
|
||||
}
|
||||
|
||||
if (exclude_file_numbers_) {
|
||||
// FIXME(peterd): figure out where these extra two ADDs are coming from
|
||||
options.statistics->recordTick(BLOCK_CACHE_INDEX_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
options.statistics->recordTick(BLOCK_CACHE_FILTER_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
options.statistics->recordTick(BLOCK_CACHE_COMPRESSED_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
}
|
||||
#endif
|
||||
|
||||
perform_gets();
|
||||
verify_stats();
|
||||
|
||||
// Make sure we can cache hit after re-open
|
||||
ReopenWithColumnFamilies({"default", "koko"}, options);
|
||||
|
||||
perform_gets();
|
||||
verify_stats();
|
||||
|
||||
// Make sure we can cache hit even on a full copy of the DB. Using
|
||||
// StableCacheKeyTestFS, Checkpoint will resort to full copy not hard link.
|
||||
// (Checkpoint not available in LITE mode to test this.)
|
||||
#ifndef ROCKSDB_LITE
|
||||
auto db_copy_name = dbname_ + "-copy";
|
||||
ASSERT_OK(Checkpoint::Create(db_, &checkpoint));
|
||||
ASSERT_OK(checkpoint->CreateCheckpoint(db_copy_name));
|
||||
delete checkpoint;
|
||||
|
||||
Close();
|
||||
Destroy(options);
|
||||
|
||||
// Switch to the DB copy
|
||||
SaveAndRestore<std::string> save_dbname(&dbname_, db_copy_name);
|
||||
ReopenWithColumnFamilies({"default", "koko"}, options);
|
||||
|
||||
perform_gets();
|
||||
verify_stats();
|
||||
|
||||
// And ensure that re-importing + ingesting the same files into a
|
||||
// different DB uses same cache keys
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ColumnFamilyHandle* cfh = nullptr;
|
||||
ASSERT_OK(db_->CreateColumnFamilyWithImport(ColumnFamilyOptions(), "yoyo",
|
||||
ImportColumnFamilyOptions(),
|
||||
*metadata_ptr_, &cfh));
|
||||
ASSERT_NE(cfh, nullptr);
|
||||
delete cfh;
|
||||
cfh = nullptr;
|
||||
delete metadata_ptr_;
|
||||
metadata_ptr_ = nullptr;
|
||||
|
||||
DestroyDB(export_files_dir, options);
|
||||
|
||||
ReopenWithColumnFamilies({"default", "yoyo"}, options);
|
||||
|
||||
IngestExternalFileOptions ingest_opts;
|
||||
ASSERT_OK(db_->IngestExternalFile(handles_[1], {external}, ingest_opts));
|
||||
|
||||
if (exclude_file_numbers_) {
|
||||
// FIXME(peterd): figure out where these extra two ADDs are coming from
|
||||
options.statistics->recordTick(BLOCK_CACHE_INDEX_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
options.statistics->recordTick(BLOCK_CACHE_FILTER_ADD,
|
||||
uint64_t{0} - uint64_t{2});
|
||||
}
|
||||
|
||||
perform_gets();
|
||||
verify_stats();
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
Destroy(options);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlockCacheKeyTest, DBBlockCacheKeyTest,
|
||||
::testing::Combine(::testing::Bool(),
|
||||
::testing::Bool()));
|
||||
|
||||
class DBBlockCachePinningTest
|
||||
: public DBTestBase,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<bool, PinningTier, PinningTier, PinningTier>> {
|
||||
public:
|
||||
DBBlockCachePinningTest()
|
||||
: DBTestBase("/db_block_cache_test", /*env_do_fsync=*/false) {}
|
||||
: DBTestBase("db_block_cache_test", /*env_do_fsync=*/false) {}
|
||||
|
||||
void SetUp() override {
|
||||
partition_index_and_filters_ = std::get<0>(GetParam());
|
||||
|
||||
+22
-22
@@ -15,6 +15,7 @@
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "table/block_based/filter_policy_internal.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -28,7 +29,7 @@ using BFP = BloomFilterPolicy;
|
||||
class DBBloomFilterTest : public DBTestBase {
|
||||
public:
|
||||
DBBloomFilterTest()
|
||||
: DBTestBase("/db_bloom_filter_test", /*env_do_fsync=*/true) {}
|
||||
: DBTestBase("db_bloom_filter_test", /*env_do_fsync=*/true) {}
|
||||
};
|
||||
|
||||
class DBBloomFilterTestWithParam : public DBTestBase,
|
||||
@@ -42,7 +43,7 @@ class DBBloomFilterTestWithParam : public DBTestBase,
|
||||
|
||||
public:
|
||||
DBBloomFilterTestWithParam()
|
||||
: DBTestBase("/db_bloom_filter_tests", /*env_do_fsync=*/true) {}
|
||||
: DBTestBase("db_bloom_filter_tests", /*env_do_fsync=*/true) {}
|
||||
|
||||
~DBBloomFilterTestWithParam() override {}
|
||||
|
||||
@@ -527,7 +528,7 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) {
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_VALGRIND_RUN
|
||||
#if !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, DBBloomFilterTestDefFormatVersion,
|
||||
::testing::Values(
|
||||
@@ -551,7 +552,7 @@ INSTANTIATE_TEST_CASE_P(
|
||||
test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kLatestFormatVersion)));
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
#endif // !defined(ROCKSDB_VALGRIND_RUN) || defined(ROCKSDB_FULL_VALGRIND_RUN)
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
while (ChangeFilterOptions()) {
|
||||
@@ -1094,7 +1095,7 @@ class DBBloomFilterTestVaryPrefixAndFormatVer
|
||||
|
||||
public:
|
||||
DBBloomFilterTestVaryPrefixAndFormatVer()
|
||||
: DBTestBase("/db_bloom_filter_tests", /*env_do_fsync=*/true) {}
|
||||
: DBTestBase("db_bloom_filter_tests", /*env_do_fsync=*/true) {}
|
||||
|
||||
~DBBloomFilterTestVaryPrefixAndFormatVer() override {}
|
||||
|
||||
@@ -1819,8 +1820,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:5"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.5"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.FixedPrefix.5");
|
||||
{
|
||||
// BF changed, [abcdxx00, abce) is a valid bound, will trigger BF read
|
||||
Slice upper_bound("abce");
|
||||
@@ -1939,8 +1940,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_CHECKED), 1);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.CappedPrefix.3");
|
||||
read_options.iterate_upper_bound = &upper_bound;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter, "foo"), 2);
|
||||
@@ -1973,8 +1974,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
}
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.2"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.FixedPrefix.2");
|
||||
// third SST with fixed:2 BF
|
||||
ASSERT_OK(Put("foo6", "bar6"));
|
||||
ASSERT_OK(Put("foo7", "bar7"));
|
||||
@@ -2021,8 +2022,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 3);
|
||||
}
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.CappedPrefix.3");
|
||||
{
|
||||
std::unique_ptr<Iterator> iter_all(db_->NewIterator(read_options));
|
||||
ASSERT_EQ(CountIter(iter_all, "foo"), 6);
|
||||
@@ -2062,9 +2063,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
// create a new CF and set prefix_extractor dynamically
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(3));
|
||||
CreateColumnFamilies({"ramen_dojo_" + std::to_string(iteration)}, options);
|
||||
ASSERT_EQ(0,
|
||||
strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_EQ(dbfull()->GetOptions(handles_[2]).prefix_extractor->AsString(),
|
||||
"rocksdb.CappedPrefix.3");
|
||||
ASSERT_OK(Put(2, "foo3", "bar3"));
|
||||
ASSERT_OK(Put(2, "foo4", "bar4"));
|
||||
ASSERT_OK(Put(2, "foo5", "bar5"));
|
||||
@@ -2080,9 +2080,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
}
|
||||
ASSERT_OK(
|
||||
dbfull()->SetOptions(handles_[2], {{"prefix_extractor", "fixed:2"}}));
|
||||
ASSERT_EQ(0,
|
||||
strcmp(dbfull()->GetOptions(handles_[2]).prefix_extractor->Name(),
|
||||
"rocksdb.FixedPrefix.2"));
|
||||
ASSERT_EQ(dbfull()->GetOptions(handles_[2]).prefix_extractor->AsString(),
|
||||
"rocksdb.FixedPrefix.2");
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(
|
||||
db_->NewIterator(read_options, handles_[2]));
|
||||
@@ -2147,8 +2146,8 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
|
||||
ASSERT_OK(dbfull()->SetOptions({{"prefix_extractor", "capped:3"}}));
|
||||
ASSERT_EQ(0, strcmp(dbfull()->GetOptions().prefix_extractor->Name(),
|
||||
"rocksdb.CappedPrefix.3"));
|
||||
ASSERT_EQ(dbfull()->GetOptions().prefix_extractor->AsString(),
|
||||
"rocksdb.CappedPrefix.3");
|
||||
{
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
// "fp*" should be skipped
|
||||
@@ -2171,7 +2170,8 @@ TEST_F(DBBloomFilterTest, SeekForPrevWithPartitionedFilters) {
|
||||
Options options = CurrentOptions();
|
||||
constexpr size_t kNumKeys = 10000;
|
||||
static_assert(kNumKeys <= 10000, "kNumKeys have to be <= 10000");
|
||||
options.memtable_factory.reset(new SpecialSkipListFactory(kNumKeys + 10));
|
||||
options.memtable_factory.reset(
|
||||
test::NewSpecialSkipListFactory(kNumKeys + 10));
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kPrefixLength = 4;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixLength));
|
||||
|
||||
Loaded 100 of 741 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user