mirror of
https://github.com/facebook/rocksdb.git
synced 2026-07-07 22:55:23 +08:00
Compare commits
592 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e0ff19b1b | |||
| 414de699b6 | |||
| 300b2249dc | |||
| a28f13c937 | |||
| de97dddf61 | |||
| 05835f4071 | |||
| 51b540921d | |||
| e8b841eb47 | |||
| 6ee0acdbb4 | |||
| 6fc2818ef8 | |||
| 5881f4ac50 | |||
| 28aa6d4e76 | |||
| 9d7de9605a | |||
| f64f08e0bc | |||
| 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 | |||
| 55853de661 | |||
| 7303d02bdf | |||
| 6c7c3e8cb3 | |||
| 3469d60fcc | |||
| 6c86543590 | |||
| bd3d080ef8 | |||
| 2f1984dd45 | |||
| 94b4faa0f1 | |||
| f76326e370 | |||
| 3786181a90 | |||
| 311a544c2a | |||
| 748e3acc11 | |||
| 13232e11d4 | |||
| 871a2cb292 | |||
| ce0fc71adf | |||
| 9d61a0856d | |||
| 60e5af83c1 | |||
| 6b0a22a4b0 | |||
| 83d1a66598 | |||
| d83542ca83 | |||
| 0ed8cb666d | |||
| feb06e83b2 | |||
| d15fbae449 | |||
| d76c46e6a0 | |||
| a79b46c503 | |||
| e9a0bc14dd | |||
| a6e425dc44 | |||
| 78a309bf86 | |||
| 78e82410eb | |||
| 9f2d255aed | |||
| ff463742b5 | |||
| f89a53655d | |||
| a639c02f8e | |||
| 242ac6c17c | |||
| c26b75baa5 | |||
| a4919d6b62 | |||
| ecd63b9262 | |||
| b71b4597e7 | |||
| 7f3a0f5bc6 | |||
| e19908cba6 | |||
| 8948dc8524 | |||
| 0f42e50fec | |||
| 3b981eaa1d | |||
| c70bae1b05 | |||
| c2a3424de5 | |||
| 8a92564a82 | |||
| c3ff14e2c1 | |||
| d2ca04e3ed | |||
| 85becd94c1 | |||
| a0e0feca62 | |||
| 0db4cde6e2 | |||
| 8fe33a0a9f | |||
| cde69a7cfd | |||
| 3949731de3 | |||
| 65abb0cf71 | |||
| 13c655a887 | |||
| 0ca6d6297f | |||
| c2c7d5e916 | |||
| 2760c2aef8 | |||
| 69c986825e | |||
| 6bab3a34e9 | |||
| cc1c3ee54e | |||
| d65d7d657d | |||
| 01e460d538 | |||
| f0fca2b1d5 | |||
| e85d8a6517 | |||
| 09a9ec3ac0 | |||
| 47b424f4bd | |||
| 314352761f | |||
| 4985cea141 | |||
| 596e9008e4 | |||
| 95f6add746 | |||
| 2e5de5a2c3 | |||
| 905dd17b35 | |||
| 1b99947e99 | |||
| a89740fbc6 | |||
| a345b4d60d | |||
| a376c22066 | |||
| 0c6e4674a6 | |||
| b0e20194ea | |||
| c377c2ba15 | |||
| 531a5f88a1 | |||
| 10196d7edc | |||
| 90e245697f | |||
| c871142988 | |||
| 4c41e51c07 | |||
| 00803d619c | |||
| 83031e7343 | |||
| 296b47db25 | |||
| b1f62be10e | |||
| d89483098f | |||
| c861fb390d | |||
| fd00f39f97 | |||
| bb75092574 | |||
| 8972dd1ffa | |||
| 139778dfb3 | |||
| 728e5f5750 | |||
| 48cd7a3aae | |||
| 0be89e87fd | |||
| d52b520d51 | |||
| a4e82a3cca | |||
| 35af0433cf | |||
| 6db3af1124 | |||
| 879357fdb0 | |||
| 09528f9fa1 | |||
| c4d0e66d65 | |||
| 2d8518f5ea | |||
| ffd3f493e3 | |||
| b2c48a570f | |||
| 88c8f7a090 | |||
| 96205baa63 | |||
| bd7ddf58cb | |||
| d0d2ab0b1a | |||
| dd3fbbbf95 | |||
| 0fccc6225e | |||
| 689b13e639 | |||
| 5025c7ec09 | |||
| 17002365c1 | |||
| c43a37a922 | |||
| a3a943bf63 | |||
| 1ba2b8a568 | |||
| a781b103da | |||
| 493a4e28d9 | |||
| 335c5a6be5 | |||
| e6534900bd | |||
| 9418403c4b | |||
| f03606cd5c | |||
| ec11c23caa | |||
| 04191e1c5d | |||
| a037bb35e9 | |||
| 24b7ebee80 | |||
| 25ae380784 | |||
| 1be3867689 | |||
| 70e80c91b6 | |||
| 524b10bd6e | |||
| ae7a795686 | |||
| 5841bbe36c | |||
| 7d7f14480e | |||
| 0a5d23944d | |||
| ce6de862c1 | |||
| 63748c2204 | |||
| a34dafe5ff | |||
| c5f52714fb | |||
| 7f27767efa | |||
| 06bb45a65a | |||
| 303cb23a0f | |||
| af80a78ba4 | |||
| 711881bc25 | |||
| c20a7cd6c7 | |||
| 45c65d6dcf | |||
| f06b761185 | |||
| 469164dc3c | |||
| da6b90ab48 | |||
| 41e554da2b | |||
| 8dc6d8c748 | |||
| 9f7c02dad5 | |||
| e1aa8c160f | |||
| 4a6bc47b2e | |||
| 2a12b80769 | |||
| 08144bc2f5 | |||
| 0d800dadea | |||
| d6052d381e | |||
| 7457c7cd00 | |||
| 3bfd3ed2f3 | |||
| c8b0842bcd | |||
| d9be6556aa | |||
| c706324208 | |||
| dd0447ae2c | |||
| 7ee41a5d25 | |||
| 576cff11da | |||
| 063a68b9cd | |||
| c810947184 | |||
| e7a60d01b2 | |||
| 08ec5e7321 | |||
| 27d57a035e | |||
| c603f2f898 | |||
| 326670d265 | |||
| 8d9088464b | |||
| 1a343bc393 | |||
| 0304352882 | |||
| 85d4f2c8b3 | |||
| 3dff28cf9b | |||
| b8f40f7f7b | |||
| b708b166dc | |||
| 01c2ec3fcb | |||
| 119dda2195 | |||
| 589ea6bec2 | |||
| 82b3888433 | |||
| 64517d184a | |||
| 7a3444bf1f | |||
| cc34da75b5 | |||
| 6fad38ebe8 | |||
| 670567db09 | |||
| 7381dad1b1 | |||
| 4b18c46d10 | |||
| 847ca9f964 | |||
| 0028e3398b | |||
| 430842f948 | |||
| 8643d63bb4 | |||
| 67d72fb5dc | |||
| d2e9eab1ea | |||
| 02974c9437 | |||
| ef392fb04e | |||
| 0ff0b625a1 | |||
| ce391ff84b | |||
| cb25bc1128 | |||
| 4126bdc0e1 | |||
| 72d1e258cd | |||
| a9046f3c45 | |||
| a46f080cce | |||
| f19612970d | |||
| 1f11d07f24 | |||
| 9fdc9fbeea | |||
| a8b3b9a20c | |||
| c370d8aa12 | |||
| cef4a6c49f | |||
| 2772eb7735 | |||
| b085ee13e0 | |||
| cd79a00903 | |||
| e017af15c1 | |||
| 75c6ffb9de | |||
| f91fd0c944 | |||
| 46cf5fbfdd | |||
| daca92c17a | |||
| 59d91796d2 | |||
| 6790a983eb | |||
| 7343eb4a74 | |||
| d904233d2f | |||
| cf14cb3e29 | |||
| ec76f03168 | |||
| 4bc9df9459 | |||
| b0fd1cc45a | |||
| c9878baa87 | |||
| ad25b1afb9 | |||
| 69877ac4f2 | |||
| 251143f8fb | |||
| 0c2d71edba | |||
| 8a05c21e32 | |||
| eacb14a10a | |||
| 59ba104e4a | |||
| 6a85aea5b1 | |||
| dab4fe5bcd | |||
| 0743eba0c4 | |||
| ba8008c870 | |||
| ea8bb82fc7 | |||
| 9df78a94f1 | |||
| 00519187a6 | |||
| d1c510baec | |||
| e4f1e64c30 | |||
| 14fbb43f3e | |||
| 7ebde3da45 | |||
| 170dffac83 | |||
| c16d5a4fda | |||
| 65487da1f5 | |||
| 48669be618 | |||
| 91e2c151d2 | |||
| cf160b98e1 | |||
| 974458891c | |||
| e3183eae77 | |||
| 8d2bbdd04f | |||
| 4fba83b4c2 | |||
| ee79a28963 | |||
| cbf38af705 | |||
| 3feee6db17 | |||
| a9f8f1fe38 | |||
| 0288bdbc53 | |||
| 108e6b6354 | |||
| 21218316da | |||
| bf6795aea0 | |||
| 87983d442c | |||
| e5311a8ea4 | |||
| 78ee8564ad | |||
| 4a09d632c4 | |||
| 0a9a05ae12 | |||
| c696f27432 | |||
| 95013df278 | |||
| 19210d5715 | |||
| 8944a3bf27 | |||
| 36963dc2ca | |||
| c6ff4c0b70 | |||
| 4ee991b1e6 | |||
| 9425acacce | |||
| f9a30e0a5a | |||
| 12f1137355 | |||
| 1d226018af | |||
| 431e8afba7 | |||
| 19076c95aa | |||
| 12a8be1d44 | |||
| e18a4df62a | |||
| d5f5d6579a | |||
| 1a6438cff5 | |||
| 2d37830e44 | |||
| b0c43e7081 | |||
| cdd8b09b9b | |||
| e44948295e | |||
| 928dea0e32 | |||
| 4aa1a19db8 | |||
| 4db58bcfb2 | |||
| 5b748b9e68 | |||
| acc9679cda | |||
| ffe4906192 | |||
| d76a8eeef7 | |||
| 1001bc01c9 | |||
| 2fb6d9337f | |||
| ac956f2bea | |||
| 7189ea8fb7 | |||
| 3e6ee9f82e | |||
| 0426d4a4ee | |||
| 8e7b068ecc | |||
| 77b4bfe511 | |||
| fdbebdf484 | |||
| e4bdf9e0cb | |||
| edfcce39da | |||
| 0a9f78d2c8 | |||
| eccc47e81c | |||
| a3066ee75c | |||
| 4926b33742 | |||
| 8ed680bdb0 | |||
| b2e30bdb67 | |||
| c22e619f7e | |||
| 48c0843e69 | |||
| cc2a180d00 | |||
| 8f7b6c8339 | |||
| 6e0f62f2b6 | |||
| 5792b73fdc | |||
| e628f59e87 | |||
| c1a65a4de4 | |||
| 58660bf21a | |||
| 225abffd8f | |||
| 159ea4702c | |||
| b8c01ed38a | |||
| 0bad2b4308 | |||
| 61e324422e | |||
| bb0f781da0 | |||
| 81367a4616 | |||
| 30cd38c687 | |||
| edb0b1fb7f | |||
| fd2db79fa4 | |||
| 01298c8ff7 | |||
| d7738666b0 | |||
| 736c6dc59f | |||
| 44ebc24dca | |||
| 601585bca4 | |||
| 55e99688cc | |||
| 30a5ed9c53 | |||
| bdb7e544bd | |||
| bd2645bc34 | |||
| a727efca99 | |||
| 02418194d7 | |||
| daab7603f6 | |||
| f4db3e4119 | |||
| 81592d9ffa | |||
| 41ff125a8a | |||
| 1022090981 | |||
| 9057d0a079 | |||
| fbac1b3f80 | |||
| 4d897e51df | |||
| 861b0d1a49 | |||
| fd0d35d390 | |||
| a8aeefd0fd | |||
| 4d1ac19e3d | |||
| fbce7a3808 | |||
| 62afa968c2 | |||
| 75e4af14e0 | |||
| 239d17a19c | |||
| 04b3524ad0 | |||
| 99f5a800c3 | |||
| 29d12748b0 | |||
| 2021392e25 | |||
| a0e4421e81 | |||
| 1afbd1948c | |||
| 003e72b201 | |||
| 491779514e | |||
| b1ee191405 | |||
| 07c0fc002a | |||
| fd7d8dc56e | |||
| 40f2b65008 | |||
| 8ff6557e7f | |||
| 8e2749fd3a | |||
| 71239908cf | |||
| 80159f6e0b | |||
| 8a1488efbf | |||
| 3c2a448856 | |||
| 89cc06b3e7 | |||
| efe827baf0 | |||
| 98236fb10e | |||
| 7b2216c906 | |||
| 66e54c5984 | |||
| 93c6c18cf9 | |||
| 07030c6f4a | |||
| 11c4be2222 | |||
| d8bd9fc7b3 | |||
| db03172d08 | |||
| 8a06fe278f | |||
| ba2a3bf092 | |||
| 20c7d7c58a | |||
| b77569f18b | |||
| 1df8584896 | |||
| eee0af9af1 | |||
| e34b2e9f41 | |||
| 61932cdf1d | |||
| e102de7318 | |||
| ee4bd4780b | |||
| eb5a8c06dd | |||
| 70f2e0916a | |||
| ac2f90d6f9 | |||
| 29e8f6a698 | |||
| 3b9bfe8f14 | |||
| 7fec715db4 | |||
| e062a719cc | |||
| 9e1640403a | |||
| b937be3779 | |||
| 0b06af9146 | |||
| eb65d673fe | |||
| cc431ece37 | |||
| 51a8dc6d14 | |||
| dd6b7fc520 | |||
| 0baa5055f1 | |||
| 1a5fc4f577 | |||
| 7c19d43883 | |||
| 5c585e1908 | |||
| a65e905bbb | |||
| 7169ca9c80 | |||
| 8a97f35619 | |||
| 6cacb0d3a1 | |||
| 8c93b16f02 | |||
| 303d283420 | |||
| 4c336c6912 | |||
| 699411b2ca | |||
| 84a700819e | |||
| 869f0538dd | |||
| 9627e342c8 | |||
| 1c5f13f2a5 | |||
| 7582c5682b | |||
| 1861de455e | |||
| 5bd1258381 | |||
| 1aae41786a | |||
| 9aa1b1dc19 | |||
| e300ce211d | |||
| 60af964372 | |||
| bbbb5a280d | |||
| 76ef894f9f | |||
| bb69b4ce7f | |||
| cf9d8e45c0 | |||
| 0dc437d65c | |||
| 2400cd69e3 | |||
| ec346da98c | |||
| c442f6809f | |||
| 8b6b6aeb1a | |||
| c57f914482 | |||
| bcba372352 | |||
| 18aee7db7e | |||
| 16d103d35b | |||
| 202605143b | |||
| 9f1c84ca47 | |||
| c3911f1a72 | |||
| 5e794b0841 | |||
| 8b8a2e9f05 | |||
| 1e40696dd1 | |||
| 1ce105d0ea | |||
| cdc7ba3a32 | |||
| da42eceabc | |||
| 4c2aef04bd | |||
| 81543369e5 | |||
| 1f627210ca | |||
| c9c9709a1a | |||
| b6d8e36741 | |||
| fde0cd7ced | |||
| 06a92fcf5c | |||
| 746909ceda | |||
| 0b94468bba | |||
| 881e0dcc09 | |||
| c992eb118b | |||
| 1adbceb581 | |||
| a388c8cc6b | |||
| 30beecef8c | |||
| 6773901f76 | |||
| 7eb2824e3f | |||
| 394210f280 | |||
| 99a0305bb8 | |||
| ea347d80df | |||
| 2404f8b9ec | |||
| 248d10fb96 | |||
| 793e9b7f5b | |||
| 9a690a74e1 | |||
| 6c2c0635c9 | |||
| 0e2e67562f | |||
| f35f7f2704 | |||
| 6134ce6444 | |||
| a7a04b6898 | |||
| 25d54c799c | |||
| 6595267980 | |||
| 1b224324b5 | |||
| a16d1b2fd3 | |||
| b1cdb8cc86 | |||
| d8ec0a760a | |||
| 1c78e4b235 | |||
| eef27d0048 | |||
| 5227b315ec | |||
| 73dbe10bbf | |||
| 1eda625eab | |||
| fc9b416013 | |||
| b4cd51d847 | |||
| 00751e4292 | |||
| 0ea7db768e | |||
| ed90e2a450 | |||
| f4ade82ad2 | |||
| e8cb32ed67 | |||
| fa2a8cda7b | |||
| a8c89cc969 | |||
| ccbf468cb1 | |||
| 1a83f5a8ac | |||
| 05fba96927 | |||
| 850cc0dbed | |||
| 6528ecc800 | |||
| b99fe1ab74 | |||
| db87afbcb3 | |||
| 7b65666cf1 | |||
| c87c3a48af | |||
| 4a6840bd00 | |||
| 3dc823212d | |||
| 16bff5370d |
+397
-65
@@ -17,6 +17,13 @@ commands:
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install pyenv
|
||||
|
||||
install-cmake-on-macos:
|
||||
steps:
|
||||
- run:
|
||||
name: Install cmake on macos
|
||||
command: |
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 brew install cmake
|
||||
|
||||
increase-max-open-files-on-macos:
|
||||
steps:
|
||||
- run:
|
||||
@@ -27,10 +34,14 @@ commands:
|
||||
sudo launchctl limit maxfiles 1048576
|
||||
|
||||
pre-steps:
|
||||
parameters:
|
||||
python-version:
|
||||
default: "3.5.9"
|
||||
type: string
|
||||
steps:
|
||||
- checkout
|
||||
- run: pyenv install --skip-existing 3.5.9
|
||||
- run: pyenv global 3.5.9
|
||||
- run: pyenv install --skip-existing <<parameters.python-version>>
|
||||
- run: pyenv global <<parameters.python-version>>
|
||||
- run:
|
||||
name: Setup Environment Variables
|
||||
command: |
|
||||
@@ -38,6 +49,14 @@ 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:
|
||||
- pre-steps:
|
||||
python-version: "3.6.0"
|
||||
|
||||
post-steps:
|
||||
steps:
|
||||
@@ -46,6 +65,14 @@ commands:
|
||||
path: /tmp/test-results
|
||||
- store_artifacts: # store LOG for debugging if there's any
|
||||
path: LOG
|
||||
- run: # on fail, compress Test Logs for diagnosing the issue
|
||||
name: Compress Test Logs
|
||||
command: tar -cvzf t.tar.gz t
|
||||
when: on_fail
|
||||
- store_artifacts: # on fail, store Test Logs for diagnosing the issue
|
||||
path: t.tar.gz
|
||||
destination: test_logs
|
||||
when: on_fail
|
||||
|
||||
install-clang-10:
|
||||
steps:
|
||||
@@ -63,7 +90,15 @@ commands:
|
||||
name: Install gflags
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libgflags-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:
|
||||
@@ -77,7 +112,14 @@ commands:
|
||||
name: Install gtest-parallel
|
||||
command: |
|
||||
git clone --single-branch --branch master --depth 1 https://github.com/google/gtest-parallel.git ~/gtest-parallel
|
||||
echo "export PATH=$HOME/gtest-parallel:$PATH" >> $BASH_ENV
|
||||
echo 'export PATH=$HOME/gtest-parallel:$PATH' >> $BASH_ENV
|
||||
|
||||
install-compression-libs:
|
||||
steps:
|
||||
- run:
|
||||
name: Install compression libs
|
||||
command: |
|
||||
sudo apt-get update -y && sudo apt-get install -y libsnappy-dev zlib1g-dev libbz2-dev liblz4-dev libzstd-dev
|
||||
|
||||
executors:
|
||||
windows-2xlarge:
|
||||
@@ -90,17 +132,31 @@ jobs:
|
||||
build-macos:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
resource_class: large
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- pre-steps
|
||||
- install-gflags-on-macos
|
||||
- pre-steps-macos
|
||||
- run: ulimit -S -n 1048576 && OPT=-DCIRCLECI make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-macos-cmake:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
resource_class: large
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- 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 && 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
|
||||
@@ -108,26 +164,46 @@ jobs:
|
||||
- run: make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
build-linux-mem-env:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 LIB_MODE=shared OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j32 all check_some | .circleci/cat_ignore_eagain
|
||||
- run: MEM_ENV=1 make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-encrypted-env:
|
||||
machine:
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ENCRYPTED_ENV=1 make V=1 J=32 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
machine:
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ASSERT_STATUS_CHECKED=1 TEST_UINT128_COMPAT=1 ROCKSDB_MODIFY_NPHASH=1 LIB_MODE=shared OPT="-DROCKSDB_NAMESPACE=alternative_rocksdb_ns" make V=1 -j32 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-release:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: 2xlarge
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: make V=1 -j32 release | .circleci/cat_ignore_eagain
|
||||
- run: make V=1 -j8 release | .circleci/cat_ignore_eagain
|
||||
- run: if ./db_stress --version; then false; else true; fi # ensure without gflags
|
||||
- install-gflags
|
||||
- run: make V=1 -j32 release | .circleci/cat_ignore_eagain
|
||||
- run: make V=1 -j8 release | .circleci/cat_ignore_eagain
|
||||
- run: ./db_stress --version # ensure with gflags
|
||||
- post-steps
|
||||
|
||||
@@ -138,16 +214,16 @@ jobs:
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: make clean
|
||||
- run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j16 static_lib tools db_bench | .circleci/cat_ignore_eagain
|
||||
- run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j8 static_lib tools db_bench | .circleci/cat_ignore_eagain
|
||||
- run: if ./db_stress --version; then false; else true; fi # ensure without gflags
|
||||
- run: sudo apt-get update -y && sudo apt-get install -y libgflags-dev
|
||||
- run: make clean
|
||||
- run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j16 static_lib tools db_bench | .circleci/cat_ignore_eagain
|
||||
- run: USE_RTTI=1 DEBUG_LEVEL=0 make V=1 -j8 static_lib tools db_bench | .circleci/cat_ignore_eagain
|
||||
- run: ./db_stress --version # ensure with gflags
|
||||
|
||||
build-linux-lite:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -157,30 +233,30 @@ 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
|
||||
- run: LITE=1 make V=1 -j32 release | .circleci/cat_ignore_eagain
|
||||
- run: LITE=1 make V=1 -j8 release | .circleci/cat_ignore_eagain
|
||||
- run: if ./db_stress --version; then false; else true; fi # ensure without gflags
|
||||
- install-gflags
|
||||
- run: LITE=1 make V=1 -j32 release | .circleci/cat_ignore_eagain
|
||||
- run: LITE=1 make V=1 -j8 release | .circleci/cat_ignore_eagain
|
||||
- run: ./db_stress --version # ensure with gflags
|
||||
- post-steps
|
||||
|
||||
build-linux-clang-no-test:
|
||||
build-linux-clang-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: 2xlarge
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- run: sudo apt-get update -y && sudo apt-get install -y clang libgflags-dev
|
||||
- run: CC=clang CXX=clang++ USE_CLANG=1 PORTABLE=1 make V=1 -j32 all | .circleci/cat_ignore_eagain
|
||||
- run: sudo apt-get update -y && sudo apt-get install -y clang libgflags-dev libtbb-dev
|
||||
- run: CC=clang CXX=clang++ USE_CLANG=1 PORTABLE=1 make V=1 -j16 all | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-clang10-asan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -191,7 +267,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-mini-tsan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -202,7 +278,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-ubsan:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -213,7 +289,7 @@ jobs:
|
||||
|
||||
build-linux-clang10-clang-analyze:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
@@ -225,31 +301,73 @@ jobs:
|
||||
|
||||
build-linux-cmake:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
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 -j32) | .circleci/cat_ignore_eagain
|
||||
- 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-cmake-ubuntu-20:
|
||||
machine:
|
||||
image: ubuntu-2004:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- checkout # check out the code in the project directory
|
||||
- install-gflags
|
||||
- run: (mkdir build && cd build && cmake -DWITH_GFLAGS=1 .. && make V=1 -j20 && ctest -j20) | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-unity:
|
||||
docker: # executor type
|
||||
- image: gcc:latest
|
||||
resource_class: xlarge
|
||||
resource_class: large
|
||||
steps:
|
||||
- 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 -j16 unity_test | .circleci/cat_ignore_eagain
|
||||
- run: TEST_TMPDIR=/dev/shm && make V=1 -j8 unity_test | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-4-8:
|
||||
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
|
||||
- run: sudo apt-get update -y && sudo apt-get install gcc-4.8 g++-4.8 libgflags-dev
|
||||
- run: CC=gcc-4.8 CXX=g++-4.8 V=1 SKIP_LINK=1 make -j4 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- run: CC=gcc-4.8 CXX=g++-4.8 V=1 SKIP_LINK=1 make -j8 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-8-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-2004:202010-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: sudo apt-get update -y && sudo apt-get install gcc-8 g++-8 libgflags-dev
|
||||
- run: CC=gcc-8 CXX=g++-8 V=1 SKIP_LINK=1 make -j8 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-9-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-2004:202010-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: sudo apt-get update -y && sudo apt-get install gcc-9 g++-9 libgflags-dev
|
||||
- run: CC=gcc-9 CXX=g++-9 V=1 SKIP_LINK=1 make -j8 all | .circleci/cat_ignore_eagain # Linking broken because libgflags compiled with newer ABI
|
||||
- post-steps
|
||||
|
||||
build-linux-gcc-10-cxx20-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-2004:202010-01
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- run: sudo apt-get update -y && sudo apt-get install gcc-10 g++-10 libgflags-dev
|
||||
- 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-windows:
|
||||
@@ -278,11 +396,17 @@ jobs:
|
||||
- run:
|
||||
name: "Setup VS"
|
||||
command: |
|
||||
if [[ "${VS_YEAR}" == "2017" ]]; then
|
||||
if [[ "${VS_YEAR}" == "2019" ]]; then
|
||||
echo "VS2019 already present."
|
||||
elif [[ "${VS_YEAR}" == "2017" ]]; then
|
||||
echo "Installing VS2017..."
|
||||
powershell .circleci/vs2017_install.ps1
|
||||
elif [[ "${VS_YEAR}" == "2015" ]]; then
|
||||
echo "Installing VS2015..."
|
||||
powershell .circleci/vs2015_install.ps1
|
||||
fi
|
||||
- store_artifacts:
|
||||
path: \Users\circleci\AppData\Local\Temp\vslogs.zip
|
||||
- run:
|
||||
name: "Install thirdparty dependencies"
|
||||
command: |
|
||||
@@ -306,6 +430,7 @@ jobs:
|
||||
cd build
|
||||
${CMAKE_BIN} -G "${CMAKE_GENERATOR}" -DCMAKE_BUILD_TYPE=Debug -DOPTDBG=1 -DPORTABLE=1 -DSNAPPY=1 -DJNI=1 << parameters.extra_cmake_opt >> ..
|
||||
cd ..
|
||||
echo "Building with VS version: ${CMAKE_GENERATOR}"
|
||||
msbuild.exe build/rocksdb.sln -maxCpuCount -property:Configuration=Debug -property:Platform=x64
|
||||
- run:
|
||||
name: "Test RocksDB"
|
||||
@@ -315,26 +440,103 @@ jobs:
|
||||
|
||||
build-linux-java:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: 2xlarge
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run:
|
||||
name: "Build RocksDBJava"
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
export JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
make V=1 J=32 -j32 rocksdbjava jtest | .circleci/cat_ignore_eagain
|
||||
- run:
|
||||
name: "Build RocksDBJava Shared Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjava | .circleci/cat_ignore_eagain
|
||||
- run:
|
||||
name: "Test RocksDBJava"
|
||||
command: make V=1 J=8 -j8 jtest | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-java-static:
|
||||
machine:
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-1.8.0-openjdk-amd64
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build RocksDBJava Static Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjavastatic | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-macos-java:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
resource_class: medium
|
||||
environment:
|
||||
JAVA_HOME: /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- install-gflags-on-macos
|
||||
- pre-steps-macos
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build RocksDBJava Shared Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjava | .circleci/cat_ignore_eagain
|
||||
- run:
|
||||
name: "Test RocksDBJava"
|
||||
command: make V=1 J=8 -j8 jtest | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-macos-java-static:
|
||||
macos:
|
||||
xcode: 11.3.0
|
||||
resource_class: medium
|
||||
environment:
|
||||
JAVA_HOME: /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home
|
||||
steps:
|
||||
- increase-max-open-files-on-macos
|
||||
- install-pyenv-on-macos
|
||||
- install-gflags-on-macos
|
||||
- install-cmake-on-macos
|
||||
- pre-steps-macos
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build RocksDBJava Static Library"
|
||||
command: make V=1 J=8 -j8 rocksdbjavastatic | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-examples:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
resource_class: medium
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: large
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
@@ -344,9 +546,32 @@ jobs:
|
||||
OPT=-DTRAVIS V=1 make -j4 static_lib && cd examples && make -j4 | ../.circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-cmake-mingw:
|
||||
machine:
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: sudo apt-get update -y && sudo apt-get install -y mingw-w64
|
||||
- run: sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix
|
||||
- run:
|
||||
name: "Build cmake-mingw"
|
||||
command: |
|
||||
sudo apt-get install snapd && sudo snap install cmake --beta --classic
|
||||
export PATH=/snap/bin:$PATH
|
||||
sudo apt-get install -y openjdk-8-jdk
|
||||
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
|
||||
export PATH=$JAVA_HOME/bin:$PATH
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
mkdir build && cd build && cmake -DJNI=1 -DWITH_GFLAGS=OFF .. -DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc -DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ -DCMAKE_SYSTEM_NAME=Windows && make -j4 rocksdb rocksdbjni
|
||||
- post-steps
|
||||
|
||||
build-linux-non-shm:
|
||||
machine:
|
||||
image: ubuntu-1604:202007-01
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
parameters:
|
||||
start_test:
|
||||
@@ -371,13 +596,94 @@ jobs:
|
||||
sed -i 's/[[:space:]]*$//; s/ / \.\//g; s/.*/.\/&/' /tmp/test_list
|
||||
cat /tmp/test_list
|
||||
export TEST_TMPDIR=/tmp/rocksdb_test_tmp
|
||||
/usr/bin/python ../gtest-parallel/gtest-parallel $(</tmp/test_list) --output_dir=/tmp | cat # pipe to cat to continuously output status on circleci UI. Otherwise, no status will be printed while the job is running.
|
||||
gtest-parallel $(</tmp/test_list) --output_dir=/tmp | cat # pipe to cat to continuously output status on circleci UI. Otherwise, no status will be printed while the job is running.
|
||||
- post-steps
|
||||
|
||||
build-linux-arm-test-full:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: make V=1 J=4 -j4 check | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-arm:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run: ROCKSDBTESTS_PLATFORM_DEPENDENT=only make V=1 J=4 -j4 all_but_some_tests check_some | .circleci/cat_ignore_eagain
|
||||
- post-steps
|
||||
|
||||
build-linux-arm-cmake-no_test_run:
|
||||
machine:
|
||||
image: ubuntu-2004:202101-01
|
||||
resource_class: arm.large
|
||||
environment:
|
||||
JAVA_HOME: /usr/lib/jvm/java-8-openjdk-arm64
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- run:
|
||||
name: "Set Java Environment"
|
||||
command: |
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
echo 'export PATH=$JAVA_HOME/bin:$PATH' >> $BASH_ENV
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
- run:
|
||||
name: "Build with cmake"
|
||||
command: |
|
||||
mkdir build
|
||||
cd build
|
||||
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"
|
||||
command: |
|
||||
rm -rf build
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DJNI=1 -DCMAKE_BUILD_TYPE=Release -DWITH_GFLAGS=1 ..
|
||||
make -j4 rocksdb rocksdbjni
|
||||
- post-steps
|
||||
|
||||
build-format-compatible:
|
||||
machine:
|
||||
image: ubuntu-1604:202104-01
|
||||
resource_class: 2xlarge
|
||||
steps:
|
||||
- pre-steps
|
||||
- install-gflags
|
||||
- install-compression-libs
|
||||
- run:
|
||||
name: "test"
|
||||
command: |
|
||||
export TEST_TMPDIR=/dev/shm/rocksdb
|
||||
rm -rf /dev/shm/rocksdb
|
||||
mkdir /dev/shm/rocksdb
|
||||
tools/check_format_compatible.sh
|
||||
- post-steps
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-linux:
|
||||
jobs:
|
||||
- build-linux
|
||||
build-linux-cmake:
|
||||
jobs:
|
||||
- build-linux-cmake
|
||||
- build-linux-cmake-ubuntu-20
|
||||
build-linux-mem-env:
|
||||
jobs:
|
||||
- build-linux-mem-env
|
||||
build-linux-encrypted-env:
|
||||
jobs:
|
||||
- build-linux-encrypted-env
|
||||
build-linux-shared_lib-alt_namespace-status_checked:
|
||||
jobs:
|
||||
- build-linux-shared_lib-alt_namespace-status_checked
|
||||
@@ -393,9 +699,6 @@ workflows:
|
||||
build-linux-lite-release:
|
||||
jobs:
|
||||
- build-linux-lite-release
|
||||
build-linux-clang-no-test:
|
||||
jobs:
|
||||
- build-linux-clang-no-test
|
||||
build-linux-clang10-asan:
|
||||
jobs:
|
||||
- build-linux-clang10-asan
|
||||
@@ -408,32 +711,36 @@ workflows:
|
||||
build-linux-clang10-clang-analyze:
|
||||
jobs:
|
||||
- build-linux-clang10-clang-analyze
|
||||
build-linux-cmake:
|
||||
jobs:
|
||||
- build-linux-cmake
|
||||
build-linux-unity:
|
||||
jobs:
|
||||
- build-linux-unity
|
||||
build-windows:
|
||||
build-windows-vs2019:
|
||||
jobs:
|
||||
- build-windows
|
||||
- build-windows:
|
||||
name: "build-windows-vs2019"
|
||||
build-windows-vs2019-cxx20:
|
||||
jobs:
|
||||
- build-windows:
|
||||
name: "build-windows-vs2019-cxx20"
|
||||
extra_cmake_opt: -DCMAKE_CXX_STANDARD=20
|
||||
build-windows-vs2017:
|
||||
jobs:
|
||||
- build-windows:
|
||||
name: "build-windows-vs2017"
|
||||
vs_year: "2017"
|
||||
cmake_generator: "Visual Studio 15 Win64"
|
||||
build-windows-vs2015:
|
||||
jobs:
|
||||
- build-windows:
|
||||
vs_year: "2015"
|
||||
cmake_generator: "Visual Studio 14 Win64"
|
||||
build-windows-cxx20:
|
||||
jobs:
|
||||
- build-windows:
|
||||
extra_cmake_opt: -DCMAKE_CXX_STANDARD=20
|
||||
# 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
|
||||
- build-linux-java-static
|
||||
- build-macos-java
|
||||
- build-macos-java-static
|
||||
build-examples:
|
||||
jobs:
|
||||
- build-examples
|
||||
@@ -451,9 +758,34 @@ workflows:
|
||||
- build-linux-non-shm:
|
||||
start_test: "statistics_test" # make sure unique in src.mk
|
||||
end_test: ""
|
||||
build-linux-gcc-4-8:
|
||||
build-linux-compilers-no_test_run:
|
||||
jobs:
|
||||
- build-linux-gcc-4-8
|
||||
- build-linux-clang-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-arm-cmake-no_test_run
|
||||
build-macos:
|
||||
jobs:
|
||||
- build-macos
|
||||
build-macos-cmake:
|
||||
jobs:
|
||||
- build-macos-cmake
|
||||
build-cmake-mingw:
|
||||
jobs:
|
||||
- build-cmake-mingw
|
||||
build-linux-arm:
|
||||
jobs:
|
||||
- build-linux-arm
|
||||
nightly:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 0 * * *"
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
jobs:
|
||||
- build-format-compatible
|
||||
- build-linux-arm-test-full
|
||||
|
||||
@@ -10,7 +10,7 @@ $process = Start-Process "${PWD}\vs_installer.exe" -ArgumentList $VS_INSTALL_ARG
|
||||
Remove-Item -Path vs_installer.exe -Force
|
||||
$exitCode = $process.ExitCode
|
||||
if (($exitCode -ne 0) -and ($exitCode -ne 3010)) {
|
||||
echo "VS 2017 installer exited with code $exitCode, which should be one of [0, 3010]."
|
||||
echo "VS 2015 installer exited with code $exitCode, which should be one of [0, 3010]."
|
||||
curl.exe --retry 3 -kL $COLLECT_DOWNLOAD_LINK --output Collect.exe
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
echo "Download of the VS Collect tool failed."
|
||||
@@ -21,3 +21,4 @@ if (($exitCode -ne 0) -and ($exitCode -ne 3010)) {
|
||||
Copy-Item -Path "C:\Users\circleci\AppData\Local\Temp\vslogs.zip" -Destination "C:\w\build-results\"
|
||||
exit 1
|
||||
}
|
||||
echo "VS 2015 installed."
|
||||
|
||||
@@ -32,3 +32,4 @@ if (($exitCode -ne 0) -and ($exitCode -ne 3010)) {
|
||||
Copy-Item -Path "C:\Users\circleci\AppData\Local\Temp\vslogs.zip" -Destination "C:\w\build-results\"
|
||||
exit 1
|
||||
}
|
||||
echo "VS 2017 installed."
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Download clang-format-diff.py
|
||||
uses: wei/wget@v1
|
||||
with:
|
||||
args: https://raw.githubusercontent.com/llvm-mirror/clang/master/tools/clang-format/clang-format-diff.py
|
||||
args: https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py
|
||||
|
||||
- name: Check format
|
||||
run: VERBOSE_CHECK=1 make check-format
|
||||
|
||||
@@ -8,6 +8,7 @@ rocksdb.pc
|
||||
*.gcda
|
||||
*.gcno
|
||||
*.o
|
||||
*.o.tmp
|
||||
*.so
|
||||
*.so.*
|
||||
*_test
|
||||
@@ -89,3 +90,8 @@ buckifier/__pycache__
|
||||
compile_commands.json
|
||||
clang-format-diff.py
|
||||
.py3/
|
||||
|
||||
fuzz/proto/gen/
|
||||
fuzz/crash-*
|
||||
|
||||
cmake-build-*
|
||||
|
||||
+58
-123
@@ -2,20 +2,18 @@ dist: xenial
|
||||
language: cpp
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
arch:
|
||||
- amd64
|
||||
- arm64
|
||||
- ppc64le
|
||||
compiler:
|
||||
- clang
|
||||
- gcc
|
||||
osx_image: xcode9.4
|
||||
cache:
|
||||
- ccache
|
||||
|
||||
addons:
|
||||
apt:
|
||||
update: true
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
packages:
|
||||
@@ -26,15 +24,6 @@ addons:
|
||||
- liblzma-dev # xv
|
||||
- libzstd-dev
|
||||
- zlib1g-dev
|
||||
homebrew:
|
||||
update: true
|
||||
packages:
|
||||
- ccache
|
||||
- gflags
|
||||
- lz4
|
||||
- snappy
|
||||
- xz
|
||||
- zstd
|
||||
|
||||
env:
|
||||
- TEST_GROUP=platform_dependent # 16-18 minutes
|
||||
@@ -58,20 +47,6 @@ env:
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- os: osx
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- os: osx
|
||||
env: JOB_NAME=cmake-gcc9
|
||||
- os: osx
|
||||
env: JOB_NAME=cmake-gcc9-c++20
|
||||
- os: osx
|
||||
env: JOB_NAME=cmake-mingw
|
||||
- os: osx
|
||||
env: JOB_NAME=make-gcc4.8
|
||||
- os: osx
|
||||
arch: ppc64le
|
||||
- os: osx
|
||||
compiler: gcc
|
||||
- os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake-mingw
|
||||
@@ -86,73 +61,46 @@ matrix:
|
||||
env: JOB_NAME=make-gcc4.8
|
||||
- os: linux
|
||||
compiler: clang
|
||||
# Exclude all but most unique cmake variants for pull requests, but build all in branches
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=platform_dependent
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: amd64
|
||||
arch: arm64
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: JOB_NAME=cmake
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: amd64
|
||||
env: JOB_NAME=cmake-gcc8
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: amd64
|
||||
env: JOB_NAME=cmake-gcc9
|
||||
# Exclude most osx, arm64 and ppc64le tests for pull requests, but build in branches
|
||||
# Temporarily disable ppc64le cmake test while snapd is broken
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake
|
||||
# NB: the cmake build is a partial java test
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: osx
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=1
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: osx
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=2
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: osx
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=3
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: osx
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: TEST_GROUP=4
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/ AND commit_message !~ /java/
|
||||
os : osx
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/ AND commit_message !~ /java/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -161,9 +109,6 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=java_test
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : osx
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -172,9 +117,6 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=lite_build
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : osx
|
||||
env: JOB_NAME=examples
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -207,9 +149,6 @@ matrix:
|
||||
os: linux
|
||||
arch: ppc64le
|
||||
env: JOB_NAME=cmake-gcc9-c++20
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : osx
|
||||
env: JOB_NAME=status_checked
|
||||
- if: type = pull_request AND commit_message !~ /FULL_CI/
|
||||
os : linux
|
||||
arch: arm64
|
||||
@@ -220,9 +159,6 @@ matrix:
|
||||
env: JOB_NAME=status_checked
|
||||
|
||||
install:
|
||||
- if [ "${TRAVIS_OS_NAME}" == osx ]; then
|
||||
PATH=$PATH:/usr/local/opt/ccache/libexec;
|
||||
fi
|
||||
- if [ "${JOB_NAME}" == cmake-gcc8 ]; then
|
||||
sudo apt-get install -y g++-8 || exit $?;
|
||||
CC=gcc-8 && CXX=g++-8;
|
||||
@@ -238,22 +174,21 @@ install:
|
||||
sudo apt-get install -y g++-4.8 || exit $?;
|
||||
CC=gcc-4.8 && CXX=g++-4.8;
|
||||
fi
|
||||
- if [[ "${JOB_NAME}" == cmake* ]] && [ "${TRAVIS_OS_NAME}" == linux ]; then
|
||||
sudo apt-get install snapd && sudo snap install cmake --beta --classic || exit $?;
|
||||
export PATH=/snap/bin:$PATH;
|
||||
- |
|
||||
if [[ "${JOB_NAME}" == cmake* ]]; then
|
||||
sudo apt-get remove -y cmake cmake-data
|
||||
export CMAKE_DEB="cmake-3.14.5-Linux-$(uname -m).deb"
|
||||
export CMAKE_DEB_URL="https://rocksdb-deps.s3-us-west-2.amazonaws.com/cmake/${CMAKE_DEB}"
|
||||
curl --silent --fail --show-error --location --output "${CMAKE_DEB}" "${CMAKE_DEB_URL}" || exit $?
|
||||
sudo dpkg -i "${CMAKE_DEB}" || exit $?
|
||||
which cmake && cmake --version
|
||||
fi
|
||||
- |
|
||||
if [[ "${JOB_NAME}" == java_test || "${JOB_NAME}" == cmake* ]]; then
|
||||
# Ensure JDK 8
|
||||
if [ "${TRAVIS_OS_NAME}" == osx ]; then
|
||||
brew tap AdoptOpenJDK/openjdk || exit $?
|
||||
brew cask install adoptopenjdk8 || exit $?
|
||||
export JAVA_HOME=$(/usr/libexec/java_home)
|
||||
else
|
||||
sudo apt-get install -y openjdk-8-jdk || exit $?
|
||||
export PATH=/usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)/bin:$PATH
|
||||
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)
|
||||
fi
|
||||
sudo apt-get install -y openjdk-8-jdk || exit $?
|
||||
export PATH=/usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)/bin:$PATH
|
||||
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-$(dpkg --print-architecture)
|
||||
echo "JAVA_HOME=${JAVA_HOME}"
|
||||
which java && java -version
|
||||
which javac && javac -version
|
||||
@@ -269,30 +204,30 @@ script:
|
||||
- if [ `command -v ccache` ]; then ccache -C; fi
|
||||
- case $TEST_GROUP in
|
||||
platform_dependent)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=only make -j4 all_but_some_tests check_some
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=only make -j4 all_but_some_tests check_some
|
||||
;;
|
||||
1)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_END=backupable_db_test make -j4 check_some
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_END=backupable_db_test make -j4 check_some
|
||||
;;
|
||||
2)
|
||||
OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" V=1 make -j4 tools && OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=backupable_db_test ROCKSDBTESTS_END=db_universal_compaction_test make -j4 check_some
|
||||
OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" LIB_MODE=shared V=1 make -j4 tools && OPT="-DTRAVIS -DROCKSDB_NAMESPACE=alternative_rocksdb_ns" LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=backupable_db_test ROCKSDBTESTS_END=db_universal_compaction_test make -j4 check_some
|
||||
;;
|
||||
3)
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=db_universal_compaction_test ROCKSDBTESTS_END=table_properties_collector_test make -j4 check_some
|
||||
;;
|
||||
4)
|
||||
OPT=-DTRAVIS V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=table_properties_collector_test make -j4 check_some
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ROCKSDBTESTS_PLATFORM_DEPENDENT=exclude ROCKSDBTESTS_START=table_properties_collector_test make -j4 check_some
|
||||
;;
|
||||
esac
|
||||
- case $JOB_NAME in
|
||||
java_test)
|
||||
OPT=-DTRAVIS V=1 make rocksdbjava jtest
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 make rocksdbjava jtest
|
||||
;;
|
||||
lite_build)
|
||||
OPT='-DTRAVIS -DROCKSDB_LITE' V=1 make -j4 all
|
||||
OPT='-DTRAVIS -DROCKSDB_LITE' LIB_MODE=shared V=1 make -j4 all
|
||||
;;
|
||||
examples)
|
||||
OPT=-DTRAVIS V=1 make -j4 static_lib && cd examples && make -j4
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 make -j4 static_lib && cd examples && make -j4
|
||||
;;
|
||||
cmake-mingw)
|
||||
sudo update-alternatives --set x86_64-w64-mingw32-g++ /usr/bin/x86_64-w64-mingw32-g++-posix;
|
||||
@@ -308,10 +243,10 @@ script:
|
||||
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 .. && make -j4 && cd .. && rm -rf build && mkdir build && cd build && cmake -DJNI=1 .. -DCMAKE_BUILD_TYPE=Release $OPT && make -j4 rocksdb rocksdbjni
|
||||
;;
|
||||
make-gcc4.8)
|
||||
OPT=-DTRAVIS V=1 SKIP_LINK=1 make -j4 all && [ "Linking broken because libgflags compiled with newer ABI" ]
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 SKIP_LINK=1 make -j4 all && [ "Linking broken because libgflags compiled with newer ABI" ]
|
||||
;;
|
||||
status_checked)
|
||||
OPT=-DTRAVIS V=1 ASSERT_STATUS_CHECKED=1 make -j4 check_some
|
||||
OPT=-DTRAVIS LIB_MODE=shared V=1 ASSERT_STATUS_CHECKED=1 make -j4 check_some
|
||||
;;
|
||||
esac
|
||||
notifications:
|
||||
|
||||
+152
-58
@@ -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}
|
||||
@@ -62,6 +63,7 @@ if(CCACHE_FOUND)
|
||||
endif(CCACHE_FOUND)
|
||||
|
||||
option(WITH_JEMALLOC "build with JeMalloc" OFF)
|
||||
option(WITH_LIBURING "build with liburing" ON)
|
||||
option(WITH_SNAPPY "build with SNAPPY" OFF)
|
||||
option(WITH_LZ4 "build with lz4" OFF)
|
||||
option(WITH_ZLIB "build with zlib" OFF)
|
||||
@@ -70,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")
|
||||
@@ -88,10 +96,9 @@ if( NOT DEFINED CMAKE_CXX_STANDARD )
|
||||
endif()
|
||||
|
||||
include(CMakeDependentOption)
|
||||
CMAKE_DEPENDENT_OPTION(WITH_GFLAGS "build with GFlags" ON
|
||||
"NOT MSVC;NOT MINGW" OFF)
|
||||
|
||||
if(MSVC)
|
||||
option(WITH_GFLAGS "build with GFlags" OFF)
|
||||
option(WITH_XPRESS "build with windows built in compression" OFF)
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/thirdparty.inc)
|
||||
else()
|
||||
@@ -107,6 +114,11 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
option(WITH_GFLAGS "build with GFlags" OFF)
|
||||
else()
|
||||
option(WITH_GFLAGS "build with GFlags" ON)
|
||||
endif()
|
||||
set(GFLAGS_LIB)
|
||||
if(WITH_GFLAGS)
|
||||
# Config with namespace available since gflags 2.2.2
|
||||
@@ -118,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})
|
||||
@@ -170,23 +182,25 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
string(TIMESTAMP TS "%Y/%m/%d %H:%M:%S" UTC)
|
||||
set(GIT_DATE_TIME "${TS}" CACHE STRING "the time we first built rocksdb")
|
||||
string(TIMESTAMP TS "%Y-%m-%d %H:%M:%S" UTC)
|
||||
set(BUILD_DATE "${TS}" CACHE STRING "the time we first built rocksdb")
|
||||
|
||||
find_package(Git)
|
||||
|
||||
if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git")
|
||||
if(WIN32)
|
||||
execute_process(COMMAND $ENV{COMSPEC} /C ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse HEAD OUTPUT_VARIABLE GIT_SHA)
|
||||
else()
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} -C ${CMAKE_CURRENT_SOURCE_DIR} rev-parse HEAD OUTPUT_VARIABLE GIT_SHA)
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_SHA COMMAND "${GIT_EXECUTABLE}" rev-parse HEAD )
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" RESULT_VARIABLE GIT_MOD COMMAND "${GIT_EXECUTABLE}" diff-index HEAD --quiet)
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_DATE COMMAND "${GIT_EXECUTABLE}" log -1 --date=format:"%Y-%m-%d %T" --format="%ad")
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG RESULT_VARIABLE rv COMMAND "${GIT_EXECUTABLE}" symbolic-ref -q --short HEAD OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if (rv AND NOT rv EQUAL 0)
|
||||
execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" OUTPUT_VARIABLE GIT_TAG COMMAND "${GIT_EXECUTABLE}" describe --tags --exact-match OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
endif()
|
||||
else()
|
||||
set(GIT_SHA 0)
|
||||
set(GIT_MOD 1)
|
||||
endif()
|
||||
|
||||
string(REGEX REPLACE "[^0-9a-f]+" "" GIT_SHA "${GIT_SHA}")
|
||||
|
||||
string(REGEX REPLACE "[^0-9a-fA-F]+" "" GIT_SHA "${GIT_SHA}")
|
||||
string(REGEX REPLACE "[^0-9: /-]+" "" GIT_DATE "${GIT_DATE}")
|
||||
|
||||
option(WITH_MD_LIBRARY "build with MD" ON)
|
||||
if(WIN32 AND MSVC)
|
||||
@@ -199,15 +213,16 @@ endif()
|
||||
|
||||
set(BUILD_VERSION_CC ${CMAKE_BINARY_DIR}/build_version.cc)
|
||||
configure_file(util/build_version.cc.in ${BUILD_VERSION_CC} @ONLY)
|
||||
add_library(build_version OBJECT ${BUILD_VERSION_CC})
|
||||
target_include_directories(build_version PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/util)
|
||||
|
||||
if(MSVC)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi /nologo /EHsc /GS /Gd /GR /GF /fp:precise /Zc:wchar_t /Zc:forScope /errorReport:queue")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /FC /d2Zi+ /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")
|
||||
endif()
|
||||
if(MINGW)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-format -fno-asynchronous-unwind-tables")
|
||||
add_definitions(-D_POSIX_C_SOURCE=1)
|
||||
@@ -243,14 +258,14 @@ if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
|
||||
endif(HAS_ALTIVEC)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64")
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|AARCH64")
|
||||
CHECK_C_COMPILER_FLAG("-march=armv8-a+crc+crypto" HAS_ARMV8_CRC)
|
||||
if(HAS_ARMV8_CRC)
|
||||
message(STATUS " HAS_ARMV8_CRC yes")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv8-a+crc+crypto -Wno-unused-function")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv8-a+crc+crypto -Wno-unused-function")
|
||||
endif(HAS_ARMV8_CRC)
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|AARCH64")
|
||||
endif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64|AARCH64")
|
||||
|
||||
option(PORTABLE "build a portable binary" OFF)
|
||||
option(FORCE_SSE42 "force building with SSE4.2, even when PORTABLE=ON" OFF)
|
||||
@@ -289,6 +304,7 @@ else()
|
||||
endif()
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||
if(NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "-msse4.2 -mpclmul")
|
||||
endif()
|
||||
@@ -305,7 +321,6 @@ int main() {
|
||||
auto d = _mm_cvtsi128_si64(c);
|
||||
}
|
||||
" HAVE_SSE42)
|
||||
unset(CMAKE_REQUIRED_FLAGS)
|
||||
if(HAVE_SSE42)
|
||||
add_definitions(-DHAVE_SSE42)
|
||||
add_definitions(-DHAVE_PCLMUL)
|
||||
@@ -313,18 +328,66 @@ elseif(FORCE_SSE42)
|
||||
message(FATAL_ERROR "FORCE_SSE42=ON but unable to compile with SSE4.2 enabled")
|
||||
endif()
|
||||
|
||||
# Check if -latomic is required or not
|
||||
if (NOT MSVC)
|
||||
set(CMAKE_REQUIRED_FLAGS "--std=c++11")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
std::atomic<uint64_t> x(0);
|
||||
int main() {
|
||||
uint64_t i = x.load(std::memory_order_relaxed);
|
||||
bool b = x.is_lock_free();
|
||||
return 0;
|
||||
}
|
||||
" BUILTIN_ATOMIC)
|
||||
if (NOT BUILTIN_ATOMIC)
|
||||
#TODO: Check if -latomic exists
|
||||
list(APPEND THIRDPARTY_LIBS atomic)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WITH_LIBURING)
|
||||
set(CMAKE_REQUIRED_FLAGS "-luring")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <liburing.h>
|
||||
int main() {
|
||||
struct io_uring ring;
|
||||
io_uring_queue_init(1, &ring, 0);
|
||||
return 0;
|
||||
}
|
||||
" HAS_LIBURING)
|
||||
if (HAS_LIBURING)
|
||||
add_definitions(-DROCKSDB_IOURING_PRESENT)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -luring")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Reset the required flags
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#if defined(_MSC_VER) && !defined(__thread)
|
||||
#define __thread __declspec(thread)
|
||||
#endif
|
||||
int main() {
|
||||
static __thread int tls;
|
||||
(void)tls;
|
||||
}
|
||||
" HAVE_THREAD_LOCAL)
|
||||
if(HAVE_THREAD_LOCAL)
|
||||
add_definitions(-DROCKSDB_SUPPORT_THREAD_LOCAL)
|
||||
endif()
|
||||
|
||||
option(WITH_IOSTATS_CONTEXT "Enable IO stats context" ON)
|
||||
if (NOT WITH_IOSTATS_CONTEXT)
|
||||
add_definitions(-DNIOSTATS_CONTEXT)
|
||||
endif()
|
||||
|
||||
option(WITH_PERF_CONTEXT "Enable perf context" ON)
|
||||
if (NOT WITH_PERF_CONTEXT)
|
||||
add_definitions(-DNPERF_CONTEXT)
|
||||
endif()
|
||||
|
||||
option(FAIL_ON_WARNINGS "Treat compile warnings as errors" ON)
|
||||
if(FAIL_ON_WARNINGS)
|
||||
if(MSVC)
|
||||
@@ -466,11 +529,6 @@ if(CMAKE_SYSTEM_NAME MATCHES "Cygwin")
|
||||
add_definitions(-fno-builtin-memcmp -DCYGWIN)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
|
||||
add_definitions(-DOS_MACOSX)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES arm)
|
||||
add_definitions(-DIOS_CROSS_COMPILE -DROCKSDB_LITE)
|
||||
# no debug info for IOS, that will make our library big
|
||||
add_definitions(-DNDEBUG)
|
||||
endif()
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
add_definitions(-DOS_LINUX)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "SunOS")
|
||||
@@ -565,12 +623,15 @@ find_package(Threads REQUIRED)
|
||||
|
||||
set(SOURCES
|
||||
cache/cache.cc
|
||||
cache/cache_entry_roles.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
|
||||
@@ -580,7 +641,6 @@ set(SOURCES
|
||||
db/builder.cc
|
||||
db/c.cc
|
||||
db/column_family.cc
|
||||
db/compacted_db_impl.cc
|
||||
db/compaction/compaction.cc
|
||||
db/compaction/compaction_iterator.cc
|
||||
db/compaction/compaction_picker.cc
|
||||
@@ -591,6 +651,7 @@ set(SOURCES
|
||||
db/compaction/sst_partitioner.cc
|
||||
db/convenience.cc
|
||||
db/db_filesnapshot.cc
|
||||
db/db_impl/compacted_db_impl.cc
|
||||
db/db_impl/db_impl.cc
|
||||
db/db_impl/db_impl_write.cc
|
||||
db/db_impl/db_impl_compaction_flush.cc
|
||||
@@ -641,17 +702,20 @@ set(SOURCES
|
||||
db/write_batch_base.cc
|
||||
db/write_controller.cc
|
||||
db/write_thread.cc
|
||||
env/composite_env.cc
|
||||
env/env.cc
|
||||
env/env_chroot.cc
|
||||
env/env_encryption.cc
|
||||
env/env_hdfs.cc
|
||||
env/file_system.cc
|
||||
env/file_system_tracer.cc
|
||||
env/fs_remap.cc
|
||||
env/mock_env.cc
|
||||
file/delete_scheduler.cc
|
||||
file/file_prefetch_buffer.cc
|
||||
file/file_util.cc
|
||||
file/filename.cc
|
||||
file/line_file_reader.cc
|
||||
file/random_access_file_reader.cc
|
||||
file/read_write_util.cc
|
||||
file/readahead_raf.cc
|
||||
@@ -686,6 +750,7 @@ set(SOURCES
|
||||
monitoring/thread_status_util_debug.cc
|
||||
options/cf_options.cc
|
||||
options/configurable.cc
|
||||
options/customizable.cc
|
||||
options/db_options.cc
|
||||
options/options.cc
|
||||
options/options_helper.cc
|
||||
@@ -764,6 +829,7 @@ set(SOURCES
|
||||
util/murmurhash.cc
|
||||
util/random.cc
|
||||
util/rate_limiter.cc
|
||||
util/ribbon_config.cc
|
||||
util/slice.cc
|
||||
util/file_checksum_helper.cc
|
||||
util/status.cc
|
||||
@@ -809,8 +875,11 @@ 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/transactions/lock/lock_tracker.cc
|
||||
utilities/transactions/lock/point_lock_tracker.cc
|
||||
utilities/transactions/lock/lock_manager.cc
|
||||
utilities/transactions/lock/point/point_lock_tracker.cc
|
||||
utilities/transactions/lock/point/point_lock_manager.cc
|
||||
utilities/transactions/lock/range/range_tree/range_tree_lock_manager.cc
|
||||
utilities/transactions/lock/range/range_tree/range_tree_lock_tracker.cc
|
||||
utilities/transactions/optimistic_transaction_db_impl.cc
|
||||
utilities/transactions/optimistic_transaction.cc
|
||||
utilities/transactions/pessimistic_transaction.cc
|
||||
@@ -818,7 +887,6 @@ set(SOURCES
|
||||
utilities/transactions/snapshot_checker.cc
|
||||
utilities/transactions/transaction_base.cc
|
||||
utilities/transactions/transaction_db_mutex_impl.cc
|
||||
utilities/transactions/transaction_lock_mgr.cc
|
||||
utilities/transactions/transaction_util.cc
|
||||
utilities/transactions/write_prepared_txn.cc
|
||||
utilities/transactions/write_prepared_txn_db.cc
|
||||
@@ -826,8 +894,21 @@ set(SOURCES
|
||||
utilities/transactions/write_unprepared_txn_db.cc
|
||||
utilities/ttl/db_ttl_impl.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_internal.cc
|
||||
$<TARGET_OBJECTS:build_version>)
|
||||
utilities/write_batch_with_index/write_batch_with_index_internal.cc)
|
||||
|
||||
list(APPEND SOURCES
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/concurrent_tree.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/keyrange.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/lock_request.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/locktree.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/manager.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/range_buffer.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/treenode.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/txnid_set.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/locktree/wfg.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/standalone_port.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/util/dbt.cc
|
||||
utilities/transactions/lock/range/range_tree/lib/util/memarena.cc)
|
||||
|
||||
if(HAVE_SSE42 AND NOT MSVC)
|
||||
set_source_files_properties(
|
||||
@@ -904,12 +985,12 @@ else()
|
||||
set(SYSTEM_LIBS ${CMAKE_THREAD_LIBS_INIT})
|
||||
endif()
|
||||
|
||||
add_library(${ROCKSDB_STATIC_LIB} STATIC ${SOURCES})
|
||||
add_library(${ROCKSDB_STATIC_LIB} STATIC ${SOURCES} ${BUILD_VERSION_CC})
|
||||
target_link_libraries(${ROCKSDB_STATIC_LIB} PRIVATE
|
||||
${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
|
||||
|
||||
if(ROCKSDB_BUILD_SHARED)
|
||||
add_library(${ROCKSDB_SHARED_LIB} SHARED ${SOURCES})
|
||||
add_library(${ROCKSDB_SHARED_LIB} SHARED ${SOURCES} ${BUILD_VERSION_CC})
|
||||
target_link_libraries(${ROCKSDB_SHARED_LIB} PRIVATE
|
||||
${THIRDPARTY_LIBS} ${SYSTEM_LIBS})
|
||||
|
||||
@@ -927,7 +1008,7 @@ if(ROCKSDB_BUILD_SHARED)
|
||||
LINKER_LANGUAGE CXX
|
||||
VERSION ${rocksdb_VERSION}
|
||||
SOVERSION ${rocksdb_VERSION_MAJOR}
|
||||
OUTPUT_NAME "rocksdb")
|
||||
OUTPUT_NAME "rocksdb${ARTIFACT_SUFFIX}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -1044,15 +1125,21 @@ if(WITH_TESTS)
|
||||
cache/lru_cache_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/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
|
||||
db/compaction/compaction_picker_test.cc
|
||||
db/compaction/compaction_service_test.cc
|
||||
db/comparator_db_test.cc
|
||||
db/corruption_test.cc
|
||||
db/cuckoo_table_db_test.cc
|
||||
@@ -1068,6 +1155,7 @@ if(WITH_TESTS)
|
||||
db/db_iter_test.cc
|
||||
db/db_iter_stress_test.cc
|
||||
db/db_iterator_test.cc
|
||||
db/db_kv_checksum_test.cc
|
||||
db/db_log_iter_test.cc
|
||||
db/db_memtable_test.cc
|
||||
db/db_merge_operator_test.cc
|
||||
@@ -1075,7 +1163,7 @@ if(WITH_TESTS)
|
||||
db/db_options_test.cc
|
||||
db/db_properties_test.cc
|
||||
db/db_range_del_test.cc
|
||||
db/db_impl/db_secondary_test.cc
|
||||
db/db_secondary_test.cc
|
||||
db/db_sst_test.cc
|
||||
db/db_statistics_test.cc
|
||||
db/db_table_properties_test.cc
|
||||
@@ -1139,6 +1227,7 @@ if(WITH_TESTS)
|
||||
monitoring/statistics_test.cc
|
||||
monitoring/stats_history_test.cc
|
||||
options/configurable_test.cc
|
||||
options/customizable_test.cc
|
||||
options/options_settable_test.cc
|
||||
options/options_test.cc
|
||||
table/block_based/block_based_filter_block_test.cc
|
||||
@@ -1155,6 +1244,7 @@ if(WITH_TESTS)
|
||||
table/table_test.cc
|
||||
table/block_fetcher_test.cc
|
||||
test_util/testutil_test.cc
|
||||
trace_replay/block_cache_tracer_test.cc
|
||||
trace_replay/io_tracer_test.cc
|
||||
tools/block_cache_analyzer/block_cache_trace_analyzer_test.cc
|
||||
tools/io_tracer_parser_test.cc
|
||||
@@ -1175,6 +1265,7 @@ if(WITH_TESTS)
|
||||
util/random_test.cc
|
||||
util/rate_limiter_test.cc
|
||||
util/repeatable_thread_test.cc
|
||||
util/ribbon_test.cc
|
||||
util/slice_test.cc
|
||||
util/slice_transform_test.cc
|
||||
util/timer_queue_test.cc
|
||||
@@ -1201,9 +1292,10 @@ if(WITH_TESTS)
|
||||
utilities/table_properties_collectors/compact_on_deletion_collector_test.cc
|
||||
utilities/transactions/optimistic_transaction_test.cc
|
||||
utilities/transactions/transaction_test.cc
|
||||
utilities/transactions/transaction_lock_mgr_test.cc
|
||||
utilities/transactions/lock/point/point_lock_manager_test.cc
|
||||
utilities/transactions/write_prepared_transaction_test.cc
|
||||
utilities/transactions/write_unprepared_transaction_test.cc
|
||||
utilities/transactions/lock/range/range_locking_test.cc
|
||||
utilities/ttl/ttl_test.cc
|
||||
utilities/write_batch_with_index/write_batch_with_index_test.cc
|
||||
)
|
||||
@@ -1238,21 +1330,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})
|
||||
|
||||
@@ -1277,40 +1369,42 @@ if(WITH_TESTS)
|
||||
endif()
|
||||
|
||||
if(WITH_BENCHMARK_TOOLS)
|
||||
add_executable(db_bench
|
||||
add_executable(db_bench${ARTIFACT_SUFFIX}
|
||||
tools/simulated_hybrid_file_system.cc
|
||||
tools/db_bench.cc
|
||||
tools/db_bench_tool.cc)
|
||||
target_link_libraries(db_bench
|
||||
target_link_libraries(db_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${THIRDPARTY_LIBS})
|
||||
|
||||
add_executable(cache_bench
|
||||
cache/cache_bench.cc)
|
||||
target_link_libraries(cache_bench
|
||||
add_executable(cache_bench${ARTIFACT_SUFFIX}
|
||||
cache/cache_bench.cc
|
||||
cache/cache_bench_tool.cc)
|
||||
target_link_libraries(cache_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(memtablerep_bench
|
||||
add_executable(memtablerep_bench${ARTIFACT_SUFFIX}
|
||||
memtable/memtablerep_bench.cc)
|
||||
target_link_libraries(memtablerep_bench
|
||||
target_link_libraries(memtablerep_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(range_del_aggregator_bench
|
||||
add_executable(range_del_aggregator_bench${ARTIFACT_SUFFIX}
|
||||
db/range_del_aggregator_bench.cc)
|
||||
target_link_libraries(range_del_aggregator_bench
|
||||
target_link_libraries(range_del_aggregator_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(table_reader_bench
|
||||
add_executable(table_reader_bench${ARTIFACT_SUFFIX}
|
||||
table/table_reader_bench.cc)
|
||||
target_link_libraries(table_reader_bench
|
||||
target_link_libraries(table_reader_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} testharness ${GFLAGS_LIB})
|
||||
|
||||
add_executable(filter_bench
|
||||
add_executable(filter_bench${ARTIFACT_SUFFIX}
|
||||
util/filter_bench.cc)
|
||||
target_link_libraries(filter_bench
|
||||
target_link_libraries(filter_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
|
||||
add_executable(hash_table_bench
|
||||
add_executable(hash_table_bench${ARTIFACT_SUFFIX}
|
||||
utilities/persistent_cache/hash_table_bench.cc)
|
||||
target_link_libraries(hash_table_bench
|
||||
target_link_libraries(hash_table_bench${ARTIFACT_SUFFIX}
|
||||
${ROCKSDB_LIB} ${GFLAGS_LIB})
|
||||
endif()
|
||||
|
||||
|
||||
+295
-74
@@ -1,13 +1,231 @@
|
||||
# Rocksdb Change Log
|
||||
## Unreleased
|
||||
## 6.22.3 (2021-07-19)
|
||||
### Bug Fixes
|
||||
* Fixed block cache entry stat scans not to hold the DB mutex, which was a serious performance bug for tail latencies in TransactionDB and likely elsewhere.
|
||||
|
||||
## 6.22.2 (2021-07-14)
|
||||
### Bug Fixes
|
||||
* Fix continuous logging of an existing background error on every user write
|
||||
|
||||
## 6.22.1 (2021-06-25)
|
||||
### Bug Fixes
|
||||
* `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.
|
||||
|
||||
## 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.
|
||||
|
||||
## 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.
|
||||
* Fixed a data race between insertion into memtables and the retrieval of the DB properties `rocksdb.cur-size-active-mem-table`, `rocksdb.cur-size-all-mem-tables`, and `rocksdb.size-all-mem-tables`.
|
||||
* Fixed the false-positive alert when recovering from the WAL file. Avoid reporting "SST file is ahead of WAL" on a newly created empty column family, if the previous WAL file is corrupted.
|
||||
* Fixed a bug where `GetLiveFiles()` output included a non-existent file called "OPTIONS-000000". Backups and checkpoints, which use `GetLiveFiles()`, failed on DBs impacted by this bug. Read-write DBs were impacted when the latest OPTIONS file failed to write and `fail_if_options_file_error == false`. Read-only DBs were impacted when no OPTIONS files existed.
|
||||
* Handle return code by io_uring_submit_and_wait() and io_uring_wait_cqe().
|
||||
* In the IngestExternalFile() API, only try to sync the ingested file if the file is linked and the FileSystem/Env supports reopening a writable file.
|
||||
* Fixed a bug that `AdvancedColumnFamilyOptions.max_compaction_bytes` is under-calculated for manual compaction (`CompactRange()`). Manual compaction is split to multiple compactions if the compaction size exceed the `max_compaction_bytes`. The bug creates much larger compaction which size exceed the user setting. On the other hand, larger manual compaction size can increase the subcompaction parallelism, you can tune that by setting `max_compaction_bytes`.
|
||||
|
||||
### Behavior Changes
|
||||
* Due to the fix of false-postive alert of "SST file is ahead of WAL", all the CFs with no SST file (CF empty) will bypass the consistency check. We fixed a false-positive, but introduced a very rare true-negative which will be triggered in the following conditions: A CF with some delete operations in the last a few queries which will result in an empty CF (those are flushed to SST file and a compaction triggered which combines this file and all other SST files and generates an empty CF, or there is another reason to write a manifest entry for this CF after a flush that generates no SST file from an empty CF). The deletion entries are logged in a WAL and this WAL was corrupted, while the CF's log number points to the next WAL (due to the flush). Therefore, the DB can only recover to the point without these trailing deletions and cause the inconsistent DB status.
|
||||
|
||||
### New Features
|
||||
* Add new option allow_stall passed during instance creation of WriteBufferManager. When allow_stall is set, WriteBufferManager will stall all writers shared across multiple DBs and columns if memory usage goes beyond specified WriteBufferManager::buffer_size (soft limit). Stall will be cleared when memory is freed after flush and memory usage goes down below buffer_size.
|
||||
* Allow `CompactionFilter`s to apply in more table file creation scenarios such as flush and recovery. For compatibility, `CompactionFilter`s by default apply during compaction. Users can customize this behavior by overriding `CompactionFilterFactory::ShouldFilterTableFileCreation()`.
|
||||
* Added more fields to FilterBuildingContext with LSM details, for custom filter policies that vary behavior based on where they are in the LSM-tree.
|
||||
* Added DB::Properties::kBlockCacheEntryStats for querying statistics on what percentage of block cache is used by various kinds of blocks, etc. using DB::GetProperty and DB::GetMapProperty. The same information is now dumped to info LOG periodically according to `stats_dump_period_sec`.
|
||||
* Add an experimental Remote Compaction feature, which allows the user to run Compaction on a different host or process. The feature is still under development, currently only works on some basic use cases. The interface will be changed without backward/forward compatibility support.
|
||||
* RocksDB would validate total entries read in flush, and compare with counter inserted into it. If flush_verify_memtable_count = true (default), flush will fail. Otherwise, only log to info logs.
|
||||
* Add `TableProperties::num_filter_entries`, which can be used with `TableProperties::filter_size` to calculate the effective bits per filter entry (unique user key or prefix) for a table file.
|
||||
|
||||
### Performance Improvements
|
||||
* BlockPrefetcher is used by iterators to prefetch data if they anticipate more data to be used in future. It is enabled implicitly by rocksdb. Added change to take in account read pattern if reads are sequential. This would disable prefetching for random reads in MultiGet and iterators as readahead_size is increased exponential doing large prefetches.
|
||||
|
||||
### Public API change
|
||||
* Removed a parameter from TableFactory::NewTableBuilder, which should not be called by user code because TableBuilder is not a public API.
|
||||
* Removed unused structure `CompactionFilterContext`.
|
||||
* The `skip_filters` parameter to SstFileWriter is now considered deprecated. Use `BlockBasedTableOptions::filter_policy` to control generation of filters.
|
||||
* ClockCache is known to have bugs that could lead to crash or corruption, so should not be used until fixed. Use NewLRUCache instead.
|
||||
* Added a new pure virtual function `ApplyToAllEntries` to `Cache`, to replace `ApplyToAllCacheEntries`. Custom `Cache` implementations must add an implementation. Because this function is for gathering statistics, an empty implementation could be acceptable for some applications.
|
||||
* Added the ObjectRegistry to the ConfigOptions class. This registry instance will be used to find any customizable loadable objects during initialization.
|
||||
* Expanded the ObjectRegistry functionality to allow nested ObjectRegistry instances. Added methods to register a set of functions with the registry/library as a group.
|
||||
* Deprecated backupable_db.h and BackupableDBOptions in favor of new versions with appropriate names: backup_engine.h and BackupEngineOptions. Old API compatibility is preserved.
|
||||
|
||||
### Default Option Change
|
||||
* When options.arena_block_size <= 0 (default value 0), still use writer_buffer_size / 8 but cap to 1MB. Too large alloation size might not be friendly to allocator and might cause performance issues in extreme cases.
|
||||
|
||||
### Build
|
||||
* By default, try to build with liburing. For make, if ROCKSDB_USE_IO_URING is not set, treat as enable, which means RocksDB will try to build with liburing. Users can disable it with ROCKSDB_USE_IO_URING=0. For cmake, add WITH_LIBURING to control it, with default on.
|
||||
|
||||
## 6.20.0 (2021-04-16)
|
||||
### Behavior Changes
|
||||
* `ColumnFamilyOptions::sample_for_compression` now takes effect for creation of all block-based tables. Previously it only took effect for block-based tables created by flush.
|
||||
* `CompactFiles()` can no longer compact files from lower level to up level, which has the risk to corrupt DB (details: #8063). The validation is also added to all compactions.
|
||||
* Fixed some cases in which DB::OpenForReadOnly() could write to the filesystem. If you want a Logger with a read-only DB, you must now set DBOptions::info_log yourself, such as using CreateLoggerFromOptions().
|
||||
* get_iostats_context() will never return nullptr. If thread-local support is not available, and user does not opt-out iostats context, then compilation will fail. The same applies to perf context as well.
|
||||
* Added support for WriteBatchWithIndex::NewIteratorWithBase when overwrite_key=false. Previously, this combination was not supported and would assert or return nullptr.
|
||||
* Improve the behavior of WriteBatchWithIndex for Merge operations. Now more operations may be stored in order to return the correct merged result.
|
||||
|
||||
### Bug Fixes
|
||||
* Use thread-safe `strerror_r()` to get error messages.
|
||||
* Fixed a potential hang in shutdown for a DB whose `Env` has high-pri thread pool disabled (`Env::GetBackgroundThreads(Env::Priority::HIGH) == 0`)
|
||||
* Made BackupEngine thread-safe and added documentation comments to clarify what is safe for multiple BackupEngine objects accessing the same backup directory.
|
||||
* Fixed crash (divide by zero) when compression dictionary is applied to a file containing only range tombstones.
|
||||
* Fixed a backward iteration bug with partitioned filter enabled: not including the prefix of the last key of the previous filter partition in current filter partition can cause wrong iteration result.
|
||||
* Fixed a bug that allowed `DBOptions::max_open_files` to be set with a non-negative integer with `ColumnFamilyOptions::compaction_style = kCompactionStyleFIFO`.
|
||||
|
||||
### Performance Improvements
|
||||
* On ARM platform, use `yield` instead of `wfe` to relax cpu to gain better performance.
|
||||
|
||||
### Public API change
|
||||
* Added `TableProperties::slow_compression_estimated_data_size` and `TableProperties::fast_compression_estimated_data_size`. When `ColumnFamilyOptions::sample_for_compression > 0`, they estimate what `TableProperties::data_size` would have been if the "fast" or "slow" (see `ColumnFamilyOptions::sample_for_compression` API doc for definitions) compression had been used instead.
|
||||
* Update DB::StartIOTrace and remove Env object from the arguments as its redundant and DB already has Env object that is passed down to IOTracer::StartIOTrace
|
||||
* Added `FlushReason::kWalFull`, which is reported when a memtable is flushed due to the WAL reaching its size limit; those flushes were previously reported as `FlushReason::kWriteBufferManager`. Also, changed the reason for flushes triggered by the write buffer manager to `FlushReason::kWriteBufferManager`; they were previously reported as `FlushReason::kWriteBufferFull`.
|
||||
* Extend file_checksum_dump ldb command and DB::GetLiveFilesChecksumInfo API for IntegratedBlobDB and get checksum of blob files along with SST files.
|
||||
|
||||
### New Features
|
||||
* Added the ability to open BackupEngine backups as read-only DBs, using BackupInfo::name_for_open and env_for_open provided by BackupEngine::GetBackupInfo() with include_file_details=true.
|
||||
* Added BackupEngine support for integrated BlobDB, with blob files shared between backups when table files are shared. Because of current limitations, blob files always use the kLegacyCrc32cAndFileSize naming scheme, and incremental backups must read and checksum all blob files in a DB, even for files that are already backed up.
|
||||
* Added an optional output parameter to BackupEngine::CreateNewBackup(WithMetadata) to return the BackupID of the new backup.
|
||||
* Added BackupEngine::GetBackupInfo / GetLatestBackupInfo for querying individual backups.
|
||||
* Made the Ribbon filter a long-term supported feature in terms of the SST schema(compatible with version >= 6.15.0) though the API for enabling it is expected to change.
|
||||
|
||||
## 6.19.0 (2021-03-21)
|
||||
### Bug Fixes
|
||||
* Fixed the truncation error found in APIs/tools when dumping block-based SST files in a human-readable format. After fix, the block-based table can be fully dumped as a readable file.
|
||||
* When hitting a write slowdown condition, no write delay (previously 1 millisecond) is imposed until `delayed_write_rate` is actually exceeded, with an initial burst allowance of 1 millisecond worth of bytes. Also, beyond the initial burst allowance, `delayed_write_rate` is now more strictly enforced, especially with multiple column families.
|
||||
|
||||
### Public API change
|
||||
* Changed default `BackupableDBOptions::share_files_with_checksum` to `true` and deprecated `false` because of potential for data loss. Note that accepting this change in behavior can temporarily increase backup data usage because files are not shared between backups using the two different settings. Also removed obsolete option kFlagMatchInterimNaming.
|
||||
* Add a new option BlockBasedTableOptions::max_auto_readahead_size. RocksDB does auto-readahead for iterators on noticing more than two reads for a table file if user doesn't provide readahead_size. The readahead starts at 8KB and doubles on every additional read upto max_auto_readahead_size and now max_auto_readahead_size can be configured dynamically as well. Found that 256 KB readahead size provides the best performance, based on experiments, for auto readahead. Experiment data is in PR #3282. If value is set 0 then no automatic prefetching will be done by rocksdb. Also changing the value will only affect files opened after the change.
|
||||
* Add suppport to extend DB::VerifyFileChecksums API to also verify blob files checksum.
|
||||
* When using the new BlobDB, the amount of data written by flushes/compactions is now broken down into table files and blob files in the compaction statistics; namely, Write(GB) denotes the amount of data written to table files, while Wblob(GB) means the amount of data written to blob files.
|
||||
* New default BlockBasedTableOptions::format_version=5 to enable new Bloom filter implementation by default, compatible with RocksDB versions >= 6.6.0.
|
||||
* Add new SetBufferSize API to WriteBufferManager to allow dynamic management of memory allotted to all write buffers. This allows user code to adjust memory monitoring provided by WriteBufferManager as process memory needs change datasets grow and shrink.
|
||||
* Clarified the required semantics of Read() functions in FileSystem and Env APIs. Please ensure any custom implementations are compliant.
|
||||
* For the new integrated BlobDB implementation, compaction statistics now include the amount of data read from blob files during compaction (due to garbage collection or compaction filters). Write amplification metrics have also been extended to account for data read from blob files.
|
||||
* Add EqualWithoutTimestamp() to Comparator.
|
||||
* Extend support to track blob files in SSTFileManager whenever a blob file is created/deleted. Blob files will be scheduled to delete via SSTFileManager and SStFileManager will now take blob files in account while calculating size and space limits along with SST files.
|
||||
* Add new Append and PositionedAppend API with checksum handoff to legacy Env.
|
||||
|
||||
### New Features
|
||||
* Support compaction filters for the new implementation of BlobDB. Add `FilterBlobByKey()` to `CompactionFilter`. Subclasses can override this method so that compaction filters can determine whether the actual blob value has to be read during compaction. Use a new `kUndetermined` in `CompactionFilter::Decision` to indicated that further action is necessary for compaction filter to make a decision.
|
||||
* Add support to extend retrieval of checksums for blob files from the MANIFEST when checkpointing. During backup, rocksdb can detect corruption in blob files during file copies.
|
||||
* Add new options for db_bench --benchmarks: flush, waitforcompaction, compact0, compact1.
|
||||
* Add an option to BackupEngine::GetBackupInfo to include the name and size of each backed-up file. Especially in the presence of file sharing among backups, this offers detailed insight into backup space usage.
|
||||
* Enable backward iteration on keys with user-defined timestamps.
|
||||
* Add statistics and info log for error handler: counters for bg error, bg io error, bg retryable io error, auto resume count, auto resume total retry number, and auto resume sucess; Histogram for auto resume retry count in each recovery call. Note that, each auto resume attempt will have one or multiple retries.
|
||||
|
||||
### Behavior Changes
|
||||
* During flush, only WAL sync retryable IO error is mapped to hard error, which will stall the writes. When WAL is used but only SST file write has retryable IO error, it will be mapped to soft error and write will not be affected.
|
||||
|
||||
## 6.18.0 (2021-02-19)
|
||||
### Behavior Changes
|
||||
* When retryable IO error occurs during compaction, it is mapped to soft error and set the BG error. However, auto resume is not called to clean the soft error since compaction will reschedule by itself. In this change, When retryable IO error occurs during compaction, BG error is not set. User will be informed the error via EventHelper.
|
||||
* Introduce a new trace file format for query tracing and replay and trace file version is bump up to 0.2. A payload map is added as the first portion of the payload. We will not have backward compatible issues when adding new entries to trace records. Added the iterator_upper_bound and iterator_lower_bound in Seek and SeekForPrev tracing function. Added them as the new payload member for iterator tracing.
|
||||
|
||||
### New Features
|
||||
* Add support for key-value integrity protection in live updates from the user buffers provided to `WriteBatch` through the write to RocksDB's in-memory update buffer (memtable). This is intended to detect some cases of in-memory data corruption, due to either software or hardware errors. Users can enable protection by constructing their `WriteBatch` with `protection_bytes_per_key == 8`.
|
||||
* Add support for updating `full_history_ts_low` option in manual compaction, which is for old timestamp data GC.
|
||||
* Add a mechanism for using Makefile to build external plugin code into the RocksDB libraries/binaries. This intends to simplify compatibility and distribution for plugins (e.g., special-purpose `FileSystem`s) whose source code resides outside the RocksDB repo. See "plugin/README.md" for developer details, and "PLUGINS.md" for a listing of available plugins.
|
||||
* Added memory pre-fetching for experimental Ribbon filter, which especially optimizes performance with batched MultiGet.
|
||||
* A new, experimental version of BlobDB (key-value separation) is now available. The new implementation is integrated into the RocksDB core, i.e. it is accessible via the usual `rocksdb::DB` API, as opposed to the separate `rocksdb::blob_db::BlobDB` interface used by the earlier version, and can be configured on a per-column family basis using the configuration options `enable_blob_files`, `min_blob_size`, `blob_file_size`, `blob_compression_type`, `enable_blob_garbage_collection`, and `blob_garbage_collection_age_cutoff`. It extends RocksDB's consistency guarantees to blobs, and offers more features and better performance. Note that some features, most notably `Merge`, compaction filters, and backup/restore are not yet supported, and there is no support for migrating a database created by the old implementation.
|
||||
|
||||
### Bug Fixes
|
||||
* Since 6.15.0, `TransactionDB` returns error `Status`es from calls to `DeleteRange()` and calls to `Write()` where the `WriteBatch` contains a range deletion. Previously such operations may have succeeded while not providing the expected transactional guarantees. There are certain cases where range deletion can still be used on such DBs; see the API doc on `TransactionDB::DeleteRange()` for details.
|
||||
* `OptimisticTransactionDB` now returns error `Status`es from calls to `DeleteRange()` and calls to `Write()` where the `WriteBatch` contains a range deletion. Previously such operations may have succeeded while not providing the expected transactional guarantees.
|
||||
* Fix `WRITE_PREPARED`, `WRITE_UNPREPARED` TransactionDB `MultiGet()` may return uncommitted data with snapshot.
|
||||
* In DB::OpenForReadOnly, if any error happens while checking Manifest file path, it was overridden by Status::NotFound. It has been fixed and now actual error is returned.
|
||||
|
||||
### Public API Change
|
||||
* Added a "only_mutable_options" flag to the ConfigOptions. When this flag is "true", the Configurable functions and convenience methods (such as GetDBOptionsFromString) will only deal with options that are marked as mutable. When this flag is true, only options marked as mutable can be configured (a Status::InvalidArgument will be returned) and options not marked as mutable will not be returned or compared. The default is "false", meaning to compare all options.
|
||||
* Add new Append and PositionedAppend APIs to FileSystem to bring the data verification information (data checksum information) from upper layer (e.g., WritableFileWriter) to the storage layer. In this way, the customized FileSystem is able to verify the correctness of data being written to the storage on time. Add checksum_handoff_file_types to DBOptions. User can use this option to control which file types (Currently supported file tyes: kWALFile, kTableFile, kDescriptorFile.) should use the new Append and PositionedAppend APIs to handoff the verification information. Currently, RocksDB only use crc32c to calculate the checksum for write handoff.
|
||||
* Add an option, `CompressionOptions::max_dict_buffer_bytes`, to limit the in-memory buffering for selecting samples for generating/training a dictionary. The limit is currently loosely adhered to.
|
||||
|
||||
|
||||
## 6.17.0 (2021-01-15)
|
||||
### Behavior Changes
|
||||
* When verifying full file checksum with `DB::VerifyFileChecksums()`, we now fail with `Status::InvalidArgument` if the name of the checksum generator used for verification does not match the name of the checksum generator used for protecting the file when it was created.
|
||||
* Since RocksDB does not continue write the same file if a file write fails for any reason, the file scope write IO error is treated the same as retryable IO error. More information about error handling of file scope IO error is included in `ErrorHandler::SetBGError`.
|
||||
|
||||
### Bug Fixes
|
||||
* Version older than 6.15 cannot decode VersionEdits `WalAddition` and `WalDeletion`, fixed this by changing the encoded format of them to be ignorable by older versions.
|
||||
* Fix a race condition between DB startups and shutdowns in managing the periodic background worker threads. One effect of this race condition could be the process being terminated.
|
||||
|
||||
### Public API Change
|
||||
* Add a public API WriteBufferManager::dummy_entries_in_cache_usage() which reports the size of dummy entries stored in cache (passed to WriteBufferManager). Dummy entries are used to account for DataBlocks.
|
||||
* Add a SystemClock class that contains the time-related methods from Env. The original methods in Env may be deprecated in a future release. This class will allow easier testing, development, and expansion of time-related features.
|
||||
* Add a public API GetRocksBuildProperties and GetRocksBuildInfoAsString to get properties about the current build. These properties may include settings related to the GIT settings (branch, timestamp). This change also sets the "build date" based on the GIT properties, rather than the actual build time, thereby enabling more reproducible builds.
|
||||
|
||||
## 6.16.0 (2020-12-18)
|
||||
### Behavior Changes
|
||||
* Attempting to write a merge operand without explicitly configuring `merge_operator` now fails immediately, causing the DB to enter read-only mode. Previously, failure was deferred until the `merge_operator` was needed by a user read or a background operation.
|
||||
|
||||
### Bug Fixes
|
||||
* Truncated WALs ending in incomplete records can no longer produce gaps in the recovered data when `WALRecoveryMode::kPointInTimeRecovery` is used. Gaps are still possible when WALs are truncated exactly on record boundaries; for complete protection, users should enable `track_and_verify_wals_in_manifest`.
|
||||
* Fix a bug where compressed blocks read by MultiGet are not inserted into the compressed block cache when use_direct_reads = true.
|
||||
* Fixed the issue of full scanning on obsolete files when there are too many outstanding compactions with ConcurrentTaskLimiter enabled.
|
||||
* Fixed the logic of populating native data structure for `read_amp_bytes_per_bit` during OPTIONS file parsing on big-endian architecture. Without this fix, original code introduced in PR7659, when running on big-endian machine, can mistakenly store read_amp_bytes_per_bit (an uint32) in little endian format. Future access to `read_amp_bytes_per_bit` will give wrong values. Little endian architecture is not affected.
|
||||
* Fixed prefix extractor with timestamp issues.
|
||||
* Fixed a bug in atomic flush: in two-phase commit mode, the minimum WAL log number to keep is incorrect.
|
||||
* Fixed a bug related to checkpoint in PR7789: if there are multiple column families, and the checkpoint is not opened as read only, then in rare cases, data loss may happen in the checkpoint. Since backup engine relies on checkpoint, it may also be affected.
|
||||
* When ldb --try_load_options is used with the --column_family option, the ColumnFamilyOptions for the specified column family was not loaded from the OPTIONS file. Fix it so its loaded from OPTIONS and then overridden with command line overrides.
|
||||
|
||||
### New Features
|
||||
* User defined timestamp feature supports `CompactRange` and `GetApproximateSizes`.
|
||||
* Support getting aggregated table properties (kAggregatedTableProperties and kAggregatedTablePropertiesAtLevel) with DB::GetMapProperty, for easier access to the data in a structured format.
|
||||
* Experimental option BlockBasedTableOptions::optimize_filters_for_memory now works with experimental Ribbon filter (as well as Bloom filter).
|
||||
|
||||
### Public API Change
|
||||
* Deprecated public but rarely-used FilterBitsBuilder::CalculateNumEntry, which is replaced with ApproximateNumEntries taking a size_t parameter and returning size_t.
|
||||
* To improve portability the functions `Env::GetChildren` and `Env::GetChildrenFileAttributes` will no longer return entries for the special directories `.` or `..`.
|
||||
* Added a new option `track_and_verify_wals_in_manifest`. If `true`, the log numbers and sizes of the synced WALs are tracked in MANIFEST, then during DB recovery, if a synced WAL is missing from disk, or the WAL's size does not match the recorded size in MANIFEST, an error will be reported and the recovery will be aborted. Note that this option does not work with secondary instance.
|
||||
* `rocksdb_approximate_sizes` and `rocksdb_approximate_sizes_cf` in the C API now requires an error pointer (`char** errptr`) for receiving any error.
|
||||
* All overloads of DB::GetApproximateSizes now return Status, so that any failure to obtain the sizes is indicated to the caller.
|
||||
|
||||
## 6.15.0 (2020-11-13)
|
||||
### Bug Fixes
|
||||
* Fixed a bug in the following combination of features: indexes with user keys (`format_version >= 3`), indexes are partitioned (`index_type == kTwoLevelIndexSearch`), and some index partitions are pinned in memory (`BlockBasedTableOptions::pin_l0_filter_and_index_blocks_in_cache`). The bug could cause keys to be truncated when read from the index leading to wrong read results or other unexpected behavior.
|
||||
* Fixed a bug when indexes are partitioned (`index_type == kTwoLevelIndexSearch`), some index partitions are pinned in memory (`BlockBasedTableOptions::pin_l0_filter_and_index_blocks_in_cache`), and partitions reads could be mixed between block cache and directly from the file (e.g., with `enable_index_compression == 1` and `mmap_read == 1`, partitions that were stored uncompressed due to poor compression ratio would be read directly from the file via mmap, while partitions that were stored compressed would be read from block cache). The bug could cause index partitions to be mistakenly considered empty during reads leading to wrong read results.
|
||||
* Since 6.12, memtable lookup should report unrecognized value_type as corruption (#7121).
|
||||
* Since 6.14, fix false positive flush/compaction `Status::Corruption` failure when `paranoid_file_checks == true` and range tombstones were written to the compaction output files.
|
||||
* Since 6.14, fix a bug that could cause a stalled write to crash with mixed of slowdown and no_slowdown writes (`WriteOptions.no_slowdown=true`).
|
||||
* Fixed a bug which causes hang in closing DB when refit level is set in opt build. It was because ContinueBackgroundWork() was called in assert statement which is a no op. It was introduced in 6.14.
|
||||
* Fixed a bug which causes Get() to return incorrect result when a key's merge operand is applied twice. This can occur if the thread performing Get() runs concurrently with a background flush thread and another thread writing to the MANIFEST file (PR6069).
|
||||
* Reverted a behavior change silently introduced in 6.14.2, in which the effects of the `ignore_unknown_options` flag (used in option parsing/loading functions) changed.
|
||||
* Reverted a behavior change silently introduced in 6.14, in which options parsing/loading functions began returning `NotFound` instead of `InvalidArgument` for option names not available in the present version.
|
||||
* Fixed MultiGet bugs it doesn't return valid data with user defined timestamp.
|
||||
* Fixed a potential bug caused by evaluating `TableBuilder::NeedCompact()` before `TableBuilder::Finish()` in compaction job. For example, the `NeedCompact()` method of `CompactOnDeletionCollector` returned by built-in `CompactOnDeletionCollectorFactory` requires `BlockBasedTable::Finish()` to return the correct result. The bug can cause a compaction-generated file not to be marked for future compaction based on deletion ratio.
|
||||
* Fixed a seek issue with prefix extractor and timestamp.
|
||||
* Fixed a bug of encoding and parsing BlockBasedTableOptions::read_amp_bytes_per_bit as a 64-bit integer.
|
||||
* Fixed a bug of a recovery corner case, details in PR7621.
|
||||
|
||||
### Public API Change
|
||||
* Deprecate `BlockBasedTableOptions::pin_l0_filter_and_index_blocks_in_cache` and `BlockBasedTableOptions::pin_top_level_index_and_filter`. These options still take effect until users migrate to the replacement APIs in `BlockBasedTableOptions::metadata_cache_options`. Migration guidance can be found in the API comments on the deprecated options.
|
||||
* Add new API `DB::VerifyFileChecksums` to verify SST file checksum with corresponding entries in the MANIFEST if present. Current implementation requires scanning and recomputing file checksums.
|
||||
|
||||
## 6.14 (10/09/2020)
|
||||
### Behavior Changes
|
||||
* The dictionary compression settings specified in `ColumnFamilyOptions::compression_opts` now additionally affect files generated by flush and compaction to non-bottommost level. Previously those settings at most affected files generated by compaction to bottommost level, depending on whether `ColumnFamilyOptions::bottommost_compression_opts` overrode them. Users who relied on dictionary compression settings in `ColumnFamilyOptions::compression_opts` affecting only the bottommost level can keep the behavior by moving their dictionary settings to `ColumnFamilyOptions::bottommost_compression_opts` and setting its `enabled` flag.
|
||||
* When the `enabled` flag is set in `ColumnFamilyOptions::bottommost_compression_opts`, those compression options now take effect regardless of the value in `ColumnFamilyOptions::bottommost_compression`. Previously, those compression options only took effect when `ColumnFamilyOptions::bottommost_compression != kDisableCompressionOption`. Now, they additionally take effect when `ColumnFamilyOptions::bottommost_compression == kDisableCompressionOption` (such a setting causes bottommost compression type to fall back to `ColumnFamilyOptions::compression_per_level` if configured, and otherwise fall back to `ColumnFamilyOptions::compression`).
|
||||
|
||||
### New Features
|
||||
* An EXPERIMENTAL new Bloom alternative that saves about 30% space compared to Bloom filters, with about 3-4x construction time and similar query times is available using NewExperimentalRibbonFilterPolicy.
|
||||
|
||||
## 6.14 (2020-10-09)
|
||||
### Bug fixes
|
||||
* Fixed a bug after a `CompactRange()` with `CompactRangeOptions::change_level` set fails due to a conflict in the level change step, which caused all subsequent calls to `CompactRange()` with `CompactRangeOptions::change_level` set to incorrectly fail with a `Status::NotSupported("another thread is refitting")` error.
|
||||
* Fixed a bug that the bottom most level compaction could still be a trivial move even if `BottommostLevelCompaction.kForce` or `kForceOptimized` is set.
|
||||
@@ -23,12 +241,15 @@
|
||||
* The settings of the DBOptions and ColumnFamilyOptions are now managed by Configurable objects (see New Features). The same convenience methods to configure these options still exist but the backend implementation has been unified under a common implementation.
|
||||
|
||||
### New Features
|
||||
|
||||
* Methods to configure serialize, and compare -- such as TableFactory -- are exposed directly through the Configurable base class (from which these objects inherit). This change will allow for better and more thorough configuration management and retrieval in the future. The options for a Configurable object can be set via the ConfigureFromMap, ConfigureFromString, or ConfigureOption method. The serialized version of the options of an object can be retrieved via the GetOptionString, ToString, or GetOption methods. The list of options supported by an object can be obtained via the GetOptionNames method. The "raw" object (such as the BlockBasedTableOption) for an option may be retrieved via the GetOptions method. Configurable options can be compared via the AreEquivalent method. The settings within a Configurable object may be validated via the ValidateOptions method. The object may be intialized (at which point only mutable options may be updated) via the PrepareOptions method.
|
||||
* Introduce options.check_flush_compaction_key_order with default value to be true. With this option, during flush and compaction, key order will be checked when writing to each SST file. If the order is violated, the flush or compaction will fail.
|
||||
* Added is_full_compaction to CompactionJobStats, so that the information is available through the EventListener interface.
|
||||
* Add more stats for MultiGet in Histogram to get number of data blocks, index blocks, filter blocks and sst files read from file system per level.
|
||||
* SST files have a new table property called db_host_id, which is set to the hostname by default. A new option in DBOptions, db_host_id, allows the property value to be overridden with a user specified string, or disable it completely by making the option string empty.
|
||||
* Methods to create customizable extensions -- such as TableFactory -- are exposed directly through the Customizable base class (from which these objects inherit). This change will allow these Customizable classes to be loaded and configured in a standard way (via CreateFromString). More information on how to write and use Customizable classes is in the customizable.h header file.
|
||||
|
||||
## 6.13 (09/12/2020)
|
||||
## 6.13 (2020-09-12)
|
||||
### Bug fixes
|
||||
* Fix a performance regression introduced in 6.4 that makes a upper bound check for every Next() even if keys are within a data block that is within the upper bound.
|
||||
* Fix a possible corruption to the LSM state (overlapping files within a level) when a `CompactRange()` for refitting levels (`CompactRangeOptions::change_level == true`) and another manual compaction are executed in parallel.
|
||||
@@ -108,7 +329,7 @@
|
||||
* Reduce key comparisons during random access in all block-based tables.
|
||||
* BackupEngine avoids unnecessary repeated checksum computation for backing up a table file to the `shared_checksum` directory when using `share_files_with_checksum_naming = kUseDbSessionId` (new default), except on SST files generated before this version of RocksDB, which fall back on using `kLegacyCrc32cAndFileSize`.
|
||||
|
||||
## 6.11 (6/12/2020)
|
||||
## 6.11 (2020-06-12)
|
||||
### Bug Fixes
|
||||
* Fix consistency checking error swallowing in some cases when options.force_consistency_checks = true.
|
||||
* Fix possible false NotFound status from batched MultiGet using index type kHashSearch.
|
||||
@@ -143,7 +364,7 @@
|
||||
### Performance Improvements
|
||||
* Eliminate redundant key comparisons during random access in block-based tables.
|
||||
|
||||
## 6.10 (5/2/2020)
|
||||
## 6.10 (2020-05-02)
|
||||
### Bug Fixes
|
||||
* Fix wrong result being read from ingested file. May happen when a key in the file happen to be prefix of another key also in the file. The issue can further cause more data corruption. The issue exists with rocksdb >= 5.0.0 since DB::IngestExternalFile() was introduced.
|
||||
* Finish implementation of BlockBasedTableOptions::IndexType::kBinarySearchWithFirstKey. It's now ready for use. Significantly reduces read amplification in some setups, especially for iterator seeks.
|
||||
@@ -175,7 +396,7 @@
|
||||
* Improve performance of batch MultiGet with partitioned filters, by sharing block cache lookups to applicable filter blocks.
|
||||
* Reduced memory copies when fetching and uncompressing compressed blocks from sst files.
|
||||
|
||||
## 6.9.0 (03/29/2020)
|
||||
## 6.9.0 (2020-03-29)
|
||||
### Behavior changes
|
||||
* Since RocksDB 6.8, ttl-based FIFO compaction can drop a file whose oldest key becomes older than options.ttl while others have not. This fix reverts this and makes ttl-based FIFO compaction use the file's flush time as the criterion. This fix also requires that max_open_files = -1 and compaction_options_fifo.allow_compaction = false to function properly.
|
||||
|
||||
@@ -202,7 +423,7 @@
|
||||
* Added a new option, best_efforts_recovery (default: false), to allow database to open in a db dir with missing table files. During best efforts recovery, missing table files are ignored, and database recovers to the most recent state without missing table file. Cross-column-family consistency is not guaranteed even if WAL is enabled.
|
||||
* options.bottommost_compression, options.compression_opts and options.bottommost_compression_opts are now dynamically changeable.
|
||||
|
||||
## 6.8.0 (02/24/2020)
|
||||
## 6.8.0 (2020-02-24)
|
||||
### Java API Changes
|
||||
* Major breaking changes to Java comparators, toward standardizing on ByteBuffer for performant, locale-neutral operations on keys (#6252).
|
||||
* Added overloads of common API methods using direct ByteBuffers for keys and values (#2283).
|
||||
@@ -229,7 +450,7 @@
|
||||
* `db_bench` now supports `value_size_distribution_type`, `value_size_min`, `value_size_max` options for generating random variable sized value. Added `blob_db_compression_type` option for BlobDB to enable blob compression.
|
||||
* Replace RocksDB namespace "rocksdb" with flag "ROCKSDB_NAMESPACE" which if is not defined, defined as "rocksdb" in header file rocksdb_namespace.h.
|
||||
|
||||
## 6.7.0 (01/21/2020)
|
||||
## 6.7.0 (2020-01-21)
|
||||
### Public API Change
|
||||
* Added a rocksdb::FileSystem class in include/rocksdb/file_system.h to encapsulate file creation/read/write operations, and an option DBOptions::file_system to allow a user to pass in an instance of rocksdb::FileSystem. If its a non-null value, this will take precendence over DBOptions::env for file operations. A new API rocksdb::FileSystem::Default() returns a platform default object. The DBOptions::env option and Env::Default() API will continue to be used for threading and other OS related functions, and where DBOptions::file_system is not specified, for file operations. For storage developers who are accustomed to rocksdb::Env, the interface in rocksdb::FileSystem is new and will probably undergo some changes as more storage systems are ported to it from rocksdb::Env. As of now, no env other than Posix has been ported to the new interface.
|
||||
* A new rocksdb::NewSstFileManager() API that allows the caller to pass in separate Env and FileSystem objects.
|
||||
@@ -254,11 +475,11 @@
|
||||
* Introduce ReadOptions.auto_prefix_mode. When set to true, iterator will return the same result as total order seek, but may choose to use prefix seek internally based on seek key and iterator upper bound.
|
||||
* MultiGet() can use IO Uring to parallelize read from the same SST file. This featuer is by default disabled. It can be enabled with environment variable ROCKSDB_USE_IO_URING.
|
||||
|
||||
## 6.6.2 (01/13/2020)
|
||||
## 6.6.2 (2020-01-13)
|
||||
### Bug Fixes
|
||||
* Fixed a bug where non-L0 compaction input files were not considered to compute the `creation_time` of new compaction outputs.
|
||||
|
||||
## 6.6.1 (01/02/2020)
|
||||
## 6.6.1 (2020-01-02)
|
||||
### Bug Fixes
|
||||
* Fix a bug in WriteBatchWithIndex::MultiGetFromBatchAndDB, which is called by Transaction::MultiGet, that causes due to stale pointer access when the number of keys is > 32
|
||||
* Fixed two performance issues related to memtable history trimming. First, a new SuperVersion is now created only if some memtables were actually trimmed. Second, trimming is only scheduled if there is at least one flushed memtable that is kept in memory for the purposes of transaction conflict checking.
|
||||
@@ -268,7 +489,7 @@
|
||||
* Delete superversions in BackgroundCallPurge.
|
||||
* Fix use-after-free and double-deleting files in BackgroundCallPurge().
|
||||
|
||||
## 6.6.0 (11/25/2019)
|
||||
## 6.6.0 (2019-11-25)
|
||||
### Bug Fixes
|
||||
* Fix data corruption caused by output of intra-L0 compaction on ingested file not being placed in correct order in L0.
|
||||
* Fix a data race between Version::GetColumnFamilyMetaData() and Compaction::MarkFilesBeingCompacted() for access to being_compacted (#6056). The current fix acquires the db mutex during Version::GetColumnFamilyMetaData(), which may cause regression.
|
||||
@@ -321,19 +542,19 @@
|
||||
* For 64-bit hashing, RocksDB is standardizing on a slightly modified preview version of XXH3. This function is now used for many non-persisted hashes, along with fastrange64() in place of the modulus operator, and some benchmarks show a slight improvement.
|
||||
* Level iterator to invlidate the iterator more often in prefix seek and the level is filtered out by prefix bloom.
|
||||
|
||||
## 6.5.2 (11/15/2019)
|
||||
## 6.5.2 (2019-11-15)
|
||||
### Bug Fixes
|
||||
* Fix a assertion failure in MultiGet() when BlockBasedTableOptions::no_block_cache is true and there is no compressed block cache
|
||||
* Fix a buffer overrun problem in BlockBasedTable::MultiGet() when compression is enabled and no compressed block cache is configured.
|
||||
* If a call to BackupEngine::PurgeOldBackups or BackupEngine::DeleteBackup suffered a crash, power failure, or I/O error, files could be left over from old backups that could only be purged with a call to GarbageCollect. Any call to PurgeOldBackups, DeleteBackup, or GarbageCollect should now suffice to purge such files.
|
||||
|
||||
## 6.5.1 (10/16/2019)
|
||||
## 6.5.1 (2019-10-16)
|
||||
### Bug Fixes
|
||||
* Revert the feature "Merging iterator to avoid child iterator reseek for some cases (#5286)" since it might cause strange results when reseek happens with a different iterator upper bound.
|
||||
* Fix a bug in BlockBasedTableIterator that might return incorrect results when reseek happens with a different iterator upper bound.
|
||||
* Fix a bug when partitioned filters and prefix search are used in conjunction, ::SeekForPrev could return invalid for an existing prefix. ::SeekForPrev might be called by the user, or internally on ::Prev, or within ::Seek if the return value involves Delete or a Merge operand.
|
||||
|
||||
## 6.5.0 (9/13/2019)
|
||||
## 6.5.0 (2019-09-13)
|
||||
### Bug Fixes
|
||||
* Fixed a number of data races in BlobDB.
|
||||
* Fix a bug where the compaction snapshot refresh feature is not disabled as advertised when `snap_refresh_nanos` is set to 0..
|
||||
@@ -354,7 +575,7 @@
|
||||
### Performance Improvements
|
||||
* Improve the speed of the MemTable Bloom filter, reducing the write overhead of enabling it by 1/3 to 1/2, with similar benefit to read performance.
|
||||
|
||||
## 6.4.0 (7/30/2019)
|
||||
## 6.4.0 (2019-07-30)
|
||||
### Default Option Change
|
||||
* LRUCacheOptions.high_pri_pool_ratio is set to 0.5 (previously 0.0) by default, which means that by default midpoint insertion is enabled. The same change is made for the default value of high_pri_pool_ratio argument in NewLRUCache(). When block cache is not explicitly created, the small block cache created by BlockBasedTable will still has this option to be 0.0.
|
||||
* Change BlockBasedTableOptions.cache_index_and_filter_blocks_with_high_priority's default value from false to true.
|
||||
@@ -390,7 +611,7 @@
|
||||
* Fixed a regression where the fill_cache read option also affected index blocks.
|
||||
* Fixed an issue where using cache_index_and_filter_blocks==false affected partitions of partitioned indexes/filters as well.
|
||||
|
||||
## 6.3.2 (8/15/2019)
|
||||
## 6.3.2 (2019-08-15)
|
||||
### Public API Change
|
||||
* The semantics of the per-block-type block read counts in the performance context now match those of the generic block_read_count.
|
||||
|
||||
@@ -398,11 +619,11 @@
|
||||
* Fixed a regression where the fill_cache read option also affected index blocks.
|
||||
* Fixed an issue where using cache_index_and_filter_blocks==false affected partitions of partitioned indexes as well.
|
||||
|
||||
## 6.3.1 (7/24/2019)
|
||||
## 6.3.1 (2019-07-24)
|
||||
### Bug Fixes
|
||||
* Fix auto rolling bug introduced in 6.3.0, which causes segfault if log file creation fails.
|
||||
|
||||
## 6.3.0 (6/18/2019)
|
||||
## 6.3.0 (2019-06-18)
|
||||
### Public API Change
|
||||
* Now DB::Close() will return Aborted() error when there is unreleased snapshot. Users can retry after all snapshots are released.
|
||||
* Index blocks are now handled similarly to data blocks with regards to the block cache: instead of storing objects in the cache, only the blocks themselves are cached. In addition, index blocks no longer get evicted from the cache when a table is closed, can now use the compressed block cache (if any), and can be shared among multiple table readers.
|
||||
@@ -439,7 +660,7 @@
|
||||
* Fix a bug caused by secondary not skipping the beginning of new MANIFEST.
|
||||
* On DB open, delete WAL trash files left behind in wal_dir
|
||||
|
||||
## 6.2.0 (4/30/2019)
|
||||
## 6.2.0 (2019-04-30)
|
||||
### New Features
|
||||
* Add an option `strict_bytes_per_sync` that causes a file-writing thread to block rather than exceed the limit on bytes pending writeback specified by `bytes_per_sync` or `wal_bytes_per_sync`.
|
||||
* Improve range scan performance by avoiding per-key upper bound check in BlockBasedTableIterator.
|
||||
@@ -461,7 +682,7 @@
|
||||
* Close a WAL file before another thread deletes it.
|
||||
* Fix an assertion failure `IsFlushPending() == true` caused by one bg thread releasing the db mutex in ~ColumnFamilyData and another thread clearing `flush_requested_` flag.
|
||||
|
||||
## 6.1.1 (4/9/2019)
|
||||
## 6.1.1 (2019-04-09)
|
||||
### New Features
|
||||
* When reading from option file/string/map, customized comparators and/or merge operators can be filled according to object registry.
|
||||
|
||||
@@ -471,7 +692,7 @@
|
||||
* Fix a bug in 2PC where a sequence of txn prepare, memtable flush, and crash could result in losing the prepared transaction.
|
||||
* Fix a bug in Encryption Env which could cause encrypted files to be read beyond file boundaries.
|
||||
|
||||
## 6.1.0 (3/27/2019)
|
||||
## 6.1.0 (2019-03-27)
|
||||
### New Features
|
||||
* Introduce two more stats levels, kExceptHistogramOrTimers and kExceptTimers.
|
||||
* Added a feature to perform data-block sampling for compressibility, and report stats to user.
|
||||
@@ -489,7 +710,7 @@
|
||||
* Fix JEMALLOC_CXX_THROW macro missing from older Jemalloc versions, causing build failures on some platforms.
|
||||
* Fix SstFileReader not able to open file ingested with write_glbal_seqno=true.
|
||||
|
||||
## 6.0.0 (2/19/2019)
|
||||
## 6.0.0 (2019-02-19)
|
||||
### New Features
|
||||
* Enabled checkpoint on readonly db (DBImplReadOnly).
|
||||
* Make DB ignore dropped column families while committing results of atomic flush.
|
||||
@@ -531,7 +752,7 @@
|
||||
### Change Default Options
|
||||
* Change options.compaction_pri's default to kMinOverlappingRatio
|
||||
|
||||
## 5.18.0 (11/30/2018)
|
||||
## 5.18.0 (2018-11-30)
|
||||
### New Features
|
||||
* Introduced `JemallocNodumpAllocator` memory allocator. When being use, block cache will be excluded from core dump.
|
||||
* Introduced `PerfContextByLevel` as part of `PerfContext` which allows storing perf context at each level. Also replaced `__thread` with `thread_local` keyword for perf_context. Added per-level perf context for bloom filter and `Get` query.
|
||||
@@ -559,7 +780,7 @@
|
||||
* Start populating `NO_FILE_CLOSES` ticker statistic, which was always zero previously.
|
||||
* The default value of NewBloomFilterPolicy()'s argument use_block_based_builder is changed to false. Note that this new default may cause large temp memory usage when building very large SST files.
|
||||
|
||||
## 5.17.0 (10/05/2018)
|
||||
## 5.17.0 (2018-10-05)
|
||||
### Public API Change
|
||||
* `OnTableFileCreated` will now be called for empty files generated during compaction. In that case, `TableFileCreationInfo::file_path` will be "(nil)" and `TableFileCreationInfo::file_size` will be zero.
|
||||
* Add `FlushOptions::allow_write_stall`, which controls whether Flush calls start working immediately, even if it causes user writes to stall, or will wait until flush can be performed without causing write stall (similar to `CompactRangeOptions::allow_write_stall`). Note that the default value is false, meaning we add delay to Flush calls until stalling can be avoided when possible. This is behavior change compared to previous RocksDB versions, where Flush calls didn't check if they might cause stall or not.
|
||||
@@ -573,21 +794,21 @@
|
||||
* Avoid creating empty SSTs and subsequently deleting them in certain cases during compaction.
|
||||
* Sync CURRENT file contents during checkpoint.
|
||||
|
||||
## 5.16.3 (10/1/2018)
|
||||
## 5.16.3 (2018-10-01)
|
||||
### Bug Fixes
|
||||
* Fix crash caused when `CompactFiles` run with `CompactionOptions::compression == CompressionType::kDisableCompressionOption`. Now that setting causes the compression type to be chosen according to the column family-wide compression options.
|
||||
|
||||
## 5.16.2 (9/21/2018)
|
||||
## 5.16.2 (2018-09-21)
|
||||
### Bug Fixes
|
||||
* Fix bug in partition filters with format_version=4.
|
||||
|
||||
## 5.16.1 (9/17/2018)
|
||||
## 5.16.1 (2018-09-17)
|
||||
### Bug Fixes
|
||||
* Remove trace_analyzer_tool from rocksdb_lib target in TARGETS file.
|
||||
* Fix RocksDB Java build and tests.
|
||||
* Remove sync point in Block destructor.
|
||||
|
||||
## 5.16.0 (8/21/2018)
|
||||
## 5.16.0 (2018-08-21)
|
||||
### Public API Change
|
||||
* The merge operands are passed to `MergeOperator::ShouldMerge` in the reversed order relative to how they were merged (passed to FullMerge or FullMergeV2) for performance reasons
|
||||
* GetAllKeyVersions() to take an extra argument of `max_num_ikeys`.
|
||||
@@ -601,7 +822,7 @@
|
||||
### Bug Fixes
|
||||
* Fix a bug in misreporting the estimated partition index size in properties block.
|
||||
|
||||
## 5.15.0 (7/17/2018)
|
||||
## 5.15.0 (2018-07-17)
|
||||
### Public API Change
|
||||
* Remove managed iterator. ReadOptions.managed is not effective anymore.
|
||||
* For bottommost_compression, a compatible CompressionOptions is added via `bottommost_compression_opts`. To keep backward compatible, a new boolean `enabled` is added to CompressionOptions. For compression_opts, it will be always used no matter what value of `enabled` is. For bottommost_compression_opts, it will only be used when user set `enabled=true`, otherwise, compression_opts will be used for bottommost_compression as default.
|
||||
@@ -627,7 +848,7 @@
|
||||
* Fix a bug caused by not copying the block trailer with compressed SST file, direct IO, prefetcher and no compressed block cache.
|
||||
* Fix write can stuck indefinitely if enable_pipelined_write=true. The issue exists since pipelined write was introduced in 5.5.0.
|
||||
|
||||
## 5.14.0 (5/16/2018)
|
||||
## 5.14.0 (2018-05-16)
|
||||
### Public API Change
|
||||
* Add a BlockBasedTableOption to align uncompressed data blocks on the smaller of block size or page size boundary, to reduce flash reads by avoiding reads spanning 4K pages.
|
||||
* The background thread naming convention changed (on supporting platforms) to "rocksdb:<thread pool priority><thread number>", e.g., "rocksdb:low0".
|
||||
@@ -660,7 +881,7 @@
|
||||
* Add `BlockBasedTableConfig.setBlockCache` to allow sharing a block cache across DB instances.
|
||||
* Added SstFileManager to the Java API to allow managing SST files across DB instances.
|
||||
|
||||
## 5.13.0 (3/20/2018)
|
||||
## 5.13.0 (2018-03-20)
|
||||
### Public API Change
|
||||
* RocksDBOptionsParser::Parse()'s `ignore_unknown_options` argument will only be effective if the option file shows it is generated using a higher version of RocksDB than the current version.
|
||||
* Remove CompactionEventListener.
|
||||
@@ -676,7 +897,7 @@
|
||||
* Fix a leak in prepared_section_completed_ where the zeroed entries would not removed from the map.
|
||||
* Fix WAL corruption caused by race condition between user write thread and backup/checkpoint thread.
|
||||
|
||||
## 5.12.0 (2/14/2018)
|
||||
## 5.12.0 (2018-02-14)
|
||||
### Public API Change
|
||||
* Iterator::SeekForPrev is now a pure virtual method. This is to prevent user who implement the Iterator interface fail to implement SeekForPrev by mistake.
|
||||
* Add `include_end` option to make the range end exclusive when `include_end == false` in `DeleteFilesInRange()`.
|
||||
@@ -698,7 +919,7 @@
|
||||
* Fix advance reservation of arena block addresses.
|
||||
* Fix handling of empty string as checkpoint directory.
|
||||
|
||||
## 5.11.0 (01/08/2018)
|
||||
## 5.11.0 (2018-01-08)
|
||||
### Public API Change
|
||||
* Add `autoTune` and `getBytesPerSecond()` to RocksJava RateLimiter
|
||||
|
||||
@@ -715,7 +936,7 @@
|
||||
* Fix a mislabel bug for bottom-pri compaction threads.
|
||||
* Fix DB::Flush() keep waiting after flush finish under certain condition.
|
||||
|
||||
## 5.10.0 (12/11/2017)
|
||||
## 5.10.0 (2017-12-11)
|
||||
### Public API Change
|
||||
* When running `make` with environment variable `USE_SSE` set and `PORTABLE` unset, will use all machine features available locally. Previously this combination only compiled SSE-related features.
|
||||
|
||||
@@ -730,7 +951,7 @@
|
||||
* Fix performance issue in `IngestExternalFile()` affecting databases with large number of SST files.
|
||||
* Fix possible corruption to LSM structure when `DeleteFilesInRange()` deletes a subset of files spanned by a `DeleteRange()` marker.
|
||||
|
||||
## 5.9.0 (11/1/2017)
|
||||
## 5.9.0 (2017-11-01)
|
||||
### Public API Change
|
||||
* `BackupableDBOptions::max_valid_backups_to_open == 0` now means no backups will be opened during BackupEngine initialization. Previously this condition disabled limiting backups opened.
|
||||
* `DBOptions::preserve_deletes` is a new option that allows one to specify that DB should not drop tombstones for regular deletes if they have sequence number larger than what was set by the new API call `DB::SetPreserveDeletesSequenceNumber(SequenceNumber seqnum)`. Disabled by default.
|
||||
@@ -757,7 +978,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Fix a potential data inconsistency issue during point-in-time recovery. `DB:Open()` will abort if column family inconsistency is found during PIT recovery.
|
||||
* Fix possible metadata corruption in databases using `DeleteRange()`.
|
||||
|
||||
## 5.8.0 (08/30/2017)
|
||||
## 5.8.0 (2017-08-30)
|
||||
### Public API Change
|
||||
* Users of `Statistics::getHistogramString()` will see fewer histogram buckets and different bucket endpoints.
|
||||
* `Slice::compare` and BytewiseComparator `Compare` no longer accept `Slice`s containing nullptr.
|
||||
@@ -777,7 +998,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Fix transient reappearance of keys covered by range deletions when memtable prefix bloom filter is enabled.
|
||||
* Fix potentially wrong file smallest key when range deletions separated by snapshot are written together.
|
||||
|
||||
## 5.7.0 (07/13/2017)
|
||||
## 5.7.0 (2017-07-13)
|
||||
### Public API Change
|
||||
* DB property "rocksdb.sstables" now prints keys in hex form.
|
||||
|
||||
@@ -792,7 +1013,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### Bug Fixes
|
||||
* Fix discarding empty compaction output files when `DeleteRange()` is used together with subcompactions.
|
||||
|
||||
## 5.6.0 (06/06/2017)
|
||||
## 5.6.0 (2017-06-06)
|
||||
### Public API Change
|
||||
* Scheduling flushes and compactions in the same thread pool is no longer supported by setting `max_background_flushes=0`. Instead, users can achieve this by configuring their high-pri thread pool to have zero threads.
|
||||
* Replace `Options::max_background_flushes`, `Options::max_background_compactions`, and `Options::base_background_compactions` all with `Options::max_background_jobs`, which automatically decides how many threads to allocate towards flush/compaction.
|
||||
@@ -809,7 +1030,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### Bug Fixes
|
||||
* Shouldn't ignore return value of fsync() in flush.
|
||||
|
||||
## 5.5.0 (05/17/2017)
|
||||
## 5.5.0 (2017-05-17)
|
||||
### New Features
|
||||
* FIFO compaction to support Intra L0 compaction too with CompactionOptionsFIFO.allow_compaction=true.
|
||||
* DB::ResetStats() to reset internal stats.
|
||||
@@ -826,7 +1047,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### Bug Fixes
|
||||
* Fix the bug that Direct I/O uses direct reads for non-SST file
|
||||
|
||||
## 5.4.0 (04/11/2017)
|
||||
## 5.4.0 (2017-04-11)
|
||||
### Public API Change
|
||||
* random_access_max_buffer_size no longer has any effect
|
||||
* Removed Env::EnableReadAhead(), Env::ShouldForwardRawRequest()
|
||||
@@ -843,7 +1064,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Introduce level-based L0->L0 compactions to reduce file count, so write delays are incurred less often.
|
||||
* (Experimental) Partitioning filters which creates an index on the partitions. The feature can be enabled by setting partition_filters when using kFullFilter. Currently the feature also requires two-level indexing to be enabled. Number of partitions is the same as the number of partitions for indexes, which is controlled by metadata_block_size.
|
||||
|
||||
## 5.3.0 (03/08/2017)
|
||||
## 5.3.0 (2017-03-08)
|
||||
### Public API Change
|
||||
* Remove disableDataSync option.
|
||||
* Remove timeout_hint_us option from WriteOptions. The option has been deprecated and has no effect since 3.13.0.
|
||||
@@ -853,7 +1074,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### Bug Fixes
|
||||
* Fix the bug that iterator may skip keys
|
||||
|
||||
## 5.2.0 (02/08/2017)
|
||||
## 5.2.0 (2017-02-08)
|
||||
### Public API Change
|
||||
* NewLRUCache() will determine number of shard bits automatically based on capacity, if the user doesn't pass one. This also impacts the default block cache when the user doesn't explicit provide one.
|
||||
* Change the default of delayed slowdown value to 16MB/s and further increase the L0 stop condition to 36 files.
|
||||
@@ -871,7 +1092,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Some fixes related to 2PC.
|
||||
* Fix bugs of data corruption in direct I/O
|
||||
|
||||
## 5.1.0 (01/13/2017)
|
||||
## 5.1.0 (2017-01-13)
|
||||
* Support dynamically change `delete_obsolete_files_period_micros` option via SetDBOptions().
|
||||
* Added EventListener::OnExternalFileIngested which will be called when IngestExternalFile() add a file successfully.
|
||||
* BackupEngine::Open and BackupEngineReadOnly::Open now always return error statuses matching those of the backup Env.
|
||||
@@ -880,7 +1101,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Fix the bug that if 2PC is enabled, checkpoints may loss some recent transactions.
|
||||
* When file copying is needed when creating checkpoints or bulk loading files, fsync the file after the file copying.
|
||||
|
||||
## 5.0.0 (11/17/2016)
|
||||
## 5.0.0 (2016-11-17)
|
||||
### Public API Change
|
||||
* Options::max_bytes_for_level_multiplier is now a double along with all getters and setters.
|
||||
* Support dynamically change `delayed_write_rate` and `max_total_wal_size` options via SetDBOptions().
|
||||
@@ -899,7 +1120,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Add LuaCompactionFilter in utilities. This allows developers to write compaction filters in Lua. To use this feature, LUA_PATH needs to be set to the root directory of Lua.
|
||||
* No longer populate "LATEST_BACKUP" file in backup directory, which formerly contained the number of the latest backup. The latest backup can be determined by finding the highest numbered file in the "meta/" subdirectory.
|
||||
|
||||
## 4.13.0 (10/18/2016)
|
||||
## 4.13.0 (2016-10-18)
|
||||
### Public API Change
|
||||
* DB::GetOptions() reflect dynamic changed options (i.e. through DB::SetOptions()) and return copy of options instead of reference.
|
||||
* Added Statistics::getAndResetTickerCount().
|
||||
@@ -908,7 +1129,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Add DB::SetDBOptions() to dynamic change base_background_compactions and max_background_compactions.
|
||||
* Added Iterator::SeekForPrev(). This new API will seek to the last key that less than or equal to the target key.
|
||||
|
||||
## 4.12.0 (9/12/2016)
|
||||
## 4.12.0 (2016-09-12)
|
||||
### Public API Change
|
||||
* CancelAllBackgroundWork() flushes all memtables for databases containing writes that have bypassed the WAL (writes issued with WriteOptions::disableWAL=true) before shutting down background threads.
|
||||
* Merge options source_compaction_factor, max_grandparent_overlap_bytes and expanded_compaction_factor into max_compaction_bytes.
|
||||
@@ -920,7 +1141,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Change ticker/histogram statistics implementations to accumulate data in thread-local storage, which improves CPU performance by reducing cache coherency costs. Callers of CreateDBStatistics do not need to change anything to use this feature.
|
||||
* Block cache mid-point insertion, where index and filter block are inserted into LRU block cache with higher priority. The feature can be enabled by setting BlockBasedTableOptions::cache_index_and_filter_blocks_with_high_priority to true and high_pri_pool_ratio > 0 when creating NewLRUCache.
|
||||
|
||||
## 4.11.0 (8/1/2016)
|
||||
## 4.11.0 (2016-08-01)
|
||||
### Public API Change
|
||||
* options.memtable_prefix_bloom_huge_page_tlb_size => memtable_huge_page_size. When it is set, RocksDB will try to allocate memory from huge page for memtable too, rather than just memtable bloom filter.
|
||||
|
||||
@@ -928,7 +1149,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* A tool to migrate DB after options change. See include/rocksdb/utilities/option_change_migration.h.
|
||||
* Add ReadOptions.background_purge_on_iterator_cleanup. If true, we avoid file deletion when destroying iterators.
|
||||
|
||||
## 4.10.0 (7/5/2016)
|
||||
## 4.10.0 (2016-07-05)
|
||||
### Public API Change
|
||||
* options.memtable_prefix_bloom_bits changes to options.memtable_prefix_bloom_bits_ratio and deprecate options.memtable_prefix_bloom_probes
|
||||
* enum type CompressionType and PerfLevel changes from char to unsigned char. Value of all PerfLevel shift by one.
|
||||
@@ -940,7 +1161,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* RepairDB support for column families. RepairDB now associates data with non-default column families using information embedded in the SST/WAL files (4.7 or later). For data written by 4.6 or earlier, RepairDB associates it with the default column family.
|
||||
* Add options.write_buffer_manager which allows users to control total memtable sizes across multiple DB instances.
|
||||
|
||||
## 4.9.0 (6/9/2016)
|
||||
## 4.9.0 (2016-06-09)
|
||||
### Public API changes
|
||||
* Add bottommost_compression option, This option can be used to set a specific compression algorithm for the bottommost level (Last level containing files in the DB).
|
||||
* Introduce CompactionJobInfo::compression, This field state the compression algorithm used to generate the output files of the compaction.
|
||||
@@ -950,7 +1171,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### New Features
|
||||
* Introduce NewSimCache() in rocksdb/utilities/sim_cache.h. This function creates a block cache that is able to give simulation results (mainly hit rate) of simulating block behavior with a configurable cache size.
|
||||
|
||||
## 4.8.0 (5/2/2016)
|
||||
## 4.8.0 (2016-05-02)
|
||||
### Public API Change
|
||||
* Allow preset compression dictionary for improved compression of block-based tables. This is supported for zlib, zstd, and lz4. The compression dictionary's size is configurable via CompressionOptions::max_dict_bytes.
|
||||
* Delete deprecated classes for creating backups (BackupableDB) and restoring from backups (RestoreBackupableDB). Now, BackupEngine should be used for creating backups, and BackupEngineReadOnly should be used for restorations. For more details, see https://github.com/facebook/rocksdb/wiki/How-to-backup-RocksDB%3F
|
||||
@@ -960,12 +1181,12 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### New Features
|
||||
* Add ReadOptions::readahead_size. If non-zero, NewIterator will create a new table reader which performs reads of the given size.
|
||||
|
||||
## 4.7.0 (4/8/2016)
|
||||
## 4.7.0 (2016-04-08)
|
||||
### Public API Change
|
||||
* rename options compaction_measure_io_stats to report_bg_io_stats and include flush too.
|
||||
* Change some default options. Now default options will optimize for server-workloads. Also enable slowdown and full stop triggers for pending compaction bytes. These changes may cause sub-optimal performance or significant increase of resource usage. To avoid these risks, users can open existing RocksDB with options extracted from RocksDB option files. See https://github.com/facebook/rocksdb/wiki/RocksDB-Options-File for how to use RocksDB option files. Or you can call Options.OldDefaults() to recover old defaults. DEFAULT_OPTIONS_HISTORY.md will track change history of default options.
|
||||
|
||||
## 4.6.0 (3/10/2016)
|
||||
## 4.6.0 (2016-03-10)
|
||||
### Public API Changes
|
||||
* Change default of BlockBasedTableOptions.format_version to 2. It means default DB created by 4.6 or up cannot be opened by RocksDB version 3.9 or earlier.
|
||||
* Added strict_capacity_limit option to NewLRUCache. If the flag is set to true, insert to cache will fail if no enough capacity can be free. Signature of Cache::Insert() is updated accordingly.
|
||||
@@ -976,7 +1197,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Add CompactionPri::kMinOverlappingRatio, a compaction picking mode friendly to write amplification.
|
||||
* Deprecate Iterator::IsKeyPinned() and replace it with Iterator::GetProperty() with prop_name="rocksdb.iterator.is.key.pinned"
|
||||
|
||||
## 4.5.0 (2/5/2016)
|
||||
## 4.5.0 (2016-02-05)
|
||||
### Public API Changes
|
||||
* Add a new perf context level between kEnableCount and kEnableTime. Level 2 now does not include timers for mutexes.
|
||||
* Statistics of mutex operation durations will not be measured by default. If you want to have them enabled, you need to set Statistics::stats_level_ to kAll.
|
||||
@@ -987,7 +1208,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Add kPersistedTier to ReadTier. This option allows Get and MultiGet to read only the persited data and skip mem-tables if writes were done with disableWAL = true.
|
||||
* Add DBOptions::sst_file_manager. Use NewSstFileManager() in include/rocksdb/sst_file_manager.h to create a SstFileManager that can be used to track the total size of SST files and control the SST files deletion rate.
|
||||
|
||||
## 4.4.0 (1/14/2016)
|
||||
## 4.4.0 (2016-01-14)
|
||||
### Public API Changes
|
||||
* Change names in CompactionPri and add a new one.
|
||||
* Deprecate options.soft_rate_limit and add options.soft_pending_compaction_bytes_limit.
|
||||
@@ -997,7 +1218,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Increase default options.delayed_write_rate to 2MB/s.
|
||||
* Added a new parameter --path to ldb tool. --path accepts the name of either MANIFEST, SST or a WAL file. Either --db or --path can be used when calling ldb.
|
||||
|
||||
## 4.3.0 (12/8/2015)
|
||||
## 4.3.0 (2015-12-08)
|
||||
### New Features
|
||||
* CompactionFilter has new member function called IgnoreSnapshots which allows CompactionFilter to be called even if there are snapshots later than the key.
|
||||
* RocksDB will now persist options under the same directory as the RocksDB database on successful DB::Open, CreateColumnFamily, DropColumnFamily, and SetOptions.
|
||||
@@ -1007,7 +1228,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### Public API Changes
|
||||
* When options.db_write_buffer_size triggers, only the column family with the largest column family size will be flushed, not all the column families.
|
||||
|
||||
## 4.2.0 (11/9/2015)
|
||||
## 4.2.0 (2015-11-09)
|
||||
### New Features
|
||||
* Introduce CreateLoggerFromOptions(), this function create a Logger for provided DBOptions.
|
||||
* Add GetAggregatedIntProperty(), which returns the sum of the GetIntProperty of all the column families.
|
||||
@@ -1020,7 +1241,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Remove DefaultCompactionFilterFactory.
|
||||
|
||||
|
||||
## 4.1.0 (10/8/2015)
|
||||
## 4.1.0 (2015-10-08)
|
||||
### New Features
|
||||
* Added single delete operation as a more efficient way to delete keys that have not been overwritten.
|
||||
* Added experimental AddFile() to DB interface that allow users to add files created by SstFileWriter into an empty Database, see include/rocksdb/sst_file_writer.h and DB::AddFile() for more info.
|
||||
@@ -1034,7 +1255,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* CompactionFilter has a new method FilterMergeOperand() that RocksDB applies to every merge operand during compaction to decide whether to filter the operand.
|
||||
* We removed CompactionFilterV2 interfaces from include/rocksdb/compaction_filter.h. The functionality was deprecated already in version 3.13.
|
||||
|
||||
## 4.0.0 (9/9/2015)
|
||||
## 4.0.0 (2015-09-09)
|
||||
### New Features
|
||||
* Added support for transactions. See include/rocksdb/utilities/transaction.h for more info.
|
||||
* DB::GetProperty() now accepts "rocksdb.aggregated-table-properties" and "rocksdb.aggregated-table-properties-at-levelN", in which case it returns aggregated table properties of the target column family, or the aggregated table properties of the specified level N if the "at-level" version is used.
|
||||
@@ -1047,7 +1268,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Added Equal() method to the Comparator interface that can optionally be overwritten in cases where equality comparisons can be done more efficiently than three-way comparisons.
|
||||
* Previous 'experimental' OptimisticTransaction class has been replaced by Transaction class.
|
||||
|
||||
## 3.13.0 (8/6/2015)
|
||||
## 3.13.0 (2015-08-06)
|
||||
### New Features
|
||||
* RollbackToSavePoint() in WriteBatch/WriteBatchWithIndex
|
||||
* Add NewCompactOnDeletionCollectorFactory() in utilities/table_properties_collectors, which allows rocksdb to mark a SST file as need-compaction when it observes at least D deletion entries in any N consecutive entries in that SST file. Note that this feature depends on an experimental NeedCompact() API --- the result of this API will not persist after DB restart.
|
||||
@@ -1062,7 +1283,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Add statistics::getHistogramString() to print detailed distribution of a histogram metric.
|
||||
* Add DBOptions::skip_stats_update_on_db_open. When it is on, DB::Open() will run faster as it skips the random reads required for loading necessary stats from SST files to optimize compaction.
|
||||
|
||||
## 3.12.0 (7/2/2015)
|
||||
## 3.12.0 (2015-07-02)
|
||||
### New Features
|
||||
* Added experimental support for optimistic transactions. See include/rocksdb/utilities/optimistic_transaction.h for more info.
|
||||
* Added a new way to report QPS from db_bench (check out --report_file and --report_interval_seconds)
|
||||
@@ -1092,7 +1313,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Add BackupEngineImpl.options_.max_background_operations to specify the maximum number of operations that may be performed in parallel. Add support for parallelized backup and restore.
|
||||
* Add DB::SyncWAL() that does a WAL sync without blocking writers.
|
||||
|
||||
## 3.11.0 (5/19/2015)
|
||||
## 3.11.0 (2015-05-19)
|
||||
### New Features
|
||||
* Added a new API Cache::SetCapacity(size_t capacity) to dynamically change the maximum configured capacity of the cache. If the new capacity is less than the existing cache usage, the implementation will try to lower the usage by evicting the necessary number of elements following a strict LRU policy.
|
||||
* Added an experimental API for handling flashcache devices (blacklists background threads from caching their reads) -- NewFlashcacheAwareEnv
|
||||
@@ -1103,7 +1324,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* TablePropertiesCollector::AddUserKey() is added to replace TablePropertiesCollector::Add(). AddUserKey() exposes key type, sequence number and file size up to now to users.
|
||||
* DBOptions::bytes_per_sync used to apply to both WAL and table files. As of 3.11 it applies only to table files. If you want to use this option to sync WAL in the background, please use wal_bytes_per_sync
|
||||
|
||||
## 3.10.0 (3/24/2015)
|
||||
## 3.10.0 (2015-03-24)
|
||||
### New Features
|
||||
* GetThreadStatus() is now able to report detailed thread status, including:
|
||||
- Thread Operation including flush and compaction.
|
||||
@@ -1138,7 +1359,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* lz4 compression is now included in rocksjava static library when running `make rocksdbjavastatic`.
|
||||
* Overflowing a size_t when setting rocksdb options now throws an IllegalArgumentException, which removes the necessity for a developer to catch these Exceptions explicitly.
|
||||
|
||||
## 3.9.0 (12/8/2014)
|
||||
## 3.9.0 (2014-12-08)
|
||||
|
||||
### New Features
|
||||
* Add rocksdb::GetThreadList(), which in the future will return the current status of all
|
||||
@@ -1157,7 +1378,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### Improvements
|
||||
* RocksDBLite library now becomes smaller and will be compiled with -fno-exceptions flag.
|
||||
|
||||
## 3.8.0 (11/14/2014)
|
||||
## 3.8.0 (2014-11-14)
|
||||
|
||||
### Public API changes
|
||||
* BackupEngine::NewBackupEngine() was deprecated; please use BackupEngine::Open() from now on.
|
||||
@@ -1171,14 +1392,14 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* CompactFiles and EventListener, although they are still in experimental state
|
||||
* Full ColumnFamily support in RocksJava.
|
||||
|
||||
## 3.7.0 (11/6/2014)
|
||||
## 3.7.0 (2014-11-06)
|
||||
### Public API changes
|
||||
* Introduce SetOptions() API to allow adjusting a subset of options dynamically online
|
||||
* Introduce 4 new convenient functions for converting Options from string: GetColumnFamilyOptionsFromMap(), GetColumnFamilyOptionsFromString(), GetDBOptionsFromMap(), GetDBOptionsFromString()
|
||||
* Remove WriteBatchWithIndex.Delete() overloads using SliceParts
|
||||
* When opening a DB, if options.max_background_compactions is larger than the existing low pri pool of options.env, it will enlarge it. Similarly, options.max_background_flushes is larger than the existing high pri pool of options.env, it will enlarge it.
|
||||
|
||||
## 3.6.0 (10/7/2014)
|
||||
## 3.6.0 (2014-10-07)
|
||||
### Disk format changes
|
||||
* If you're using RocksDB on ARM platforms and you're using default bloom filter, there is a disk format change you need to be aware of. There are three steps you need to do when you convert to new release: 1. turn off filter policy, 2. compact the whole database, 3. turn on filter policy
|
||||
|
||||
@@ -1191,7 +1412,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Change target_file_size_base type to uint64_t from int.
|
||||
* Remove allow_thread_local. This feature was proved to be stable, so we are turning it always-on.
|
||||
|
||||
## 3.5.0 (9/3/2014)
|
||||
## 3.5.0 (2014-09-03)
|
||||
### New Features
|
||||
* Add include/utilities/write_batch_with_index.h, providing a utility class to query data out of WriteBatch when building it.
|
||||
* Move BlockBasedTable related options to BlockBasedTableOptions from Options. Change corresponding JNI interface. Options affected include:
|
||||
@@ -1202,7 +1423,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### Public API changes
|
||||
* The Prefix Extractor used with V2 compaction filters is now passed user key to SliceTransform::Transform instead of unparsed RocksDB key.
|
||||
|
||||
## 3.4.0 (8/18/2014)
|
||||
## 3.4.0 (2014-08-18)
|
||||
### New Features
|
||||
* Support Multiple DB paths in universal style compactions
|
||||
* Add feature of storing plain table index and bloom filter in SST file.
|
||||
@@ -1218,7 +1439,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
* Add DB::GetIntProperty(), which returns DB properties that are integer as uint64_t.
|
||||
* The Prefix Extractor used with V2 compaction filters is now passed user key to SliceTransform::Transform instead of unparsed RocksDB key.
|
||||
|
||||
## 3.3.0 (7/10/2014)
|
||||
## 3.3.0 (2014-07-10)
|
||||
### New Features
|
||||
* Added JSON API prototype.
|
||||
* HashLinklist reduces performance outlier caused by skewed bucket by switching data in the bucket from linked list to skip list. Add parameter threshold_use_skiplist in NewHashLinkListRepFactory().
|
||||
@@ -1229,7 +1450,7 @@ if set to something > 0 user will see 2 changes in iterators behavior 1) only ke
|
||||
### Public API changes
|
||||
* Removed NewTotalOrderPlainTableFactory because it is not used and implemented semantically incorrect.
|
||||
|
||||
## 3.2.0 (06/20/2014)
|
||||
## 3.2.0 (2014-06-20)
|
||||
|
||||
### Public API changes
|
||||
* We removed seek compaction as a concept from RocksDB because:
|
||||
@@ -1247,7 +1468,7 @@ Because of that, Options::disable_seek_compaction is now obsolete. It is still a
|
||||
### Performance Improvements
|
||||
* Tailing Iterator re-implemeted with ForwardIterator + Cascading Search Hint , see ~20% throughput improvement.
|
||||
|
||||
## 3.1.0 (05/21/2014)
|
||||
## 3.1.0 (2014-05-21)
|
||||
|
||||
### Public API changes
|
||||
* Replaced ColumnFamilyOptions::table_properties_collectors with ColumnFamilyOptions::table_properties_collector_factories
|
||||
@@ -1256,7 +1477,7 @@ Because of that, Options::disable_seek_compaction is now obsolete. It is still a
|
||||
* Hash index for block-based table will be materialized and reconstructed more efficiently. Previously hash index is constructed by scanning the whole table during every table open.
|
||||
* FIFO compaction style
|
||||
|
||||
## 3.0.0 (05/05/2014)
|
||||
## 3.0.0 (2014-05-05)
|
||||
|
||||
### Public API changes
|
||||
* Added _LEVEL to all InfoLogLevel enums
|
||||
@@ -1268,7 +1489,7 @@ Because of that, Options::disable_seek_compaction is now obsolete. It is still a
|
||||
* Added an option to use different checksum functions in BlockBasedTableOptions
|
||||
* Added ApplyToAllCacheEntries() function to Cache
|
||||
|
||||
## 2.8.0 (04/04/2014)
|
||||
## 2.8.0 (2014-04-04)
|
||||
|
||||
* Removed arena.h from public header files.
|
||||
* By default, checksums are verified on every read from database
|
||||
@@ -1297,7 +1518,7 @@ Because of that, Options::disable_seek_compaction is now obsolete. It is still a
|
||||
* Now compaction filter has a V2 interface. It buffers the kv-pairs sharing the same key prefix, process them in batches, and return the batched results back to DB. The new interface uses a new structure CompactionFilterContext for the same purpose as CompactionFilter::Context in V1.
|
||||
* Geo-spatial support for locations and radial-search.
|
||||
|
||||
## 2.7.0 (01/28/2014)
|
||||
## 2.7.0 (2014-01-28)
|
||||
|
||||
### Public API changes
|
||||
|
||||
|
||||
+16
-5
@@ -43,6 +43,8 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
command line flags processing. You can compile rocksdb library even
|
||||
if you don't have gflags installed.
|
||||
|
||||
* `make check` will also check code formatting, which requires [clang-format](https://clang.llvm.org/docs/ClangFormat.html)
|
||||
|
||||
* If you wish to build the RocksJava static target, then cmake is required for building Snappy.
|
||||
|
||||
## Supported platforms
|
||||
@@ -94,12 +96,21 @@ to build a portable binary, add `PORTABLE=1` before your make commands, like thi
|
||||
sudo yum install libasan
|
||||
|
||||
* Install zstandard:
|
||||
* With [EPEL](https://fedoraproject.org/wiki/EPEL):
|
||||
|
||||
wget https://github.com/facebook/zstd/archive/v1.1.3.tar.gz
|
||||
mv v1.1.3.tar.gz zstd-1.1.3.tar.gz
|
||||
tar zxvf zstd-1.1.3.tar.gz
|
||||
cd zstd-1.1.3
|
||||
make && sudo make install
|
||||
sudo yum install libzstd-devel
|
||||
|
||||
* With CentOS 8:
|
||||
|
||||
sudo dnf install libzstd-devel
|
||||
|
||||
* From source:
|
||||
|
||||
wget https://github.com/facebook/zstd/archive/v1.1.3.tar.gz
|
||||
mv v1.1.3.tar.gz zstd-1.1.3.tar.gz
|
||||
tar zxvf zstd-1.1.3.tar.gz
|
||||
cd zstd-1.1.3
|
||||
make && sudo make install
|
||||
|
||||
* **OS X**:
|
||||
* Install latest C++ compiler that supports C++ 11:
|
||||
|
||||
@@ -55,60 +55,33 @@ DEBUG_LEVEL?=1
|
||||
# Set the default LIB_MODE to static
|
||||
LIB_MODE?=static
|
||||
|
||||
ifeq ($(MAKECMDGOALS),dbg)
|
||||
# OBJ_DIR is where the object files reside. Default to the current directory
|
||||
OBJ_DIR?=.
|
||||
|
||||
# Check the MAKECMDGOALS to set the DEBUG_LEVEL and LIB_MODE appropriately
|
||||
|
||||
ifneq ($(filter clean release install, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
ifneq ($(filter dbg, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=2
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),clean)
|
||||
else ifneq ($(filter shared_lib install-shared, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),release)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),shared_lib)
|
||||
LIB_MODE=shared
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install-shared)
|
||||
LIB_MODE=shared
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),static_lib)
|
||||
else ifneq ($(filter static_lib install-static, $(MAKECMDGOALS)),)
|
||||
DEBUG_LEVEL=0
|
||||
LIB_MODE=static
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install-static)
|
||||
DEBUG_LEVEL=0
|
||||
LIB_MODE=static
|
||||
endif
|
||||
|
||||
ifeq ($(MAKECMDGOALS),install)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
|
||||
|
||||
ifneq ($(findstring jtest, $(MAKECMDGOALS)),)
|
||||
else ifneq ($(filter jtest rocksdbjava%, $(MAKECMDGOALS)),)
|
||||
OBJ_DIR=jl
|
||||
LIB_MODE=shared
|
||||
endif
|
||||
|
||||
ifneq ($(findstring rocksdbjava, $(MAKECMDGOALS)),)
|
||||
LIB_MODE=shared
|
||||
ifneq ($(findstring rocksdbjavastatic, $(MAKECMDGOALS)),)
|
||||
ifneq ($(findstring rocksdbjavastatic, $(MAKECMDGOALS)),)
|
||||
OBJ_DIR=jls
|
||||
ifneq ($(DEBUG_LEVEL),2)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
else
|
||||
OBJ_DIR=jl
|
||||
ifneq ($(DEBUG_LEVEL),2)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
ifeq ($(MAKECMDGOALS),rocksdbjavastaticpublish)
|
||||
DEBUG_LEVEL=0
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -190,6 +163,11 @@ else
|
||||
endif
|
||||
|
||||
ifdef ASSERT_STATUS_CHECKED
|
||||
# For ASC, turn off constructor elision, preventing the case where a constructor returned
|
||||
# by a method may pass the ASC check if the status is checked in the inner method. Forcing
|
||||
# the copy constructor to be invoked disables the optimization and will cause the calling method
|
||||
# to check the status in order to prevent an error from being raised.
|
||||
PLATFORM_CXXFLAGS += -fno-elide-constructors
|
||||
ifeq ($(filter -DROCKSDB_ASSERT_STATUS_CHECKED,$(OPT)),)
|
||||
OPT += -DROCKSDB_ASSERT_STATUS_CHECKED
|
||||
endif
|
||||
@@ -254,6 +232,8 @@ AM_SHARE = $(AM_V_CCLD) $(CXX) $(PLATFORM_SHARED_LDFLAGS)$@ -L. $(patsubst lib%.
|
||||
# Export some common variables that might have been passed as Make variables
|
||||
# instead of environment variables.
|
||||
dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
export CXXFLAGS="$(EXTRA_CXXFLAGS)"; \
|
||||
export LDFLAGS="$(EXTRA_LDFLAGS)"; \
|
||||
export COMPILE_WITH_ASAN="$(COMPILE_WITH_ASAN)"; \
|
||||
export COMPILE_WITH_TSAN="$(COMPILE_WITH_TSAN)"; \
|
||||
export COMPILE_WITH_UBSAN="$(COMPILE_WITH_UBSAN)"; \
|
||||
@@ -264,6 +244,12 @@ dummy := $(shell (export ROCKSDB_ROOT="$(CURDIR)"; \
|
||||
# this file is generated by the previous line to set build flags and sources
|
||||
include make_config.mk
|
||||
|
||||
ROCKSDB_PLUGIN_MKS = $(foreach plugin, $(ROCKSDB_PLUGINS), plugin/$(plugin)/*.mk)
|
||||
include $(ROCKSDB_PLUGIN_MKS)
|
||||
ROCKSDB_PLUGIN_SOURCES = $(foreach plugin, $(ROCKSDB_PLUGINS), $(foreach source, $($(plugin)_SOURCES), plugin/$(plugin)/$(source)))
|
||||
ROCKSDB_PLUGIN_HEADERS = $(foreach plugin, $(ROCKSDB_PLUGINS), $(foreach header, $($(plugin)_HEADERS), plugin/$(plugin)/$(header)))
|
||||
PLATFORM_LDFLAGS += $(foreach plugin, $(ROCKSDB_PLUGINS), $($(plugin)_LDFLAGS))
|
||||
|
||||
export JAVAC_ARGS
|
||||
CLEAN_FILES += make_config.mk rocksdb.pc
|
||||
|
||||
@@ -414,6 +400,10 @@ ifdef TEST_UINT128_COMPAT
|
||||
PLATFORM_CCFLAGS += -DTEST_UINT128_COMPAT=1
|
||||
PLATFORM_CXXFLAGS += -DTEST_UINT128_COMPAT=1
|
||||
endif
|
||||
ifdef ROCKSDB_MODIFY_NPHASH
|
||||
PLATFORM_CCFLAGS += -DROCKSDB_MODIFY_NPHASH=1
|
||||
PLATFORM_CXXFLAGS += -DROCKSDB_MODIFY_NPHASH=1
|
||||
endif
|
||||
|
||||
# This (the first rule) must depend on "all".
|
||||
default: all
|
||||
@@ -421,6 +411,10 @@ default: all
|
||||
WARNING_FLAGS = -W -Wextra -Wall -Wsign-compare -Wshadow \
|
||||
-Wunused-parameter
|
||||
|
||||
ifeq (,$(filter amd64, $(MACHINE)))
|
||||
C_WARNING_FLAGS = -Wstrict-prototypes
|
||||
endif
|
||||
|
||||
ifdef USE_CLANG
|
||||
# Used by some teams in Facebook
|
||||
WARNING_FLAGS += -Wshift-sign-overflow
|
||||
@@ -465,38 +459,13 @@ ifeq ($(NO_THREEWAY_CRC32C), 1)
|
||||
CXXFLAGS += -DNO_THREEWAY_CRC32C
|
||||
endif
|
||||
|
||||
CFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CFLAGS += $(C_WARNING_FLAGS) $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CCFLAGS) $(OPT)
|
||||
CXXFLAGS += $(WARNING_FLAGS) -I. -I./include $(PLATFORM_CXXFLAGS) $(OPT) -Woverloaded-virtual -Wnon-virtual-dtor -Wno-missing-field-initializers
|
||||
|
||||
LDFLAGS += $(PLATFORM_LDFLAGS)
|
||||
|
||||
# If NO_UPDATE_BUILD_VERSION is set we don't update util/build_version.cc, but
|
||||
# the file needs to already exist or else the build will fail
|
||||
ifndef NO_UPDATE_BUILD_VERSION
|
||||
date := $(shell date +%F)
|
||||
ifdef FORCE_GIT_SHA
|
||||
git_sha := $(FORCE_GIT_SHA)
|
||||
else
|
||||
git_sha := $(shell git rev-parse HEAD 2>/dev/null)
|
||||
endif
|
||||
gen_build_version = sed -e s/@@GIT_SHA@@/$(git_sha)/ -e s/@@GIT_DATE_TIME@@/$(date)/ util/build_version.cc.in
|
||||
|
||||
# Record the version of the source that we are compiling.
|
||||
# We keep a record of the git revision in this file. It is then built
|
||||
# as a regular source file as part of the compilation process.
|
||||
# One can run "strings executable_filename | grep _build_" to find
|
||||
# the version of the source that we used to build the executable file.
|
||||
FORCE:
|
||||
util/build_version.cc: FORCE
|
||||
$(AM_V_GEN)rm -f $@-t
|
||||
$(AM_V_at)$(gen_build_version) > $@-t
|
||||
$(AM_V_at)if test -f $@; then \
|
||||
cmp -s $@-t $@ && rm -f $@-t || mv -f $@-t $@; \
|
||||
else mv -f $@-t $@; fi
|
||||
endif
|
||||
|
||||
OBJ_DIR?=.
|
||||
LIB_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(LIB_SOURCES))
|
||||
LIB_OBJECTS += $(patsubst %.cc, $(OBJ_DIR)/%.o, $(ROCKSDB_PLUGIN_SOURCES))
|
||||
ifeq ($(HAVE_POWER8),1)
|
||||
LIB_OBJECTS += $(patsubst %.c, $(OBJ_DIR)/%.o, $(LIB_SOURCES_C))
|
||||
LIB_OBJECTS += $(patsubst %.S, $(OBJ_DIR)/%.o, $(LIB_SOURCES_ASM))
|
||||
@@ -506,6 +475,12 @@ ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
LIB_OBJECTS += $(patsubst %.cpp, $(OBJ_DIR)/%.o, $(FOLLY_SOURCES))
|
||||
endif
|
||||
|
||||
# range_tree is not compatible with non GNU libc on ppc64
|
||||
# see https://jira.percona.com/browse/PS-7559
|
||||
ifneq ($(PPC_LIBC_IS_GNU),0)
|
||||
LIB_OBJECTS += $(patsubst %.cc, $(OBJ_DIR)/%.o, $(RANGE_TREE_SOURCES))
|
||||
endif
|
||||
|
||||
GTEST = $(OBJ_DIR)/$(GTEST_DIR)/gtest/gtest-all.o
|
||||
TESTUTIL = $(OBJ_DIR)/test_util/testutil.o
|
||||
TESTHARNESS = $(OBJ_DIR)/test_util/testharness.o $(TESTUTIL) $(GTEST)
|
||||
@@ -516,12 +491,14 @@ VALGRIND_OPTS = --error-exitcode=$(VALGRIND_ERROR) --leak-check=full
|
||||
|
||||
TEST_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES)) $(GTEST)
|
||||
BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(BENCH_LIB_SOURCES))
|
||||
CACHE_BENCH_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(CACHE_BENCH_LIB_SOURCES))
|
||||
TOOL_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(TOOL_LIB_SOURCES))
|
||||
ANALYZE_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(ANALYZER_LIB_SOURCES))
|
||||
STRESS_OBJECTS = $(patsubst %.cc, $(OBJ_DIR)/%.o, $(STRESS_LIB_SOURCES))
|
||||
|
||||
ALL_SOURCES = $(LIB_SOURCES) $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES) $(GTEST_DIR)/gtest/gtest-all.cc
|
||||
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
# Exclude build_version.cc -- a generated source file -- from all sources. Not needed for dependencies
|
||||
ALL_SOURCES = $(filter-out util/build_version.cc, $(LIB_SOURCES)) $(TEST_LIB_SOURCES) $(MOCK_LIB_SOURCES) $(GTEST_DIR)/gtest/gtest-all.cc
|
||||
ALL_SOURCES += $(TOOL_LIB_SOURCES) $(BENCH_LIB_SOURCES) $(CACHE_BENCH_LIB_SOURCES) $(ANALYZER_LIB_SOURCES) $(STRESS_LIB_SOURCES)
|
||||
ALL_SOURCES += $(TEST_MAIN_SOURCES) $(TOOL_MAIN_SOURCES) $(BENCH_MAIN_SOURCES)
|
||||
|
||||
TESTS = $(patsubst %.cc, %, $(notdir $(TEST_MAIN_SOURCES)))
|
||||
@@ -532,161 +509,40 @@ ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
ALL_SOURCES += third-party/folly/folly/synchronization/test/DistributedMutexTest.cc
|
||||
endif
|
||||
|
||||
PARALLEL_TEST = \
|
||||
backupable_db_test \
|
||||
db_bloom_filter_test \
|
||||
db_compaction_filter_test \
|
||||
db_compaction_test \
|
||||
db_merge_operator_test \
|
||||
db_sst_test \
|
||||
db_test \
|
||||
db_test2 \
|
||||
db_universal_compaction_test \
|
||||
db_wal_test \
|
||||
column_family_test \
|
||||
external_sst_file_test \
|
||||
import_column_family_test \
|
||||
fault_injection_test \
|
||||
file_reader_writer_test \
|
||||
inlineskiplist_test \
|
||||
manual_compaction_test \
|
||||
persistent_cache_test \
|
||||
table_test \
|
||||
transaction_test \
|
||||
transaction_lock_mgr_test \
|
||||
write_prepared_transaction_test \
|
||||
write_unprepared_transaction_test \
|
||||
|
||||
ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
TESTS += folly_synchronization_distributed_mutex_test
|
||||
PARALLEL_TEST += folly_synchronization_distributed_mutex_test
|
||||
TESTS_PASSING_ASC = folly_synchronization_distributed_mutex_test
|
||||
endif
|
||||
|
||||
# options_settable_test doesn't pass with UBSAN as we use hack in the test
|
||||
ifdef COMPILE_WITH_UBSAN
|
||||
TESTS := $(shell echo $(TESTS) | sed 's/\boptions_settable_test\b//g')
|
||||
endif
|
||||
ifdef ASSERT_STATUS_CHECKED
|
||||
# This is a new check for which we will add support incrementally. This
|
||||
# list can be removed once support is fully added.
|
||||
TESTS_PASSING_ASC = \
|
||||
arena_test \
|
||||
autovector_test \
|
||||
cache_test \
|
||||
lru_cache_test \
|
||||
blob_file_addition_test \
|
||||
blob_file_builder_test \
|
||||
blob_file_garbage_test \
|
||||
blob_file_reader_test \
|
||||
bloom_test \
|
||||
cassandra_format_test \
|
||||
cassandra_row_merge_test \
|
||||
cassandra_serialize_test \
|
||||
cleanable_test \
|
||||
coding_test \
|
||||
crc32c_test \
|
||||
dbformat_test \
|
||||
db_basic_test \
|
||||
db_flush_test \
|
||||
db_with_timestamp_basic_test \
|
||||
db_with_timestamp_compaction_test \
|
||||
db_options_test \
|
||||
db_properties_test \
|
||||
db_secondary_test \
|
||||
options_file_test \
|
||||
defer_test \
|
||||
filename_test \
|
||||
dynamic_bloom_test \
|
||||
env_basic_test \
|
||||
# TODO: finish fixing all tests to pass this check
|
||||
TESTS_FAILING_ASC = \
|
||||
c_test \
|
||||
db_test \
|
||||
db_test2 \
|
||||
env_test \
|
||||
env_logger_test \
|
||||
event_logger_test \
|
||||
error_handler_fs_test \
|
||||
auto_roll_logger_test \
|
||||
file_indexer_test \
|
||||
flush_job_test \
|
||||
hash_table_test \
|
||||
hash_test \
|
||||
heap_test \
|
||||
histogram_test \
|
||||
inlineskiplist_test \
|
||||
io_posix_test \
|
||||
iostats_context_test \
|
||||
ldb_cmd_test \
|
||||
memkind_kmem_allocator_test \
|
||||
merger_test \
|
||||
mock_env_test \
|
||||
object_registry_test \
|
||||
prefix_test \
|
||||
repair_test \
|
||||
configurable_test \
|
||||
options_settable_test \
|
||||
options_test \
|
||||
random_test \
|
||||
range_del_aggregator_test \
|
||||
sst_file_reader_test \
|
||||
range_tombstone_fragmenter_test \
|
||||
repeatable_thread_test \
|
||||
skiplist_test \
|
||||
slice_test \
|
||||
sst_dump_test \
|
||||
statistics_test \
|
||||
stats_history_test \
|
||||
thread_local_test \
|
||||
trace_analyzer_test \
|
||||
env_timed_test \
|
||||
filelock_test \
|
||||
timer_queue_test \
|
||||
timer_test \
|
||||
options_util_test \
|
||||
persistent_cache_test \
|
||||
util_merge_operators_test \
|
||||
block_cache_trace_analyzer_test \
|
||||
block_cache_tracer_test \
|
||||
cache_simulator_test \
|
||||
sim_cache_test \
|
||||
version_builder_test \
|
||||
version_edit_test \
|
||||
work_queue_test \
|
||||
write_controller_test \
|
||||
compaction_iterator_test \
|
||||
compaction_job_test \
|
||||
compaction_job_stats_test \
|
||||
io_tracer_test \
|
||||
merge_helper_test \
|
||||
memtable_list_test \
|
||||
flush_job_test \
|
||||
block_based_filter_block_test \
|
||||
block_fetcher_test \
|
||||
full_filter_block_test \
|
||||
partitioned_filter_block_test \
|
||||
column_family_test \
|
||||
file_reader_writer_test \
|
||||
corruption_test \
|
||||
db_universal_compaction_test \
|
||||
import_column_family_test \
|
||||
memory_test \
|
||||
table_test \
|
||||
range_locking_test \
|
||||
testutil_test \
|
||||
|
||||
ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
TESTS_PASSING_ASC += folly_synchronization_distributed_mutex_test
|
||||
# Since we have very few ASC exclusions left, excluding them from
|
||||
# the build is the most convenient way to exclude them from testing
|
||||
TESTS := $(filter-out $(TESTS_FAILING_ASC),$(TESTS))
|
||||
endif
|
||||
|
||||
# Enable building all unit tests, but use check_some to run only tests
|
||||
# known to pass ASC (ASSERT_STATUS_CHECKED)
|
||||
ROCKSDBTESTS_SUBSET ?= $(TESTS_PASSING_ASC)
|
||||
# Alternate: only build unit tests known to pass ASC, and run them
|
||||
# with make check
|
||||
#TESTS := $(filter $(TESTS_PASSING_ASC),$(TESTS))
|
||||
#PARALLEL_TEST := $(filter $(TESTS_PASSING_ASC),$(PARALLEL_TEST))
|
||||
else
|
||||
ROCKSDBTESTS_SUBSET ?= $(TESTS)
|
||||
endif
|
||||
ROCKSDBTESTS_SUBSET ?= $(TESTS)
|
||||
|
||||
# env_test - suspicious use of test::TmpDir
|
||||
# deletefile_test - serial because it generates giant temporary files in
|
||||
# its various tests. Parallel can fill up your /dev/shm
|
||||
NON_PARALLEL_TEST = \
|
||||
env_test \
|
||||
deletefile_test \
|
||||
|
||||
PARALLEL_TEST = $(filter-out $(NON_PARALLEL_TEST), $(TESTS))
|
||||
|
||||
# Not necessarily well thought out or up-to-date, but matches old list
|
||||
TESTS_PLATFORM_DEPENDENT := \
|
||||
db_basic_test \
|
||||
db_with_timestamp_basic_test \
|
||||
db_blob_basic_test \
|
||||
db_encryption_test \
|
||||
db_test2 \
|
||||
external_sst_file_basic_test \
|
||||
@@ -704,6 +560,7 @@ TESTS_PLATFORM_DEPENDENT := \
|
||||
io_posix_test \
|
||||
hash_test \
|
||||
random_test \
|
||||
ribbon_test \
|
||||
thread_local_test \
|
||||
work_queue_test \
|
||||
rate_limiter_test \
|
||||
@@ -769,13 +626,44 @@ else
|
||||
LIBRARY=$(STATIC_LIBRARY)
|
||||
TEST_LIBRARY=$(STATIC_TEST_LIBRARY)
|
||||
TOOLS_LIBRARY=$(STATIC_TOOLS_LIBRARY)
|
||||
STRESS_LIBRARY=$(STATIC_STRESS_LIBRARY)
|
||||
endif
|
||||
STRESS_LIBRARY=$(STATIC_STRESS_LIBRARY)
|
||||
|
||||
ROCKSDB_MAJOR = $(shell egrep "ROCKSDB_MAJOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
ROCKSDB_MINOR = $(shell egrep "ROCKSDB_MINOR.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
ROCKSDB_PATCH = $(shell egrep "ROCKSDB_PATCH.[0-9]" include/rocksdb/version.h | cut -d ' ' -f 3)
|
||||
|
||||
# If NO_UPDATE_BUILD_VERSION is set we don't update util/build_version.cc, but
|
||||
# the file needs to already exist or else the build will fail
|
||||
ifndef NO_UPDATE_BUILD_VERSION
|
||||
|
||||
# By default, use the current date-time as the date. If there are no changes,
|
||||
# we will use the last commit date instead.
|
||||
build_date := $(shell date "+%Y-%m-%d %T")
|
||||
|
||||
ifdef FORCE_GIT_SHA
|
||||
git_sha := $(FORCE_GIT_SHA)
|
||||
git_mod := 1
|
||||
git_date := $(build_date)
|
||||
else
|
||||
git_sha := $(shell git rev-parse HEAD 2>/dev/null)
|
||||
git_tag := $(shell git symbolic-ref -q --short HEAD 2> /dev/null || git describe --tags --exact-match 2>/dev/null)
|
||||
git_mod := $(shell git diff-index HEAD --quiet 2>/dev/null; echo $$?)
|
||||
git_date := $(shell git log -1 --date=format:"%Y-%m-%d %T" --format="%ad" 2>/dev/null)
|
||||
endif
|
||||
gen_build_version = sed -e s/@GIT_SHA@/$(git_sha)/ -e s:@GIT_TAG@:"$(git_tag)": -e s/@GIT_MOD@/"$(git_mod)"/ -e s/@BUILD_DATE@/"$(build_date)"/ -e s/@GIT_DATE@/"$(git_date)"/ util/build_version.cc.in
|
||||
|
||||
# Record the version of the source that we are compiling.
|
||||
# We keep a record of the git revision in this file. It is then built
|
||||
# as a regular source file as part of the compilation process.
|
||||
# One can run "strings executable_filename | grep _build_" to find
|
||||
# the version of the source that we used to build the executable file.
|
||||
util/build_version.cc: $(filter-out $(OBJ_DIR)/util/build_version.o, $(LIB_OBJECTS)) util/build_version.cc.in
|
||||
$(AM_V_GEN)rm -f $@-t
|
||||
$(AM_V_at)$(gen_build_version) > $@
|
||||
endif
|
||||
CLEAN_FILES += util/build_version.cc
|
||||
|
||||
default: all
|
||||
|
||||
#-----------------------------------------------
|
||||
@@ -823,7 +711,8 @@ endif # PLATFORM_SHARED_EXT
|
||||
analyze tools tools_lib \
|
||||
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
|
||||
blackbox_crash_test_with_best_efforts_recovery \
|
||||
blackbox_crash_test_with_ts whitebox_crash_test_with_ts
|
||||
|
||||
|
||||
all: $(LIBRARY) $(BENCHMARKS) tools tools_lib test_libs $(TESTS)
|
||||
@@ -939,7 +828,7 @@ gen_parallel_tests:
|
||||
# 107.816 PASS t/DBTest.EncodeDecompressedBlockSizeTest
|
||||
#
|
||||
slow_test_regexp = \
|
||||
^.*SnapshotConcurrentAccessTest.*$$|^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$
|
||||
^.*SnapshotConcurrentAccessTest.*$$|^.*SeqAdvanceConcurrentTest.*$$|^t/run-table_test-HarnessTest.Randomized$$|^t/run-db_test-.*(?:FileCreationRandomFailure|EncodeDecompressedBlockSizeTest)$$|^.*RecoverFromCorruptedWALWithoutFlush$$
|
||||
prioritize_long_running_tests = \
|
||||
perl -pe 's,($(slow_test_regexp)),100 $$1,' \
|
||||
| sort -k1,1gr \
|
||||
@@ -1061,6 +950,8 @@ crash_test_with_txn: whitebox_crash_test_with_txn blackbox_crash_test_with_txn
|
||||
|
||||
crash_test_with_best_efforts_recovery: blackbox_crash_test_with_best_efforts_recovery
|
||||
|
||||
crash_test_with_ts: whitebox_crash_test_with_ts blackbox_crash_test_with_ts
|
||||
|
||||
blackbox_crash_test: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --simple blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
$(PYTHON) -u tools/db_crashtest.py blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
@@ -1074,6 +965,9 @@ blackbox_crash_test_with_txn: db_stress
|
||||
blackbox_crash_test_with_best_efforts_recovery: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --test_best_efforts_recovery blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
blackbox_crash_test_with_ts: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --enable_ts blackbox $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
ifeq ($(CRASH_TEST_KILL_ODD),)
|
||||
CRASH_TEST_KILL_ODD=888887
|
||||
endif
|
||||
@@ -1092,6 +986,10 @@ whitebox_crash_test_with_txn: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --txn whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
whitebox_crash_test_with_ts: db_stress
|
||||
$(PYTHON) -u tools/db_crashtest.py --enable_ts whitebox --random_kill_odd \
|
||||
$(CRASH_TEST_KILL_ODD) $(CRASH_TEST_EXT_ARGS)
|
||||
|
||||
asan_check: clean
|
||||
COMPILE_WITH_ASAN=1 $(MAKE) check -j32
|
||||
$(MAKE) clean
|
||||
@@ -1237,8 +1135,9 @@ analyze_incremental:
|
||||
$(MAKE) dbg
|
||||
|
||||
CLEAN_FILES += unity.cc
|
||||
unity.cc: Makefile
|
||||
unity.cc: Makefile util/build_version.cc.in
|
||||
rm -f $@ $@-t
|
||||
$(AM_V_at)$(gen_build_version) > util/build_version.cc
|
||||
for source_file in $(LIB_SOURCES); do \
|
||||
echo "#include \"$$source_file\"" >> $@-t; \
|
||||
done
|
||||
@@ -1318,11 +1217,11 @@ $(STATIC_TEST_LIBRARY): $(TEST_OBJECTS)
|
||||
$(AM_V_AR)rm -f $@ $(SHARED_TEST_LIBRARY)
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
$(STATIC_TOOLS_LIBRARY): $(BENCH_OBJECTS) $(TOOL_OBJECTS)
|
||||
$(STATIC_TOOLS_LIBRARY): $(TOOL_OBJECTS)
|
||||
$(AM_V_AR)rm -f $@ $(SHARED_TOOLS_LIBRARY)
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
$(STATIC_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS)
|
||||
$(STATIC_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(TESTUTIL)
|
||||
$(AM_V_AR)rm -f $@ $(SHARED_STRESS_LIBRARY)
|
||||
$(AM_V_at)$(AR) $(ARFLAGS) $@ $^
|
||||
|
||||
@@ -1334,7 +1233,7 @@ $(SHARED_TOOLS_LIBRARY): $(TOOL_OBJECTS) $(SHARED1)
|
||||
$(AM_V_AR)rm -f $@ $(STATIC_TOOLS_LIBRARY)
|
||||
$(AM_SHARE)
|
||||
|
||||
$(SHARED_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(SHARED_TOOLS_LIBRARY) $(SHARED1)
|
||||
$(SHARED_STRESS_LIBRARY): $(ANALYZE_OBJECTS) $(STRESS_OBJECTS) $(TESTUTIL) $(SHARED_TOOLS_LIBRARY) $(SHARED1)
|
||||
$(AM_V_AR)rm -f $@ $(STATIC_STRESS_LIBRARY)
|
||||
$(AM_SHARE)
|
||||
|
||||
@@ -1356,7 +1255,7 @@ folly_synchronization_distributed_mutex_test: $(OBJ_DIR)/third-party/folly/folly
|
||||
$(AM_LINK)
|
||||
endif
|
||||
|
||||
cache_bench: $(OBJ_DIR)/cache/cache_bench.o $(LIBRARY)
|
||||
cache_bench: $(OBJ_DIR)/cache/cache_bench.o $(CACHE_BENCH_OBJECTS) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
persistent_cache_bench: $(OBJ_DIR)/utilities/persistent_cache/persistent_cache_bench.o $(LIBRARY)
|
||||
@@ -1416,6 +1315,9 @@ hash_test: $(OBJ_DIR)/util/hash_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
random_test: $(OBJ_DIR)/util/random_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
ribbon_test: $(OBJ_DIR)/util/ribbon_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
option_change_migration_test: $(OBJ_DIR)/utilities/option_change_migration/option_change_migration_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1461,6 +1363,12 @@ slice_transform_test: $(OBJ_DIR)/util/slice_transform_test.o $(TEST_LIBRARY) $(L
|
||||
db_basic_test: $(OBJ_DIR)/db/db_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_basic_test: $(OBJ_DIR)/db/blob/db_blob_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_compaction_test: $(OBJ_DIR)/db/blob/db_blob_compaction_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_with_timestamp_basic_test: $(OBJ_DIR)/db/db_with_timestamp_basic_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1509,6 +1417,9 @@ db_inplace_update_test: $(OBJ_DIR)/db/db_inplace_update_test.o $(TEST_LIBRARY) $
|
||||
db_iterator_test: $(OBJ_DIR)/db/db_iterator_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_kv_checksum_test: $(OBJ_DIR)/db/db_kv_checksum_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_memtable_test: $(OBJ_DIR)/db/db_memtable_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1631,6 +1542,9 @@ compaction_job_test: $(OBJ_DIR)/db/compaction/compaction_job_test.o $(TEST_LIBRA
|
||||
compaction_job_stats_test: $(OBJ_DIR)/db/compaction/compaction_job_stats_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
compaction_service_test: $(OBJ_DIR)/db/compaction/compaction_service_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
compact_on_deletion_collector_test: $(OBJ_DIR)/utilities/table_properties_collectors/compact_on_deletion_collector_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1784,6 +1698,9 @@ compact_files_test: $(OBJ_DIR)/db/compact_files_test.o $(TEST_LIBRARY) $(LIBRARY
|
||||
configurable_test: options/configurable_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
customizable_test: options/customizable_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
options_test: $(OBJ_DIR)/options/options_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1835,7 +1752,7 @@ write_callback_test: $(OBJ_DIR)/db/write_callback_test.o $(TEST_LIBRARY) $(LIBRA
|
||||
heap_test: $(OBJ_DIR)/util/heap_test.o $(GTEST)
|
||||
$(AM_LINK)
|
||||
|
||||
transaction_lock_mgr_test: utilities/transactions/transaction_lock_mgr_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
point_lock_manager_test: utilities/transactions/lock/point/point_lock_manager_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
transaction_test: $(OBJ_DIR)/utilities/transactions/transaction_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
@@ -1889,13 +1806,16 @@ blob_db_test: $(OBJ_DIR)/utilities/blob_db/blob_db_test.o $(TEST_LIBRARY) $(LIBR
|
||||
repeatable_thread_test: $(OBJ_DIR)/util/repeatable_thread_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
range_locking_test: utilities/transactions/lock/range/range_locking_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
range_tombstone_fragmenter_test: $(OBJ_DIR)/db/range_tombstone_fragmenter_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
sst_file_reader_test: $(OBJ_DIR)/table/sst_file_reader_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_secondary_test: $(OBJ_DIR)/db/db_impl/db_secondary_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
db_secondary_test: $(OBJ_DIR)/db/db_secondary_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
block_cache_tracer_test: $(OBJ_DIR)/trace_replay/block_cache_tracer_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
@@ -1913,6 +1833,9 @@ blob_file_addition_test: $(OBJ_DIR)/db/blob/blob_file_addition_test.o $(TEST_LIB
|
||||
blob_file_builder_test: $(OBJ_DIR)/db/blob/blob_file_builder_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_file_cache_test: $(OBJ_DIR)/db/blob/blob_file_cache_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
blob_file_garbage_test: $(OBJ_DIR)/db/blob/blob_file_garbage_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
@@ -1940,6 +1863,15 @@ io_tracer_parser_test: $(OBJ_DIR)/tools/io_tracer_parser_test.o $(OBJ_DIR)/tools
|
||||
io_tracer_parser: $(OBJ_DIR)/tools/io_tracer_parser.o $(TOOLS_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
db_blob_corruption_test: $(OBJ_DIR)/db/blob/db_blob_corruption_test.o $(TEST_LIBRARY) $(LIBRARY)
|
||||
$(AM_LINK)
|
||||
|
||||
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)
|
||||
|
||||
#-------------------------------------------------
|
||||
# make install related stuff
|
||||
PREFIX ?= /usr/local
|
||||
@@ -1964,6 +1896,10 @@ install-headers: gen-pc
|
||||
for header in `$(FIND) "include/rocksdb" -type f -name *.h`; do \
|
||||
install -C -m 644 $$header $(DESTDIR)/$(PREFIX)/$$header; \
|
||||
done
|
||||
for header in $(ROCKSDB_PLUGIN_HEADERS); do \
|
||||
install -d $(DESTDIR)/$(PREFIX)/include/rocksdb/`dirname $$header`; \
|
||||
install -C -m 644 $$header $(DESTDIR)/$(PREFIX)/include/rocksdb/$$header; \
|
||||
done
|
||||
install -C -m 644 rocksdb.pc $(INSTALL_LIBDIR)/pkgconfig/rocksdb.pc
|
||||
|
||||
install-static: install-headers $(LIBRARY)
|
||||
@@ -2047,11 +1983,11 @@ BZIP2_DOWNLOAD_BASE ?= https://sourceware.org/pub/bzip2
|
||||
SNAPPY_VER ?= 1.1.8
|
||||
SNAPPY_SHA256 ?= 16b677f07832a612b0836178db7f374e414f94657c138e6993cbfc5dcc58651f
|
||||
SNAPPY_DOWNLOAD_BASE ?= https://github.com/google/snappy/archive
|
||||
LZ4_VER ?= 1.9.2
|
||||
LZ4_SHA256 ?= 658ba6191fa44c92280d4aa2c271b0f4fbc0e34d249578dd05e50e76d0e5efcc
|
||||
LZ4_VER ?= 1.9.3
|
||||
LZ4_SHA256 ?= 030644df4611007ff7dc962d981f390361e6c97a34e5cbc393ddfbe019ffe2c1
|
||||
LZ4_DOWNLOAD_BASE ?= https://github.com/lz4/lz4/archive
|
||||
ZSTD_VER ?= 1.4.4
|
||||
ZSTD_SHA256 ?= a364f5162c7d1a455cc915e8e3cf5f4bd8b75d09bc0f53965b0c9ca1383c52c8
|
||||
ZSTD_VER ?= 1.4.9
|
||||
ZSTD_SHA256 ?= acf714d98e3db7b876e5b540cbf6dee298f60eb3c0723104f6d3f065cd60d6a8
|
||||
ZSTD_DOWNLOAD_BASE ?= https://github.com/facebook/zstd/archive
|
||||
CURL_SSL_OPTS ?= --tlsv1
|
||||
|
||||
@@ -2083,80 +2019,80 @@ ifeq ($(PLATFORM), OS_AIX)
|
||||
SNAPPY_MAKE_TARGET = libsnappy.la
|
||||
endif
|
||||
ifeq ($(PLATFORM), OS_OPENBSD)
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/openbsd
|
||||
JAVA_INCLUDE = -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/openbsd
|
||||
ROCKSDBJNILIB = librocksdbjni-openbsd$(ARCH).so
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-openbsd$(ARCH).jar
|
||||
ROCKSDB_JAR = rocksdbjni-$(ROCKSDB_JAVA_VERSION)-openbsd$(ARCH).jar
|
||||
endif
|
||||
|
||||
libz.a:
|
||||
-rm -rf zlib-$(ZLIB_VER)
|
||||
ifeq (,$(wildcard ./zlib-$(ZLIB_VER).tar.gz))
|
||||
zlib-$(ZLIB_VER).tar.gz:
|
||||
curl --fail --output zlib-$(ZLIB_VER).tar.gz --location ${ZLIB_DOWNLOAD_BASE}/zlib-$(ZLIB_VER).tar.gz
|
||||
endif
|
||||
ZLIB_SHA256_ACTUAL=`$(SHA256_CMD) zlib-$(ZLIB_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZLIB_SHA256)" != "$$ZLIB_SHA256_ACTUAL" ]; then \
|
||||
echo zlib-$(ZLIB_VER).tar.gz checksum mismatch, expected=\"$(ZLIB_SHA256)\" actual=\"$$ZLIB_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
libz.a: zlib-$(ZLIB_VER).tar.gz
|
||||
-rm -rf zlib-$(ZLIB_VER)
|
||||
tar xvzf zlib-$(ZLIB_VER).tar.gz
|
||||
cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${EXTRA_CFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' ./configure --static && $(MAKE)
|
||||
cd zlib-$(ZLIB_VER) && CFLAGS='-fPIC ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' ./configure --static && $(MAKE)
|
||||
cp zlib-$(ZLIB_VER)/libz.a .
|
||||
|
||||
libbz2.a:
|
||||
-rm -rf bzip2-$(BZIP2_VER)
|
||||
ifeq (,$(wildcard ./bzip2-$(BZIP2_VER).tar.gz))
|
||||
bzip2-$(BZIP2_VER).tar.gz:
|
||||
curl --fail --output bzip2-$(BZIP2_VER).tar.gz --location ${CURL_SSL_OPTS} ${BZIP2_DOWNLOAD_BASE}/bzip2-$(BZIP2_VER).tar.gz
|
||||
endif
|
||||
BZIP2_SHA256_ACTUAL=`$(SHA256_CMD) bzip2-$(BZIP2_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(BZIP2_SHA256)" != "$$BZIP2_SHA256_ACTUAL" ]; then \
|
||||
echo bzip2-$(BZIP2_VER).tar.gz checksum mismatch, expected=\"$(BZIP2_SHA256)\" actual=\"$$BZIP2_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
libbz2.a: bzip2-$(BZIP2_VER).tar.gz
|
||||
-rm -rf bzip2-$(BZIP2_VER)
|
||||
tar xvzf bzip2-$(BZIP2_VER).tar.gz
|
||||
cd bzip2-$(BZIP2_VER) && $(MAKE) CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${EXTRA_CFLAGS}' AR='ar ${EXTRA_ARFLAGS}'
|
||||
cd bzip2-$(BZIP2_VER) && $(MAKE) CFLAGS='-fPIC -O2 -g -D_FILE_OFFSET_BITS=64 ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' AR='ar ${EXTRA_ARFLAGS}'
|
||||
cp bzip2-$(BZIP2_VER)/libbz2.a .
|
||||
|
||||
libsnappy.a:
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
ifeq (,$(wildcard ./snappy-$(SNAPPY_VER).tar.gz))
|
||||
snappy-$(SNAPPY_VER).tar.gz:
|
||||
curl --fail --output snappy-$(SNAPPY_VER).tar.gz --location ${CURL_SSL_OPTS} ${SNAPPY_DOWNLOAD_BASE}/$(SNAPPY_VER).tar.gz
|
||||
endif
|
||||
SNAPPY_SHA256_ACTUAL=`$(SHA256_CMD) snappy-$(SNAPPY_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(SNAPPY_SHA256)" != "$$SNAPPY_SHA256_ACTUAL" ]; then \
|
||||
echo snappy-$(SNAPPY_VER).tar.gz checksum mismatch, expected=\"$(SNAPPY_SHA256)\" actual=\"$$SNAPPY_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
libsnappy.a: snappy-$(SNAPPY_VER).tar.gz
|
||||
-rm -rf snappy-$(SNAPPY_VER)
|
||||
tar xvzf snappy-$(SNAPPY_VER).tar.gz
|
||||
mkdir snappy-$(SNAPPY_VER)/build
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='${EXTRA_CFLAGS}' CXXFLAGS='${EXTRA_CXXFLAGS}' LDFLAGS='${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cd snappy-$(SNAPPY_VER)/build && CFLAGS='${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' CXXFLAGS='${JAVA_STATIC_DEPS_CXXFLAGS} ${EXTRA_CXXFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON ${PLATFORM_CMAKE_FLAGS} .. && $(MAKE) ${SNAPPY_MAKE_TARGET}
|
||||
cp snappy-$(SNAPPY_VER)/build/libsnappy.a .
|
||||
|
||||
liblz4.a:
|
||||
-rm -rf lz4-$(LZ4_VER)
|
||||
ifeq (,$(wildcard ./lz4-$(LZ4_VER).tar.gz))
|
||||
lz4-$(LZ4_VER).tar.gz:
|
||||
curl --fail --output lz4-$(LZ4_VER).tar.gz --location ${CURL_SSL_OPTS} ${LZ4_DOWNLOAD_BASE}/v$(LZ4_VER).tar.gz
|
||||
endif
|
||||
LZ4_SHA256_ACTUAL=`$(SHA256_CMD) lz4-$(LZ4_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(LZ4_SHA256)" != "$$LZ4_SHA256_ACTUAL" ]; then \
|
||||
echo lz4-$(LZ4_VER).tar.gz checksum mismatch, expected=\"$(LZ4_SHA256)\" actual=\"$$LZ4_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
liblz4.a: lz4-$(LZ4_VER).tar.gz
|
||||
-rm -rf lz4-$(LZ4_VER)
|
||||
tar xvzf lz4-$(LZ4_VER).tar.gz
|
||||
cd lz4-$(LZ4_VER)/lib && $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' all
|
||||
cd lz4-$(LZ4_VER)/lib && $(MAKE) CFLAGS='-fPIC -O2 ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' all
|
||||
cp lz4-$(LZ4_VER)/lib/liblz4.a .
|
||||
|
||||
libzstd.a:
|
||||
-rm -rf zstd-$(ZSTD_VER)
|
||||
ifeq (,$(wildcard ./zstd-$(ZSTD_VER).tar.gz))
|
||||
zstd-$(ZSTD_VER).tar.gz:
|
||||
curl --fail --output zstd-$(ZSTD_VER).tar.gz --location ${CURL_SSL_OPTS} ${ZSTD_DOWNLOAD_BASE}/v$(ZSTD_VER).tar.gz
|
||||
endif
|
||||
ZSTD_SHA256_ACTUAL=`$(SHA256_CMD) zstd-$(ZSTD_VER).tar.gz | cut -d ' ' -f 1`; \
|
||||
if [ "$(ZSTD_SHA256)" != "$$ZSTD_SHA256_ACTUAL" ]; then \
|
||||
echo zstd-$(ZSTD_VER).tar.gz checksum mismatch, expected=\"$(ZSTD_SHA256)\" actual=\"$$ZSTD_SHA256_ACTUAL\"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
libzstd.a: zstd-$(ZSTD_VER).tar.gz
|
||||
-rm -rf zstd-$(ZSTD_VER)
|
||||
tar xvzf zstd-$(ZSTD_VER).tar.gz
|
||||
cd zstd-$(ZSTD_VER)/lib && DESTDIR=. PREFIX= $(MAKE) CFLAGS='-fPIC -O2 ${EXTRA_CFLAGS}' install
|
||||
cd zstd-$(ZSTD_VER)/lib && DESTDIR=. PREFIX= $(MAKE) CFLAGS='-fPIC -O2 ${JAVA_STATIC_DEPS_CCFLAGS} ${EXTRA_CFLAGS}' LDFLAGS='${JAVA_STATIC_DEPS_LDFLAGS} ${EXTRA_LDFLAGS}' libzstd.a
|
||||
cp zstd-$(ZSTD_VER)/lib/libzstd.a .
|
||||
|
||||
# A version of each $(LIB_OBJECTS) compiled with -fPIC and a fixed set of static compression libraries
|
||||
@@ -2165,14 +2101,23 @@ JAVA_COMPRESSIONS = libz.a libbz2.a libsnappy.a liblz4.a libzstd.a
|
||||
endif
|
||||
|
||||
JAVA_STATIC_FLAGS = -DZLIB -DBZIP2 -DSNAPPY -DLZ4 -DZSTD
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib/include
|
||||
ifneq ($(findstring rocksdbjavastatic, $(MAKECMDGOALS)),)
|
||||
JAVA_STATIC_INCLUDES = -I./zlib-$(ZLIB_VER) -I./bzip2-$(BZIP2_VER) -I./snappy-$(SNAPPY_VER) -I./snappy-$(SNAPPY_VER)/build -I./lz4-$(LZ4_VER)/lib -I./zstd-$(ZSTD_VER)/lib -I./zstd-$(ZSTD_VER)/lib/dictBuilder
|
||||
|
||||
ifneq ($(findstring rocksdbjavastatic, $(filter-out rocksdbjavastatic_deps, $(MAKECMDGOALS))),)
|
||||
CXXFLAGS += $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES)
|
||||
CFLAGS += $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES)
|
||||
CFLAGS += $(JAVA_STATIC_FLAGS) $(JAVA_STATIC_INCLUDES)
|
||||
endif
|
||||
rocksdbjavastatic: $(LIB_OBJECTS) $(JAVA_COMPRESSIONS)
|
||||
cd java;$(MAKE) javalib;
|
||||
rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
rocksdbjavastatic:
|
||||
ifeq ($(JAVA_HOME),)
|
||||
$(error JAVA_HOME is not set)
|
||||
endif
|
||||
$(MAKE) rocksdbjavastatic_deps
|
||||
$(MAKE) rocksdbjavastatic_libobjects
|
||||
$(MAKE) rocksdbjavastatic_javalib
|
||||
|
||||
rocksdbjavastatic_javalib:
|
||||
cd java; SHA256_CMD='$(SHA256_CMD)' $(MAKE) javalib
|
||||
rm -f java/target/$(ROCKSDBJNILIB)
|
||||
$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC \
|
||||
-o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) \
|
||||
$(LIB_OBJECTS) $(COVERAGEFLAGS) \
|
||||
@@ -2189,6 +2134,10 @@ rocksdbjavastatic: $(LIB_OBJECTS) $(JAVA_COMPRESSIONS)
|
||||
openssl sha1 java/target/$(ROCKSDB_JAVADOCS_JAR) | sed 's/.*= \([0-9a-f]*\)/\1/' > java/target/$(ROCKSDB_JAVADOCS_JAR).sha1
|
||||
openssl sha1 java/target/$(ROCKSDB_SOURCES_JAR) | sed 's/.*= \([0-9a-f]*\)/\1/' > java/target/$(ROCKSDB_SOURCES_JAR).sha1
|
||||
|
||||
rocksdbjavastatic_deps: $(JAVA_COMPRESSIONS)
|
||||
|
||||
rocksdbjavastatic_libobjects: $(LIB_OBJECTS)
|
||||
|
||||
rocksdbjavastaticrelease: rocksdbjavastatic
|
||||
cd java/crossbuild && (vagrant destroy -f || true) && vagrant up linux32 && vagrant halt linux32 && vagrant up linux64 && vagrant halt linux64 && vagrant up linux64-musl && vagrant halt linux64-musl
|
||||
cd java;jar -cf target/$(ROCKSDB_JAR_ALL) HISTORY*.md
|
||||
@@ -2204,7 +2153,7 @@ rocksdbjavastaticreleasedocker: rocksdbjavastatic rocksdbjavastaticdockerx86 roc
|
||||
|
||||
rocksdbjavastaticdockerx86:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_x86-be --platform linux/386 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:centos6_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerx86_64:
|
||||
mkdir -p java/target
|
||||
@@ -2220,7 +2169,7 @@ rocksdbjavastaticdockerarm64v8:
|
||||
|
||||
rocksdbjavastaticdockerx86musl:
|
||||
mkdir -p java/target
|
||||
docker run --rm --name rocksdb_linux_x86-musl-be --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
docker run --rm --name rocksdb_linux_x86-musl-be --platform linux/386 --attach stdin --attach stdout --attach stderr --volume $(HOME)/.m2:/root/.m2:ro --volume `pwd`:/rocksdb-host:ro --volume /rocksdb-local-build --volume `pwd`/java/target:/rocksdb-java-target --env DEBUG_LEVEL=$(DEBUG_LEVEL) evolvedbinary/rocksjava:alpine3_x86-be /rocksdb-host/java/crossbuild/docker-build-linux-centos.sh
|
||||
|
||||
rocksdbjavastaticdockerx86_64musl:
|
||||
mkdir -p java/target
|
||||
@@ -2258,7 +2207,10 @@ jl/%.o: %.cc
|
||||
$(AM_V_CC)mkdir -p $(@D) && $(CXX) $(CXXFLAGS) -fPIC -c $< -o $@ $(COVERAGEFLAGS)
|
||||
|
||||
rocksdbjava: $(LIB_OBJECTS)
|
||||
$(AM_V_GEN)cd java;$(MAKE) javalib;
|
||||
ifeq ($(JAVA_HOME),)
|
||||
$(error JAVA_HOME is not set)
|
||||
endif
|
||||
$(AM_V_GEN)cd java; SHA256_CMD='$(SHA256_CMD)' $(MAKE) javalib;
|
||||
$(AM_V_at)rm -f ./java/target/$(ROCKSDBJNILIB)
|
||||
$(AM_V_at)$(CXX) $(CXXFLAGS) -I./java/. $(JAVA_INCLUDE) -shared -fPIC -o ./java/target/$(ROCKSDBJNILIB) $(JNI_NATIVE_SOURCES) $(LIB_OBJECTS) $(JAVA_LDFLAGS) $(COVERAGEFLAGS)
|
||||
$(AM_V_at)cd java;jar -cf target/$(ROCKSDB_JAR) HISTORY*.md
|
||||
@@ -2270,13 +2222,13 @@ jclean:
|
||||
cd java;$(MAKE) clean;
|
||||
|
||||
jtest_compile: rocksdbjava
|
||||
cd java;$(MAKE) java_test
|
||||
cd java; SHA256_CMD='$(SHA256_CMD)' $(MAKE) java_test
|
||||
|
||||
jtest_run:
|
||||
cd java;$(MAKE) run_test
|
||||
|
||||
jtest: rocksdbjava
|
||||
cd java;$(MAKE) sample;$(MAKE) test;
|
||||
cd java;$(MAKE) sample; SHA256_CMD='$(SHA256_CMD)' $(MAKE) test;
|
||||
$(PYTHON) tools/check_all_python.py # TODO peterd: find a better place for this check in CI targets
|
||||
|
||||
jdb_bench:
|
||||
@@ -2335,12 +2287,14 @@ endif
|
||||
# ---------------------------------------------------------------------------
|
||||
# Source files dependencies detection
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# If skip dependencies is ON, skip including the dep files
|
||||
ifneq ($(SKIP_DEPENDS), 1)
|
||||
DEPFILES = $(patsubst %.cc, $(OBJ_DIR)/%.cc.d, $(ALL_SOURCES))
|
||||
DEPFILES+ = $(patsubst %.c, $(OBJ_DIR)/%.c.d, $(LIB_SOURCES_C) $(TEST_MAIN_SOURCES_C))
|
||||
ifeq ($(USE_FOLLY_DISTRIBUTED_MUTEX),1)
|
||||
DEPFILES +=$(patsubst %.cpp, $(OBJ_DIR)/%.cpp.d, $(FOLLY_SOURCES))
|
||||
endif
|
||||
endif
|
||||
|
||||
# Add proper dependency support so changing a .h file forces a .cc file to
|
||||
# rebuild.
|
||||
@@ -2380,20 +2334,9 @@ endif
|
||||
build_subset_tests: $(ROCKSDBTESTS_SUBSET)
|
||||
$(AM_V_GEN)if [ -n "$${ROCKSDBTESTS_SUBSET_TESTS_TO_FILE}" ]; then echo "$(ROCKSDBTESTS_SUBSET)" > "$${ROCKSDBTESTS_SUBSET_TESTS_TO_FILE}"; else echo "$(ROCKSDBTESTS_SUBSET)"; fi
|
||||
|
||||
# if the make goal is either "clean" or "format", we shouldn't
|
||||
# try to import the *.d files.
|
||||
# TODO(kailiu) The unfamiliarity of Make's conditions leads to the ugly
|
||||
# working solution.
|
||||
ifneq ($(MAKECMDGOALS),clean)
|
||||
ifneq ($(MAKECMDGOALS),format)
|
||||
ifneq ($(MAKECMDGOALS),jclean)
|
||||
ifneq ($(MAKECMDGOALS),jtest)
|
||||
ifneq ($(MAKECMDGOALS),package)
|
||||
ifneq ($(MAKECMDGOALS),analyze)
|
||||
# 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))
|
||||
ifneq ("$(ROCKS_DEP_RULES)", "")
|
||||
-include $(DEPFILES)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
This is the list of all known third-party plugins for RocksDB. If something is missing, please open a pull request to add it.
|
||||
|
||||
* [Dedupfs](https://github.com/ajkr/dedupfs): an example for plugin developers to reference
|
||||
* [ZenFS](https://github.com/westerndigitalcorporation/zenfs): a file system for zoned block devices
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://circleci.com/gh/facebook/rocksdb)
|
||||
[](https://travis-ci.org/facebook/rocksdb)
|
||||
[](https://ci.appveyor.com/project/Facebook/rocksdb/branch/master)
|
||||
[](http://140.211.168.68:8080/job/Rocksdb)
|
||||
[](http://140-211-168-68-openstack.osuosl.org:8080/job/rocksdb)
|
||||
|
||||
RocksDB is developed and maintained by Facebook Database Engineering Team.
|
||||
It is built on earlier work on [LevelDB](https://github.com/google/leveldb) by Sanjay Ghemawat (sanjay@google.com)
|
||||
|
||||
@@ -26,6 +26,9 @@ Learn more about those use cases in a Tech Talk by Ankit Gupta and Naveen Somasu
|
||||
## Yahoo
|
||||
Yahoo is using RocksDB as a storage engine for their biggest distributed data store Sherpa. Learn more about it here: http://yahooeng.tumblr.com/post/120730204806/sherpa-scales-new-heights
|
||||
|
||||
## Baidu
|
||||
[Apache Doris](http://doris.apache.org/master/en/) is a MPP analytical database engine released by Baidu. It [uses RocksDB](http://doris.apache.org/master/en/administrator-guide/operation/tablet-meta-tool.html) to manage its tablet's metadata.
|
||||
|
||||
## CockroachDB
|
||||
CockroachDB is an open-source geo-replicated transactional database. They are using RocksDB as their storage engine. Check out their github: https://github.com/cockroachdb/cockroach
|
||||
|
||||
@@ -44,7 +47,7 @@ Tango is using RocksDB as a graph storage to store all users' connection data an
|
||||
Turn is using RocksDB as a storage layer for their key/value store, serving at peak 2.4MM QPS out of different datacenters.
|
||||
Check out our RocksDB Protobuf merge operator at: https://github.com/vladb38/rocksdb_protobuf
|
||||
|
||||
## Santanader UK/Cloudera Profession Services
|
||||
## Santander UK/Cloudera Profession Services
|
||||
Check out their blog post: http://blog.cloudera.com/blog/2015/08/inside-santanders-near-real-time-data-ingest-architecture/
|
||||
|
||||
## Airbnb
|
||||
@@ -67,7 +70,7 @@ Pinterest's Object Retrieval System uses RocksDB for storage: https://www.youtub
|
||||
[VWO's](https://vwo.com/) Smart Code checker and URL helper uses RocksDB to store all the URLs where VWO's Smart Code is installed.
|
||||
|
||||
## quasardb
|
||||
[quasardb](https://www.quasardb.net) is a high-performance, distributed, transactional key-value database that integrates well with in-memory analytics engines such as Apache Spark.
|
||||
[quasardb](https://www.quasardb.net) is a high-performance, distributed, transactional key-value database that integrates well with in-memory analytics engines such as Apache Spark.
|
||||
quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
|
||||
## Netflix
|
||||
@@ -86,7 +89,7 @@ quasardb uses a heavily tuned RocksDB as its persistence layer.
|
||||
[Uber](http://eng.uber.com/cherami/) uses RocksDB as a durable and scalable task queue.
|
||||
|
||||
## 360 Pika
|
||||
[360](http://www.360.cn/) [Pika](https://github.com/Qihoo360/pika) is a nosql compatible with redis. With the huge amount of data stored, redis may suffer for a capacity bottleneck, and pika was born for solving it. It has widely been widely used in many company
|
||||
[360](http://www.360.cn/) [Pika](https://github.com/Qihoo360/pika) is a nosql compatible with redis. With the huge amount of data stored, redis may suffer for a capacity bottleneck, and pika was born for solving it. It has widely been used in many companies.
|
||||
|
||||
## LzLabs
|
||||
LzLabs is using RocksDB as a storage engine in their multi-database distributed framework to store application configuration and user data.
|
||||
@@ -96,16 +99,19 @@ LzLabs is using RocksDB as a storage engine in their multi-database distributed
|
||||
|
||||
## IOTA Foundation
|
||||
[IOTA Foundation](https://www.iota.org/) is using RocksDB in the [IOTA Reference Implementation (IRI)](https://github.com/iotaledger/iri) to store the local state of the Tangle. The Tangle is the first open-source distributed ledger powering the future of the Internet of Things.
|
||||
|
||||
|
||||
## Avrio Project
|
||||
[Avrio Project](http://avrio-project.github.io/avrio.network/) is using RocksDB in [Avrio ](https://github.com/avrio-project/avrio) to store blocks, account balances and data and other blockchain-releated data. Avrio is a multiblockchain decentralized cryptocurrency empowering monetary transactions.
|
||||
|
||||
|
||||
## Crux
|
||||
[Crux](https://github.com/juxt/crux) is a document database that uses RocksDB for local [EAV](https://en.wikipedia.org/wiki/Entity%E2%80%93attribute%E2%80%93value_model) index storage to enable point-in-time bitemporal Datalog queries. The "unbundled" architecture uses Kafka to provide horizontal scalability.
|
||||
|
||||
## Nebula Graph
|
||||
|
||||
[Nebula Graph](https://github.com/vesoft-inc/nebula) is a distributed, scalable, lightning-fast, open source graph database capable of hosting super large scale graphs with dozens of billions of vertices (nodes) and trillions of edges, with milliseconds of latency.
|
||||
|
||||
## YugabyteDB
|
||||
[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.
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ from util import ColorString
|
||||
# (This generates a TARGET file without user-specified dependency for unit
|
||||
# tests.)
|
||||
# $python3 buckifier/buckify_rocksdb.py \
|
||||
# '{"fake": { \
|
||||
# "extra_deps": [":test_dep", "//fakes/module:mock1"], \
|
||||
# "extra_compiler_flags": ["-DROCKSDB_LITE", "-Os"], \
|
||||
# } \
|
||||
# '{"fake": {
|
||||
# "extra_deps": [":test_dep", "//fakes/module:mock1"],
|
||||
# "extra_compiler_flags": ["-DROCKSDB_LITE", "-Os"]
|
||||
# }
|
||||
# }'
|
||||
# (Generated TARGETS file has test_dep and mock1 as dependencies for RocksDB
|
||||
# unit tests, and will use the extra_compiler_flags to compile the unit test
|
||||
@@ -69,25 +69,25 @@ def get_cc_files(repo_path):
|
||||
return cc_files
|
||||
|
||||
|
||||
# Get parallel tests from Makefile
|
||||
def get_parallel_tests(repo_path):
|
||||
# Get non_parallel tests from Makefile
|
||||
def get_non_parallel_tests(repo_path):
|
||||
Makefile = repo_path + "/Makefile"
|
||||
|
||||
s = set({})
|
||||
|
||||
found_parallel_tests = False
|
||||
found_non_parallel_tests = False
|
||||
for line in open(Makefile):
|
||||
line = line.strip()
|
||||
if line.startswith("PARALLEL_TEST ="):
|
||||
found_parallel_tests = True
|
||||
elif found_parallel_tests:
|
||||
if line.startswith("NON_PARALLEL_TEST ="):
|
||||
found_non_parallel_tests = True
|
||||
elif found_non_parallel_tests:
|
||||
if line.endswith("\\"):
|
||||
# remove the trailing \
|
||||
line = line[:-1]
|
||||
line = line.strip()
|
||||
s.add(line)
|
||||
else:
|
||||
# we consumed all the parallel tests
|
||||
# we consumed all the non_parallel tests
|
||||
break
|
||||
|
||||
return s
|
||||
@@ -123,23 +123,33 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk = parse_src_mk(repo_path)
|
||||
# get all .cc files
|
||||
cc_files = get_cc_files(repo_path)
|
||||
# get parallel tests from Makefile
|
||||
parallel_tests = get_parallel_tests(repo_path)
|
||||
# get non_parallel tests from Makefile
|
||||
non_parallel_tests = get_non_parallel_tests(repo_path)
|
||||
|
||||
if src_mk is None or cc_files is None or parallel_tests is None:
|
||||
if src_mk is None or cc_files is None or non_parallel_tests is None:
|
||||
return False
|
||||
|
||||
TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path)
|
||||
extra_argv = ""
|
||||
if len(sys.argv) >= 2:
|
||||
# Heuristically quote and canonicalize whitespace for inclusion
|
||||
# in how the file was generated.
|
||||
extra_argv = " '{0}'".format(" ".join(sys.argv[1].split()))
|
||||
|
||||
TARGETS = TARGETSBuilder("%s/TARGETS" % repo_path, extra_argv)
|
||||
|
||||
# rocksdb_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_lib",
|
||||
src_mk["LIB_SOURCES"] +
|
||||
# always add range_tree, it's only excluded on ppc64, which we don't use internally
|
||||
src_mk["RANGE_TREE_SOURCES"] +
|
||||
src_mk["TOOL_LIB_SOURCES"])
|
||||
# rocksdb_whole_archive_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_whole_archive_lib",
|
||||
src_mk["LIB_SOURCES"] +
|
||||
# always add range_tree, it's only excluded on ppc64, which we don't use internally
|
||||
src_mk["RANGE_TREE_SOURCES"] +
|
||||
src_mk["TOOL_LIB_SOURCES"],
|
||||
deps=None,
|
||||
headers=None,
|
||||
@@ -163,6 +173,11 @@ def generate_targets(repo_path, deps_map):
|
||||
src_mk.get("ANALYZER_LIB_SOURCES", []) +
|
||||
["test_util/testutil.cc"],
|
||||
[":rocksdb_lib"])
|
||||
# rocksdb_cache_bench_tools_lib
|
||||
TARGETS.add_library(
|
||||
"rocksdb_cache_bench_tools_lib",
|
||||
src_mk.get("CACHE_BENCH_LIB_SOURCES", []),
|
||||
[":rocksdb_lib"])
|
||||
# rocksdb_stress_lib
|
||||
TARGETS.add_rocksdb_library(
|
||||
"rocksdb_stress_lib",
|
||||
@@ -201,7 +216,7 @@ def generate_targets(repo_path, deps_map):
|
||||
TARGETS.register_test(
|
||||
test_target_name,
|
||||
test_src,
|
||||
test in parallel_tests,
|
||||
test not in non_parallel_tests,
|
||||
json.dumps(deps['extra_deps']),
|
||||
json.dumps(deps['extra_compiler_flags']))
|
||||
|
||||
|
||||
@@ -25,10 +25,11 @@ def pretty_list(lst, indent=8):
|
||||
|
||||
|
||||
class TARGETSBuilder(object):
|
||||
def __init__(self, path):
|
||||
def __init__(self, path, extra_argv):
|
||||
self.path = path
|
||||
self.targets_file = open(path, 'wb')
|
||||
header = targets_cfg.rocksdb_target_header_template
|
||||
header = targets_cfg.rocksdb_target_header_template.format(
|
||||
extra_argv=extra_argv)
|
||||
self.targets_file.write(header.encode("utf-8"))
|
||||
self.total_lib = 0
|
||||
self.total_bin = 0
|
||||
@@ -79,26 +80,25 @@ class TARGETSBuilder(object):
|
||||
|
||||
def add_c_test(self):
|
||||
self.targets_file.write(b"""
|
||||
if not is_opt_mode:
|
||||
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,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
)
|
||||
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,
|
||||
deps = [":rocksdb_test_lib"],
|
||||
) if not is_opt_mode else None
|
||||
|
||||
if not is_opt_mode:
|
||||
custom_unittest(
|
||||
"c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
)
|
||||
custom_unittest(
|
||||
name = "c_test",
|
||||
command = [
|
||||
native.package_name() + "/buckifier/rocks_test_runner.sh",
|
||||
"$(location :{})".format("c_test_bin"),
|
||||
],
|
||||
type = "simple",
|
||||
) if not is_opt_mode else None
|
||||
""")
|
||||
|
||||
def register_test(self,
|
||||
|
||||
+21
-14
@@ -5,7 +5,8 @@ from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
rocksdb_target_header_template = \
|
||||
"""# This file \100generated by `python3 buckifier/buckify_rocksdb.py`
|
||||
"""# This file \100generated by:
|
||||
#$ python3 buckifier/buckify_rocksdb.py{extra_argv}
|
||||
# --> DO NOT EDIT MANUALLY <--
|
||||
# This file is a Facebook-specific integration for buck builds, so can
|
||||
# only be validated by Facebook employees.
|
||||
@@ -16,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",
|
||||
@@ -31,10 +32,10 @@ ROCKSDB_EXTERNAL_DEPS = [
|
||||
("zlib", None, "z"),
|
||||
("gflags", None, "gflags"),
|
||||
("lz4", None, "lz4"),
|
||||
("zstd", None),
|
||||
("zstd", None, "zstd"),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_DEPS = [
|
||||
ROCKSDB_OS_DEPS_0 = [
|
||||
(
|
||||
"linux",
|
||||
["third-party//numa:numa", "third-party//liburing:uring", "third-party//tbb:tbb"],
|
||||
@@ -45,7 +46,7 @@ ROCKSDB_OS_DEPS = [
|
||||
),
|
||||
]
|
||||
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS = [
|
||||
ROCKSDB_OS_PREPROCESSOR_FLAGS_0 = [
|
||||
(
|
||||
"linux",
|
||||
[
|
||||
@@ -93,17 +94,19 @@ ROCKSDB_PREPROCESSOR_FLAGS = [
|
||||
|
||||
# Added missing flags from output of build_detect_platform
|
||||
"-DROCKSDB_BACKTRACE",
|
||||
|
||||
# Directories with files for #include
|
||||
"-I" + REPO_PATH + "include/",
|
||||
"-I" + REPO_PATH,
|
||||
]
|
||||
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {
|
||||
# Directories with files for #include
|
||||
ROCKSDB_INCLUDE_PATHS = [
|
||||
"",
|
||||
"include",
|
||||
]
|
||||
|
||||
ROCKSDB_ARCH_PREPROCESSOR_FLAGS = {{
|
||||
"x86_64": [
|
||||
"-DHAVE_PCLMUL",
|
||||
],
|
||||
}
|
||||
}}
|
||||
|
||||
build_mode = read_config("fbcode", "build_mode")
|
||||
|
||||
@@ -111,18 +114,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,6 +147,7 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS{extra_external_deps},
|
||||
link_whole = {link_whole},
|
||||
@@ -160,6 +164,7 @@ cpp_library(
|
||||
os_deps = ROCKSDB_OS_DEPS,
|
||||
os_preprocessor_flags = ROCKSDB_OS_PREPROCESSOR_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = ROCKSDB_LIB_DEPS,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
@@ -172,6 +177,7 @@ cpp_binary(
|
||||
arch_preprocessor_flags = ROCKSDB_ARCH_PREPROCESSOR_FLAGS,
|
||||
compiler_flags = ROCKSDB_COMPILER_FLAGS,
|
||||
preprocessor_flags = ROCKSDB_PREPROCESSOR_FLAGS,
|
||||
include_paths = ROCKSDB_INCLUDE_PATHS,
|
||||
deps = [{deps}],
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS,
|
||||
)
|
||||
@@ -202,6 +208,7 @@ ROCKS_TESTS = [
|
||||
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,
|
||||
deps = [":rocksdb_test_lib"] + extra_deps,
|
||||
external_deps = ROCKSDB_EXTERNAL_DEPS + [
|
||||
("googletest", None, "gtest"),
|
||||
|
||||
@@ -45,8 +45,13 @@ if test -z "$OUTPUT"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# we depend on C++11
|
||||
PLATFORM_CXXFLAGS="-std=c++11"
|
||||
# we depend on C++11, but should be compatible with newer standards
|
||||
if [ "$ROCKSDB_CXX_STANDARD" ]; then
|
||||
PLATFORM_CXXFLAGS="-std=$ROCKSDB_CXX_STANDARD"
|
||||
else
|
||||
PLATFORM_CXXFLAGS="-std=c++11"
|
||||
fi
|
||||
|
||||
# we currently depend on POSIX platform
|
||||
COMMON_FLAGS="-DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX"
|
||||
|
||||
@@ -64,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
|
||||
@@ -170,9 +171,12 @@ case "$TARGET_OS" in
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -latomic"
|
||||
fi
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lpthread -lrt -ldl"
|
||||
if test $ROCKSDB_USE_IO_URING; then
|
||||
if test -z "$ROCKSDB_USE_IO_URING"; then
|
||||
ROCKSDB_USE_IO_URING=1
|
||||
fi
|
||||
if test "$ROCKSDB_USE_IO_URING" -ne 0; then
|
||||
# check for liburing
|
||||
$CXX $CFLAGS -x c++ - -luring -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -luring -o /dev/null 2>/dev/null <<EOF
|
||||
#include <liburing.h>
|
||||
int main() {
|
||||
struct io_uring ring;
|
||||
@@ -283,7 +287,7 @@ if [ "$CROSS_COMPILE" = "true" -o "$FBCODE_BUILD" = "true" ]; then
|
||||
else
|
||||
if ! test $ROCKSDB_DISABLE_FALLOCATE; then
|
||||
# Test whether fallocate is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
#include <linux/falloc.h>
|
||||
int main() {
|
||||
@@ -299,7 +303,7 @@ EOF
|
||||
if ! test $ROCKSDB_DISABLE_SNAPPY; then
|
||||
# Test whether Snappy library is installed
|
||||
# http://code.google.com/p/snappy/
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <snappy.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -314,7 +318,7 @@ EOF
|
||||
# Test whether gflags library is installed
|
||||
# http://gflags.github.io/gflags/
|
||||
# check if the namespace is gflags
|
||||
if $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
if $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace GFLAGS_NAMESPACE;
|
||||
int main() {}
|
||||
@@ -323,7 +327,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# check if namespace is gflags
|
||||
elif $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace gflags;
|
||||
int main() {}
|
||||
@@ -332,7 +336,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DGFLAGS=1 -DGFLAGS_NAMESPACE=gflags"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -lgflags"
|
||||
# check if namespace is google
|
||||
elif $CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
elif $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null << EOF
|
||||
#include <gflags/gflags.h>
|
||||
using namespace google;
|
||||
int main() {}
|
||||
@@ -345,7 +349,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZLIB; then
|
||||
# Test whether zlib library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zlib.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -358,7 +362,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BZIP; then
|
||||
# Test whether bzip library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <bzlib.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -371,7 +375,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_LZ4; then
|
||||
# Test whether lz4 library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <lz4.h>
|
||||
#include <lz4hc.h>
|
||||
int main() {}
|
||||
@@ -385,7 +389,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_ZSTD; then
|
||||
# Test whether zstd library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <zstd.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -398,7 +402,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_NUMA; then
|
||||
# Test whether numa is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -lnuma 2>/dev/null <<EOF
|
||||
#include <numa.h>
|
||||
#include <numaif.h>
|
||||
int main() {}
|
||||
@@ -412,7 +416,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_TBB; then
|
||||
# Test whether tbb is available
|
||||
$CXX $CFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $LDFLAGS -x c++ - -o /dev/null -ltbb 2>/dev/null <<EOF
|
||||
#include <tbb/tbb.h>
|
||||
int main() {}
|
||||
EOF
|
||||
@@ -425,7 +429,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_JEMALLOC; then
|
||||
# Test whether jemalloc is available
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -ljemalloc \
|
||||
2>/dev/null; then
|
||||
# This will enable some preprocessor identifiers in the Makefile
|
||||
JEMALLOC=1
|
||||
@@ -446,7 +450,7 @@ EOF
|
||||
fi
|
||||
if ! test $JEMALLOC && ! test $ROCKSDB_DISABLE_TCMALLOC; then
|
||||
# jemalloc is not available. Let's try tcmalloc
|
||||
if echo 'int main() {}' | $CXX $CFLAGS -x c++ - -o /dev/null \
|
||||
if echo 'int main() {}' | $CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null \
|
||||
-ltcmalloc 2>/dev/null; then
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -ltcmalloc"
|
||||
JAVA_LDFLAGS="$JAVA_LDFLAGS -ltcmalloc"
|
||||
@@ -455,7 +459,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_MALLOC_USABLE_SIZE; then
|
||||
# Test whether malloc_usable_size is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <malloc.h>
|
||||
int main() {
|
||||
size_t res = malloc_usable_size(0);
|
||||
@@ -470,7 +474,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_MEMKIND; then
|
||||
# Test whether memkind library is installed
|
||||
$CXX $CFLAGS $COMMON_FLAGS -lmemkind -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS $COMMON_FLAGS -lmemkind -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <memkind.h>
|
||||
int main() {
|
||||
memkind_malloc(MEMKIND_DAX_KMEM, 1024);
|
||||
@@ -486,7 +490,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_PTHREAD_MUTEX_ADAPTIVE_NP; then
|
||||
# Test whether PTHREAD_MUTEX_ADAPTIVE_NP mutex type is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <pthread.h>
|
||||
int main() {
|
||||
int x = PTHREAD_MUTEX_ADAPTIVE_NP;
|
||||
@@ -501,7 +505,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_BACKTRACE; then
|
||||
# Test whether backtrace is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
@@ -513,7 +517,7 @@ EOF
|
||||
COMMON_FLAGS="$COMMON_FLAGS -DROCKSDB_BACKTRACE"
|
||||
else
|
||||
# Test whether execinfo library is installed
|
||||
$CXX $CFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -lexecinfo -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <execinfo.h>
|
||||
int main() {
|
||||
void* frames[1];
|
||||
@@ -530,7 +534,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_PG; then
|
||||
# Test if -pg is supported
|
||||
$CXX $CFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -pg -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
int main() {
|
||||
return 0;
|
||||
}
|
||||
@@ -542,7 +546,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SYNC_FILE_RANGE; then
|
||||
# Test whether sync_file_range is supported for compatibility with an old glibc
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <fcntl.h>
|
||||
int main() {
|
||||
int fd = open("/dev/null", 0);
|
||||
@@ -556,7 +560,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_SCHED_GETCPU; then
|
||||
# Test whether sched_getcpu is supported
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sched.h>
|
||||
int main() {
|
||||
int cpuid = sched_getcpu();
|
||||
@@ -570,7 +574,7 @@ EOF
|
||||
|
||||
if ! test $ROCKSDB_DISABLE_AUXV_GETAUXVAL; then
|
||||
# Test whether getauxval is supported
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <sys/auxv.h>
|
||||
int main() {
|
||||
uint64_t auxv = getauxval(AT_HWCAP);
|
||||
@@ -598,7 +602,7 @@ fi
|
||||
# -Wshorten-64-to-32 breaks compilation on FreeBSD i386
|
||||
if ! [ "$TARGET_OS" = FreeBSD -a "$TARGET_ARCHITECTURE" = i386 ]; then
|
||||
# Test whether -Wshorten-64-to-32 is available
|
||||
$CXX $CFLAGS -x c++ - -o /dev/null -Wshorten-64-to-32 2>/dev/null <<EOF
|
||||
$CXX $PLATFORM_CXXFLAGS -x c++ - -o /dev/null -Wshorten-64-to-32 2>/dev/null <<EOF
|
||||
int main() {}
|
||||
EOF
|
||||
if [ "$?" = 0 ]; then
|
||||
@@ -655,6 +659,28 @@ else
|
||||
COMMON_FLAGS="$COMMON_FLAGS -mmacosx-version-min=10.12"
|
||||
PLATFORM_LDFLAGS="$PLATFORM_LDFLAGS -mmacosx-version-min=10.12"
|
||||
PLATFORM_SHARED_LDFLAGS="$PLATFORM_SHARED_LDFLAGS -mmacosx-version-min=10.12"
|
||||
PLATFORM_CMAKE_FLAGS="-DCMAKE_OSX_DEPLOYMENT_TARGET=10.12"
|
||||
JAVA_STATIC_DEPS_COMMON_FLAGS="-mmacosx-version-min=10.12"
|
||||
JAVA_STATIC_DEPS_LDFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
JAVA_STATIC_DEPS_CCFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
JAVA_STATIC_DEPS_CXXFLAGS="$JAVA_STATIC_DEPS_COMMON_FLAGS"
|
||||
fi
|
||||
fi
|
||||
|
||||
if test -n "`echo $TARGET_ARCHITECTURE | grep ^ppc64`"; then
|
||||
# check for GNU libc on ppc64
|
||||
$CXX -x c++ - -o /dev/null 2>/dev/null <<EOF
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <gnu/libc-version.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
printf("GNU libc version: %s\n", gnu_get_libc_version());
|
||||
return 0;
|
||||
}
|
||||
EOF
|
||||
if [ "$?" != 0 ]; then
|
||||
PPC_LIBC_IS_GNU=0
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -812,8 +838,12 @@ echo "CXX=$CXX" >> "$OUTPUT"
|
||||
echo "AR=$AR" >> "$OUTPUT"
|
||||
echo "PLATFORM=$PLATFORM" >> "$OUTPUT"
|
||||
echo "PLATFORM_LDFLAGS=$PLATFORM_LDFLAGS" >> "$OUTPUT"
|
||||
echo "PLATFORM_CMAKE_FLAGS=$PLATFORM_CMAKE_FLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_LDFLAGS=$JAVA_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_STATIC_LDFLAGS=$JAVA_STATIC_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_STATIC_DEPS_CCFLAGS=$JAVA_STATIC_DEPS_CCFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_STATIC_DEPS_CXXFLAGS=$JAVA_STATIC_DEPS_CXXFLAGS" >> "$OUTPUT"
|
||||
echo "JAVA_STATIC_DEPS_LDFLAGS=$JAVA_STATIC_DEPS_LDFLAGS" >> "$OUTPUT"
|
||||
echo "JAVAC_ARGS=$JAVAC_ARGS" >> "$OUTPUT"
|
||||
echo "VALGRIND_VER=$VALGRIND_VER" >> "$OUTPUT"
|
||||
echo "PLATFORM_CCFLAGS=$PLATFORM_CCFLAGS" >> "$OUTPUT"
|
||||
@@ -847,3 +877,6 @@ echo "LUA_PATH=$LUA_PATH" >> "$OUTPUT"
|
||||
if test -n "$USE_FOLLY_DISTRIBUTED_MUTEX"; then
|
||||
echo "USE_FOLLY_DISTRIBUTED_MUTEX=$USE_FOLLY_DISTRIBUTED_MUTEX" >> "$OUTPUT"
|
||||
fi
|
||||
if test -n "$PPC_LIBC_IS_GNU"; then
|
||||
echo "PPC_LIBC_IS_GNU=$PPC_LIBC_IS_GNU" >> "$OUTPUT"
|
||||
fi
|
||||
|
||||
@@ -120,7 +120,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 +133,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"
|
||||
@@ -150,10 +150,11 @@ CFLAGS+=" -DROCKSDB_PLATFORM_POSIX -DROCKSDB_LIB_IO_POSIX -DROCKSDB_FALLOCATE_PR
|
||||
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+=" -B$BINUTILS"
|
||||
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"
|
||||
|
||||
|
||||
+14
-12
@@ -52,15 +52,16 @@ else
|
||||
else
|
||||
echo "You didn't have clang-format-diff.py and/or clang-format available in your computer!"
|
||||
echo "You can download clang-format-diff.py by running: "
|
||||
echo " curl --location http://goo.gl/iUW1u2 -o ${CLANG_FORMAT_DIFF}"
|
||||
echo " curl --location https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o ${REPO_ROOT}/clang-format-diff.py"
|
||||
echo "You should make sure the downloaded script is not compromised."
|
||||
echo "You can download clang-format by running:"
|
||||
echo " brew install clang-format"
|
||||
echo " Or"
|
||||
echo " apt install clang-format"
|
||||
echo " This might work too:"
|
||||
echo " yum install git-clang-format"
|
||||
echo "Then, move both files (i.e. ${CLANG_FORMAT_DIFF} and clang-format) to some directory within PATH=${PATH}"
|
||||
echo "and make sure ${CLANG_FORMAT_DIFF} is executable."
|
||||
echo "Then make sure clang-format is available and executable from \$PATH:"
|
||||
echo " clang-format --version"
|
||||
exit 128
|
||||
fi
|
||||
# Check argparse pre-req on interpreter, or it will fail
|
||||
@@ -75,17 +76,16 @@ else
|
||||
exit 129
|
||||
fi
|
||||
# Unfortunately, some machines have a Python2 clang-format-diff.py
|
||||
# installed but only a Python3 interpreter installed. Rather than trying
|
||||
# different Python versions that might be installed, we can try migrating
|
||||
# the code to Python3 if it looks like Python2
|
||||
# installed but only a Python3 interpreter installed. Unfortunately,
|
||||
# automatic 2to3 migration is insufficient, so suggest downloading latest.
|
||||
if grep -q "print '" "$CFD_PATH" && \
|
||||
${PYTHON:-python3} --version | grep -q 'ython 3'; then
|
||||
if [ ! -f "$REPO_ROOT/.py3/clang-format-diff.py" ]; then
|
||||
echo "Migrating $CFD_PATH to Python3 in a hidden file"
|
||||
mkdir -p "$REPO_ROOT/.py3"
|
||||
${PYTHON:-python3} -m lib2to3 -w -n -o "$REPO_ROOT/.py3" "$CFD_PATH" > /dev/null || exit 128
|
||||
fi
|
||||
CFD_PATH="$REPO_ROOT/.py3/clang-format-diff.py"
|
||||
echo "You have clang-format-diff.py for Python 2 but are using a Python 3"
|
||||
echo "interpreter (${PYTHON:-python3})."
|
||||
echo "You can download clang-format-diff.py for Python 3 by running: "
|
||||
echo " curl --location https://raw.githubusercontent.com/llvm/llvm-project/main/clang/tools/clang-format/clang-format-diff.py -o ${REPO_ROOT}/clang-format-diff.py"
|
||||
echo "You should make sure the downloaded script is not compromised."
|
||||
exit 130
|
||||
fi
|
||||
CLANG_FORMAT_DIFF="${PYTHON:-python3} $CFD_PATH"
|
||||
# This had better work after all those checks
|
||||
@@ -136,9 +136,11 @@ then
|
||||
FORMAT_UPSTREAM_MERGE_BASE="$(git merge-base "$FORMAT_UPSTREAM" HEAD)"
|
||||
# Get the differences
|
||||
diffs=$(git diff -U0 "$FORMAT_UPSTREAM_MERGE_BASE" | $CLANG_FORMAT_DIFF -p 1)
|
||||
echo "Checking format of changes not yet in $FORMAT_UPSTREAM..."
|
||||
else
|
||||
# Check the format of uncommitted lines,
|
||||
diffs=$(git diff -U0 HEAD | $CLANG_FORMAT_DIFF -p 1)
|
||||
echo "Checking format of uncommitted changes..."
|
||||
fi
|
||||
|
||||
if [ -z "$diffs" ]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# to determine next steps to run
|
||||
|
||||
# Usage:
|
||||
# EMAIL=<email> ONCALL=<email> TRIGGER=<trigger> SUBSCRIBER=<email> rocks_ci.py <test-name>
|
||||
# EMAIL=<email> ONCALL=<email> TRIGGER=<trigger> SUBSCRIBER=<email> WORKINGDIR=<working_dir> rocksdb-lego-determinator <test-name>
|
||||
#
|
||||
# Input Value
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -11,7 +11,7 @@
|
||||
# ONCALL Email address to raise a task on failure
|
||||
# TRIGGER Trigger conditions for email. Valid values are fail, warn, all
|
||||
# SUBSCRIBER Email addresss to add as subscriber for task
|
||||
#
|
||||
# WORKINGDIR Working directory
|
||||
|
||||
#
|
||||
# Report configuration
|
||||
@@ -53,13 +53,19 @@ if [[ ! -z $REPORT_EMAIL || ! -z $CREATE_TASK ]]; then
|
||||
]"
|
||||
fi
|
||||
|
||||
# Working directory for the following command, default to current directory
|
||||
WORKING_DIR=.
|
||||
if [ ! -z $WORKINGDIR ]; then
|
||||
WORKING_DIR=$WORKINGDIR
|
||||
fi
|
||||
|
||||
#
|
||||
# Helper variables
|
||||
#
|
||||
CLEANUP_ENV="
|
||||
{
|
||||
'name':'Cleanup environment',
|
||||
'shell':'rm -rf /dev/shm/rocksdb && mkdir /dev/shm/rocksdb && (chmod +t /dev/shm || true) && make clean',
|
||||
'shell':'cd $WORKING_DIR; rm -rf /dev/shm/rocksdb && mkdir /dev/shm/rocksdb && (chmod +t /dev/shm || true) && make clean',
|
||||
'user':'root'
|
||||
}"
|
||||
|
||||
@@ -100,9 +106,7 @@ 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"
|
||||
@@ -112,6 +116,7 @@ SETUP_JAVA_ENV="export $HTTP_PROXY; export JAVA_HOME=/usr/local/jdk-8u60-64/; ex
|
||||
PARSER="'parser':'python build_tools/error_filter.py $1'"
|
||||
|
||||
CONTRUN_NAME="ROCKSDB_CONTRUN_NAME"
|
||||
SKIP_FORMAT_CHECKS="SKIP_FORMAT_BUCK_CHECKS=1"
|
||||
|
||||
# This code is getting called under various scenarios. What we care about is to
|
||||
# understand when it's called from nightly contruns because in that case we'll
|
||||
@@ -153,7 +158,7 @@ UNIT_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug version',
|
||||
'shell':'$SHM $DEBUG make $PARALLELISM check || $CONTRUN_NAME=check $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM check || $CONTRUN_NAME=check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -176,7 +181,7 @@ UNIT_TEST_NON_SHM_COMMANDS="[
|
||||
{
|
||||
'name':'Build and test RocksDB debug version',
|
||||
'timeout': 86400,
|
||||
'shell':'$NON_SHM $DEBUG make $PARALLELISM check || $CONTRUN_NAME=non_shm_check $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $NON_SHM $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM check || $CONTRUN_NAME=non_shm_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -197,7 +202,7 @@ RELEASE_BUILD_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB release',
|
||||
'shell':'make $PARALLEL_j release || $CONTRUN_NAME=release $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; make $PARALLEL_j release || $CONTRUN_NAME=release $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -218,7 +223,7 @@ UNIT_TEST_COMMANDS_481="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug version',
|
||||
'shell':'$SHM $GCC_481 $DEBUG make $PARALLELISM check || $CONTRUN_NAME=unit_gcc_481_check $TASK_CREATION_TOOL',
|
||||
'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
|
||||
},
|
||||
@@ -239,7 +244,7 @@ RELEASE_BUILD_COMMANDS_481="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB release on GCC 4.8.1',
|
||||
'shell':'$GCC_481 make $PARALLEL_j release || $CONTRUN_NAME=release_gcc481 $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $GCC_481 make $PARALLEL_j release || $CONTRUN_NAME=release_gcc481 $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -260,7 +265,7 @@ CLANG_UNIT_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and test RocksDB debug',
|
||||
'shell':'$CLANG $SHM $DEBUG make $PARALLELISM check || $CONTRUN_NAME=clang_check $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $CLANG $SHM $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM check || $CONTRUN_NAME=clang_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -281,7 +286,7 @@ CLANG_RELEASE_BUILD_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB release',
|
||||
'shell':'$CLANG make $PARALLEL_j release|| $CONTRUN_NAME=clang_release $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $CLANG make $PARALLEL_j release|| $CONTRUN_NAME=clang_release $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -302,7 +307,7 @@ CLANG_ANALYZE_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'RocksDB build and analyze',
|
||||
'shell':'$CLANG $SHM $DEBUG make $PARALLEL_j analyze || $CONTRUN_NAME=clang_analyze $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $CLANG $SHM $DEBUG make $PARALLEL_j analyze || $CONTRUN_NAME=clang_analyze $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -323,7 +328,7 @@ CODE_COV_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build, test and collect code coverage info',
|
||||
'shell':'$SHM $DEBUG make $PARALLELISM coverage || $CONTRUN_NAME=coverage $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM coverage || $CONTRUN_NAME=coverage $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -344,7 +349,7 @@ UNITY_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build, test unity test',
|
||||
'shell':'$SHM $DEBUG V=1 make J=1 unity_test || $CONTRUN_NAME=unity_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG V=1 make J=1 unity_test || $CONTRUN_NAME=unity_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -365,7 +370,7 @@ LITE_BUILD_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB debug version',
|
||||
'shell':'make J=1 LITE=1 all check || $CONTRUN_NAME=lite $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SKIP_FORMAT_CHECKS make J=1 LITE=1 all check || $CONTRUN_NAME=lite $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -385,7 +390,7 @@ REPORT_LITE_BINARY_SIZE_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Report RocksDB Lite binary size to scuba',
|
||||
'shell':'tools/report_lite_binary_size.sh',
|
||||
'shell':'cd $WORKING_DIR; tools/report_lite_binary_size.sh',
|
||||
'user':'root',
|
||||
},
|
||||
],
|
||||
@@ -404,14 +409,14 @@ STRESS_CRASH_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug crash tests',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test || $CONTRUN_NAME=crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test || $CONTRUN_NAME=crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -434,14 +439,14 @@ BLACKBOX_STRESS_CRASH_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug blackbox crash tests',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 blackbox_crash_test || $CONTRUN_NAME=blackbox_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 blackbox_crash_test || $CONTRUN_NAME=blackbox_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -464,14 +469,14 @@ WHITEBOX_STRESS_CRASH_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug whitebox crash tests',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 whitebox_crash_test || $CONTRUN_NAME=whitebox_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 whitebox_crash_test || $CONTRUN_NAME=whitebox_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -494,14 +499,14 @@ STRESS_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug crash tests with atomic flush',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -524,14 +529,44 @@ STRESS_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug crash tests with txn',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_txn || $CONTRUN_NAME=crash_test_with_txn $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_txn || $CONTRUN_NAME=crash_test_with_txn $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
$UPLOAD_DB_DIR,
|
||||
],
|
||||
$REPORT
|
||||
}
|
||||
]"
|
||||
|
||||
#
|
||||
# RocksDB stress/crash test with timestamp
|
||||
#
|
||||
STRESS_CRASH_TEST_WITH_TS_COMMANDS="[
|
||||
{
|
||||
'name':'Rocksdb Stress and Crash Test with ts',
|
||||
'oncall':'$ONCALL',
|
||||
'executeLocal': 'true',
|
||||
'timeout': 86400,
|
||||
'steps': [
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB debug stress tests',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 db_stress || $CONTRUN_NAME=db_stress $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
{
|
||||
'name':'Build and run RocksDB debug crash tests with ts',
|
||||
'timeout': 86400,
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH make J=1 crash_test_with_ts || $CONTRUN_NAME=crash_test_with_ts $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -553,7 +588,7 @@ WRITE_STRESS_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build and run RocksDB write stress tests',
|
||||
'shell':'make write_stress && python tools/write_stress_runner.py --runtime_sec=3600 --db=/tmp/rocksdb_write_stress || $CONTRUN_NAME=write_stress $TASK_CREATION_TOOL',
|
||||
'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',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -576,7 +611,7 @@ ASAN_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Test RocksDB debug under ASAN',
|
||||
'shell':'set -o pipefail && ($SHM $ASAN $DEBUG make $PARALLELISM asan_check || $CONTRUN_NAME=asan_check $TASK_CREATION_TOOL) |& /usr/facebook/ops/scripts/asan_symbolize.py -d',
|
||||
'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',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -599,7 +634,7 @@ ASAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug asan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test || $CONTRUN_NAME=asan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $SKIP_FORMAT_CHECKS make J=1 asan_crash_test || $CONTRUN_NAME=asan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -623,7 +658,7 @@ ASAN_BLACKBOX_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug blackbox asan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 blackbox_asan_crash_test || $CONTRUN_NAME=blackbox_asan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $SKIP_FORMAT_CHECKS make J=1 blackbox_asan_crash_test || $CONTRUN_NAME=blackbox_asan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -647,7 +682,7 @@ ASAN_WHITEBOX_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug whitebox asan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 whitebox_asan_crash_test || $CONTRUN_NAME=whitebox_asan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $SKIP_FORMAT_CHECKS make J=1 whitebox_asan_crash_test || $CONTRUN_NAME=whitebox_asan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -671,7 +706,7 @@ ASAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug asan_crash_test_with_atomic_flush',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test_with_atomic_flush || $CONTRUN_NAME=asan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $SKIP_FORMAT_CHECKS make J=1 asan_crash_test_with_atomic_flush || $CONTRUN_NAME=asan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -695,7 +730,7 @@ ASAN_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug asan_crash_test_with_txn',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH make J=1 asan_crash_test_with_txn || $CONTRUN_NAME=asan_crash_test_with_txn $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $SKIP_FORMAT_CHECKS make J=1 asan_crash_test_with_txn || $CONTRUN_NAME=asan_crash_test_with_txn $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -717,7 +752,7 @@ UBSAN_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Test RocksDB debug under UBSAN',
|
||||
'shell':'set -o pipefail && $SHM $UBSAN $CLANG $DEBUG make $PARALLELISM ubsan_check || $CONTRUN_NAME=ubsan_check $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $UBSAN $CLANG $DEBUG $SKIP_FORMAT_CHECKS make $PARALLELISM ubsan_check || $CONTRUN_NAME=ubsan_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
}
|
||||
@@ -740,7 +775,7 @@ UBSAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug ubsan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH $CLANG make J=1 ubsan_crash_test || $CONTRUN_NAME=ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $CLANG $SKIP_FORMAT_CHECKS make J=1 ubsan_crash_test || $CONTRUN_NAME=ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -764,7 +799,7 @@ UBSAN_BLACKBOX_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug blackbox ubsan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH $CLANG make J=1 blackbox_ubsan_crash_test || $CONTRUN_NAME=blackbox_ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $CLANG $SKIP_FORMAT_CHECKS make J=1 blackbox_ubsan_crash_test || $CONTRUN_NAME=blackbox_ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -788,7 +823,7 @@ UBSAN_WHITEBOX_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug whitebox ubsan_crash_test',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH $CLANG make J=1 whitebox_ubsan_crash_test || $CONTRUN_NAME=whitebox_ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $CLANG $SKIP_FORMAT_CHECKS make J=1 whitebox_ubsan_crash_test || $CONTRUN_NAME=whitebox_ubsan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -812,7 +847,7 @@ UBSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug ubsan_crash_test_with_atomic_flush',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH $CLANG make J=1 ubsan_crash_test_with_atomic_flush || $CONTRUN_NAME=ubsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $CLANG $SKIP_FORMAT_CHECKS make J=1 ubsan_crash_test_with_atomic_flush || $CONTRUN_NAME=ubsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -836,7 +871,7 @@ UBSAN_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
{
|
||||
'name':'Build and run RocksDB debug ubsan_crash_test_with_txn',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG $NON_TSAN_CRASH $CLANG make J=1 ubsan_crash_test_with_txn || $CONTRUN_NAME=ubsan_crash_test_with_txn $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG $NON_TSAN_CRASH $CLANG $SKIP_FORMAT_CHECKS make J=1 ubsan_crash_test_with_txn || $CONTRUN_NAME=ubsan_crash_test_with_txn $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -860,7 +895,7 @@ VALGRIND_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Run RocksDB debug unit tests',
|
||||
'timeout': 86400,
|
||||
'shell':'$SHM $DEBUG make $PARALLELISM valgrind_test || $CONTRUN_NAME=valgrind_check $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SHM $DEBUG make $PARALLELISM valgrind_test || $CONTRUN_NAME=valgrind_check $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -883,7 +918,7 @@ TSAN_UNIT_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Run RocksDB debug unit test',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN make $PARALLELISM check || $CONTRUN_NAME=tsan_check $TASK_CREATION_TOOL',
|
||||
'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
|
||||
},
|
||||
@@ -906,7 +941,7 @@ TSAN_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Compile and run',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test || $CONTRUN_NAME=tsan_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test || $CONTRUN_NAME=tsan_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -930,7 +965,7 @@ TSAN_BLACKBOX_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Compile and run',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 blackbox_crash_test || $CONTRUN_NAME=tsan_blackbox_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 blackbox_crash_test || $CONTRUN_NAME=tsan_blackbox_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -954,7 +989,7 @@ TSAN_WHITEBOX_CRASH_TEST_COMMANDS="[
|
||||
{
|
||||
'name':'Compile and run',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 whitebox_crash_test || $CONTRUN_NAME=tsan_whitebox_crash_test $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 whitebox_crash_test || $CONTRUN_NAME=tsan_whitebox_crash_test $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -978,7 +1013,7 @@ TSAN_CRASH_TEST_WITH_ATOMIC_FLUSH_COMMANDS="[
|
||||
{
|
||||
'name':'Compile and run',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=tsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test_with_atomic_flush || $CONTRUN_NAME=tsan_crash_test_with_atomic_flush $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -1002,7 +1037,7 @@ TSAN_CRASH_TEST_WITH_TXN_COMMANDS="[
|
||||
{
|
||||
'name':'Compile and run',
|
||||
'timeout': 86400,
|
||||
'shell':'set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test_with_txn || $CONTRUN_NAME=tsan_crash_test_with_txn $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; set -o pipefail && $SHM $DEBUG $TSAN $TSAN_CRASH CRASH_TEST_KILL_ODD=1887 make J=1 crash_test_with_txn || $CONTRUN_NAME=tsan_crash_test_with_txn $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -1036,7 +1071,7 @@ FORMAT_COMPATIBLE_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Run RocksDB debug unit test',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_format_compatible || $CONTRUN_NAME=run_format_compatible $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; build_tools/rocksdb-lego-determinator run_format_compatible || $CONTRUN_NAME=run_format_compatible $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -1058,6 +1093,7 @@ run_no_compression()
|
||||
mv .tmp.fbcode_config.sh build_tools/fbcode_config.sh
|
||||
cat Makefile | grep -v tools/ldb_test.py > .tmp.Makefile
|
||||
mv .tmp.Makefile Makefile
|
||||
export $SKIP_FORMAT_CHECKS
|
||||
make $DEBUG J=1 check
|
||||
}
|
||||
|
||||
@@ -1070,7 +1106,7 @@ NO_COMPRESSION_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Run RocksDB debug unit test',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_no_compression || $CONTRUN_NAME=run_no_compression $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; build_tools/rocksdb-lego-determinator run_no_compression || $CONTRUN_NAME=run_no_compression $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -1129,7 +1165,7 @@ REGRESSION_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Make and run script',
|
||||
'shell':'build_tools/rocksdb-lego-determinator run_regression || $CONTRUN_NAME=run_regression $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; build_tools/rocksdb-lego-determinator run_regression || $CONTRUN_NAME=run_regression $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -1150,7 +1186,7 @@ JAVA_BUILD_TEST_COMMANDS="[
|
||||
$CLEANUP_ENV,
|
||||
{
|
||||
'name':'Build RocksDB for Java',
|
||||
'shell':'$SETUP_JAVA_ENV; $SHM make rocksdbjava || $CONTRUN_NAME=rocksdbjava $TASK_CREATION_TOOL',
|
||||
'shell':'cd $WORKING_DIR; $SETUP_JAVA_ENV; $SHM make rocksdbjava || $CONTRUN_NAME=rocksdbjava $TASK_CREATION_TOOL',
|
||||
'user':'root',
|
||||
$PARSER
|
||||
},
|
||||
@@ -1212,6 +1248,9 @@ case $1 in
|
||||
stress_crash_with_txn)
|
||||
echo $STRESS_CRASH_TEST_WITH_TXN_COMMANDS
|
||||
;;
|
||||
stress_crash_with_ts)
|
||||
echo $STRESS_CRASH_TEST_WITH_TS_COMMANDS
|
||||
;;
|
||||
write_stress)
|
||||
echo $WRITE_STRESS_COMMANDS
|
||||
;;
|
||||
|
||||
@@ -68,7 +68,7 @@ $BinariesFolder = -Join($RootFolder, "\build\Debug\")
|
||||
|
||||
if($WorkFolder -eq "") {
|
||||
|
||||
# If TEST_TMPDIR is set use it
|
||||
# If TEST_TMPDIR is set use it
|
||||
[string]$var = $Env:TEST_TMPDIR
|
||||
if($var -eq "") {
|
||||
$WorkFolder = -Join($RootFolder, "\db_tests\")
|
||||
@@ -93,7 +93,7 @@ $ExcludeCasesSet = New-Object System.Collections.Generic.HashSet[string]
|
||||
if($ExcludeCases -ne "") {
|
||||
Write-Host "ExcludeCases: $ExcludeCases"
|
||||
$l = $ExcludeCases -split ' '
|
||||
ForEach($t in $l) {
|
||||
ForEach($t in $l) {
|
||||
$ExcludeCasesSet.Add($t) | Out-Null
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ $ExcludeExesSet = New-Object System.Collections.Generic.HashSet[string]
|
||||
if($ExcludeExes -ne "") {
|
||||
Write-Host "ExcludeExe: $ExcludeExes"
|
||||
$l = $ExcludeExes -split ' '
|
||||
ForEach($t in $l) {
|
||||
ForEach($t in $l) {
|
||||
$ExcludeExesSet.Add($t) | Out-Null
|
||||
}
|
||||
}
|
||||
@@ -118,6 +118,10 @@ if($ExcludeExes -ne "") {
|
||||
# MultiThreaded/MultiThreadedDBTest.
|
||||
# MultiThreaded/0 # GetParam() = 0
|
||||
# MultiThreaded/1 # GetParam() = 1
|
||||
# RibbonTypeParamTest/0. # TypeParam = struct DefaultTypesAndSettings
|
||||
# CompactnessAndBacktrackAndFpRate
|
||||
# Extremes
|
||||
# FindOccupancyForSuccessRate
|
||||
#
|
||||
# into this:
|
||||
#
|
||||
@@ -125,6 +129,9 @@ if($ExcludeExes -ne "") {
|
||||
# DBTest.WriteEmptyBatch
|
||||
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/0
|
||||
# MultiThreaded/MultiThreadedDBTest.MultiThreaded/1
|
||||
# RibbonTypeParamTest/0.CompactnessAndBacktrackAndFpRate
|
||||
# RibbonTypeParamTest/0.Extremes
|
||||
# RibbonTypeParamTest/0.FindOccupancyForSuccessRate
|
||||
#
|
||||
# Output into the parameter in a form TestName -> Log File Name
|
||||
function ExtractTestCases([string]$GTestExe, $HashTable) {
|
||||
@@ -138,6 +145,8 @@ function ExtractTestCases([string]$GTestExe, $HashTable) {
|
||||
|
||||
ForEach( $l in $Tests) {
|
||||
|
||||
# remove trailing comment if any
|
||||
$l = $l -replace '\s+\#.*',''
|
||||
# Leading whitespace is fine
|
||||
$l = $l -replace '^\s+',''
|
||||
# Trailing dot is a test group but no whitespace
|
||||
@@ -146,8 +155,7 @@ function ExtractTestCases([string]$GTestExe, $HashTable) {
|
||||
} else {
|
||||
# Otherwise it is a test name, remove leading space
|
||||
$test = $l
|
||||
# remove trailing comment if any and create a log name
|
||||
$test = $test -replace '\s+\#.*',''
|
||||
# create a log name
|
||||
$test = "$Group$test"
|
||||
|
||||
if($ExcludeCasesSet.Contains($test)) {
|
||||
@@ -253,7 +261,7 @@ if($Run -ne "") {
|
||||
|
||||
$DiscoveredExe = @()
|
||||
dir -Path $search_path | ForEach-Object {
|
||||
$DiscoveredExe += ($_.Name)
|
||||
$DiscoveredExe += ($_.Name)
|
||||
}
|
||||
|
||||
# Remove exclusions
|
||||
@@ -293,7 +301,7 @@ if($SuiteRun -ne "") {
|
||||
|
||||
$ListOfExe = @()
|
||||
dir -Path $search_path | ForEach-Object {
|
||||
$ListOfExe += ($_.Name)
|
||||
$ListOfExe += ($_.Name)
|
||||
}
|
||||
|
||||
# Exclude those in RunOnly from running as suites
|
||||
@@ -348,7 +356,7 @@ function RunJobs($Suites, $TestCmds, [int]$ConcurrencyVal)
|
||||
|
||||
# Wait for all to finish and get the results
|
||||
while(($JobToLog.Count -gt 0) -or
|
||||
($TestCmds.Count -gt 0) -or
|
||||
($TestCmds.Count -gt 0) -or
|
||||
($Suites.Count -gt 0)) {
|
||||
|
||||
# Make sure we have maximum concurrent jobs running if anything
|
||||
@@ -468,8 +476,8 @@ RunJobs -Suites $CasesToRun -TestCmds $TestExes -ConcurrencyVal $Concurrency
|
||||
|
||||
$EndDate = (Get-Date)
|
||||
|
||||
New-TimeSpan -Start $StartDate -End $EndDate |
|
||||
ForEach-Object {
|
||||
New-TimeSpan -Start $StartDate -End $EndDate |
|
||||
ForEach-Object {
|
||||
"Elapsed time: {0:g}" -f $_
|
||||
}
|
||||
|
||||
@@ -484,4 +492,4 @@ if(!$script:success) {
|
||||
|
||||
exit 0
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -44,9 +44,9 @@ Status Cache::CreateFromString(const ConfigOptions& config_options,
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
LRUCacheOptions cache_opts;
|
||||
status = OptionTypeInfo::ParseStruct(
|
||||
config_options, "", &lru_cache_options_type_info, "", value,
|
||||
reinterpret_cast<char*>(&cache_opts));
|
||||
status = OptionTypeInfo::ParseStruct(config_options, "",
|
||||
&lru_cache_options_type_info, "",
|
||||
value, &cache_opts);
|
||||
if (status.ok()) {
|
||||
cache = NewLRUCache(cache_opts);
|
||||
}
|
||||
|
||||
Vendored
+7
-368
@@ -1,8 +1,11 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// Copyright (c) 2013-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.
|
||||
#ifndef GFLAGS
|
||||
#include <cstdio>
|
||||
int main() {
|
||||
@@ -10,372 +13,8 @@ int main() {
|
||||
return 1;
|
||||
}
|
||||
#else
|
||||
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/gflags_compat.h"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
|
||||
static constexpr uint32_t KiB = uint32_t{1} << 10;
|
||||
static constexpr uint32_t MiB = KiB << 10;
|
||||
static constexpr uint64_t GiB = MiB << 10;
|
||||
|
||||
DEFINE_uint32(threads, 16, "Number of concurrent threads to run.");
|
||||
DEFINE_uint64(cache_size, 1 * GiB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
DEFINE_uint32(num_shard_bits, 6, "shard_bits.");
|
||||
|
||||
DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 0,
|
||||
"Number of operations per thread. (Default: 5 * keyspace size)");
|
||||
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
"Ratio of lookup (+ insert on not found) to total workload "
|
||||
"(expressed as a percentage)");
|
||||
DEFINE_uint32(insert_percent, 2,
|
||||
"Ratio of insert to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(lookup_percent, 10,
|
||||
"Ratio of lookup to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(erase_percent, 1,
|
||||
"Ratio of erase to total workload (expressed as a percentage)");
|
||||
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CacheBench;
|
||||
namespace {
|
||||
// State shared by all concurrent executions of the same benchmark.
|
||||
class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
|
||||
port::Mutex* GetMutex() {
|
||||
return &mu_;
|
||||
}
|
||||
|
||||
port::CondVar* GetCondVar() {
|
||||
return &cv_;
|
||||
}
|
||||
|
||||
CacheBench* GetCacheBench() const {
|
||||
return cache_bench_;
|
||||
}
|
||||
|
||||
void IncInitialized() {
|
||||
num_initialized_++;
|
||||
}
|
||||
|
||||
void IncDone() {
|
||||
num_done_++;
|
||||
}
|
||||
|
||||
bool AllInitialized() const { return num_initialized_ >= FLAGS_threads; }
|
||||
|
||||
bool AllDone() const { return num_done_ >= FLAGS_threads; }
|
||||
|
||||
void SetStart() {
|
||||
start_ = true;
|
||||
}
|
||||
|
||||
bool Started() const {
|
||||
return start_;
|
||||
}
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
struct ThreadState {
|
||||
uint32_t tid;
|
||||
Random64 rnd;
|
||||
SharedState* shared;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
uint64_t key = FastRange64(raw, max_key);
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
EncodeFixed64(key_data + 1, key);
|
||||
key_data[9] = char{11};
|
||||
EncodeFixed64(key_data + 10, key);
|
||||
key_data[18] = char{4};
|
||||
EncodeFixed64(key_data + 19, key);
|
||||
return Slice(&key_data[off], sizeof(key_data) - off);
|
||||
}
|
||||
};
|
||||
|
||||
char* createValue(Random64& rnd) {
|
||||
char* rv = new char[FLAGS_value_bytes];
|
||||
// Fill with some filler data, and take some CPU time
|
||||
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
|
||||
EncodeFixed64(rv + i, rnd.Next());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
void deleter(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
static constexpr uint64_t kHundredthUint64 =
|
||||
std::numeric_limits<uint64_t>::max() / 100U;
|
||||
|
||||
public:
|
||||
CacheBench()
|
||||
: max_key_(static_cast<uint64_t>(FLAGS_cache_size / FLAGS_resident_ratio /
|
||||
FLAGS_value_bytes)),
|
||||
lookup_insert_threshold_(kHundredthUint64 *
|
||||
FLAGS_lookup_insert_percent),
|
||||
insert_threshold_(lookup_insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_insert_percent),
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
if (FLAGS_use_clock_cache) {
|
||||
cache_ = NewClockCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
if (!cache_) {
|
||||
fprintf(stderr, "Clock cache not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
cache_ = NewLRUCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
}
|
||||
if (FLAGS_ops_per_thread == 0) {
|
||||
FLAGS_ops_per_thread = 5 * max_key_;
|
||||
}
|
||||
}
|
||||
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
for (uint64_t i = 0; i < 2 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
cache_->Insert(keygen.GetRand(rnd, max_key_), createValue(rnd),
|
||||
FLAGS_value_bytes, &deleter);
|
||||
}
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
ROCKSDB_NAMESPACE::Env* env = ROCKSDB_NAMESPACE::Env::Default();
|
||||
|
||||
PrintEnv();
|
||||
SharedState shared(this);
|
||||
std::vector<std::unique_ptr<ThreadState> > threads(FLAGS_threads);
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
threads[i].reset(new ThreadState(i, &shared));
|
||||
env->StartThread(ThreadBody, threads[i].get());
|
||||
}
|
||||
{
|
||||
MutexLock l(shared.GetMutex());
|
||||
while (!shared.AllInitialized()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
// Record start time
|
||||
uint64_t start_time = env->NowMicros();
|
||||
|
||||
// Start all threads
|
||||
shared.SetStart();
|
||||
shared.GetCondVar()->SignalAll();
|
||||
|
||||
// Wait threads to complete
|
||||
while (!shared.AllDone()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
|
||||
// Record end time
|
||||
uint64_t end_time = env->NowMicros();
|
||||
double elapsed = static_cast<double>(end_time - start_time) * 1e-6;
|
||||
uint32_t qps = static_cast<uint32_t>(
|
||||
static_cast<double>(FLAGS_threads * FLAGS_ops_per_thread) / elapsed);
|
||||
fprintf(stdout, "Complete in %.3f s; QPS = %u\n", elapsed, qps);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
const uint64_t max_key_;
|
||||
// Cumulative thresholds in the space of a random uint64_t
|
||||
const uint64_t lookup_insert_threshold_;
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
|
||||
static void ThreadBody(void* v) {
|
||||
ThreadState* thread = static_cast<ThreadState*>(v);
|
||||
SharedState* shared = thread->shared;
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncInitialized();
|
||||
if (shared->AllInitialized()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
while (!shared->Started()) {
|
||||
shared->GetCondVar()->Wait();
|
||||
}
|
||||
}
|
||||
thread->shared->GetCacheBench()->OperateCache(thread);
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncDone();
|
||||
if (shared->AllDone()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
} else {
|
||||
// do insert
|
||||
cache_->Insert(key, createValue(thread->rnd), FLAGS_value_bytes,
|
||||
&deleter, &handle);
|
||||
}
|
||||
} else if (random_op < insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do insert
|
||||
cache_->Insert(key, createValue(thread->rnd), FLAGS_value_bytes,
|
||||
&deleter, &handle);
|
||||
} else if (random_op < lookup_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
cache_->Erase(key);
|
||||
} else {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void PrintEnv() const {
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
printf("Cache size : %" PRIu64 "\n", FLAGS_cache_size);
|
||||
printf("Num shard bits : %u\n", FLAGS_num_shard_bits);
|
||||
printf("Max key : %" PRIu64 "\n", max_key_);
|
||||
printf("Resident ratio : %g\n", FLAGS_resident_ratio);
|
||||
printf("Skew degree : %u\n", FLAGS_skew);
|
||||
printf("Populate cache : %d\n", int{FLAGS_populate_cache});
|
||||
printf("Lookup+Insert pct : %u%%\n", FLAGS_lookup_insert_percent);
|
||||
printf("Insert percentage : %u%%\n", FLAGS_insert_percent);
|
||||
printf("Lookup percentage : %u%%\n", FLAGS_lookup_percent);
|
||||
printf("Erase percentage : %u%%\n", FLAGS_erase_percent);
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#include <rocksdb/cache_bench_tool.h>
|
||||
int main(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
return ROCKSDB_NAMESPACE::cache_bench_tool(argc, argv);
|
||||
}
|
||||
|
||||
#endif // GFLAGS
|
||||
|
||||
Vendored
+573
@@ -0,0 +1,573 @@
|
||||
// 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).
|
||||
|
||||
#ifdef GFLAGS
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include "monitoring/histogram.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.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"
|
||||
#include "util/hash.h"
|
||||
#include "util/mutexlock.h"
|
||||
#include "util/random.h"
|
||||
#include "util/stop_watch.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
using GFLAGS_NAMESPACE::ParseCommandLineFlags;
|
||||
|
||||
static constexpr uint32_t KiB = uint32_t{1} << 10;
|
||||
static constexpr uint32_t MiB = KiB << 10;
|
||||
static constexpr uint64_t GiB = MiB << 10;
|
||||
|
||||
DEFINE_uint32(threads, 16, "Number of concurrent threads to run.");
|
||||
DEFINE_uint64(cache_size, 1 * GiB,
|
||||
"Number of bytes to use as a cache of uncompressed data.");
|
||||
DEFINE_uint32(num_shard_bits, 6, "shard_bits.");
|
||||
|
||||
DEFINE_double(resident_ratio, 0.25,
|
||||
"Ratio of keys fitting in cache to keyspace.");
|
||||
DEFINE_uint64(ops_per_thread, 2000000U, "Number of operations per thread.");
|
||||
DEFINE_uint32(value_bytes, 8 * KiB, "Size of each value added.");
|
||||
|
||||
DEFINE_uint32(skew, 5, "Degree of skew in key selection");
|
||||
DEFINE_bool(populate_cache, true, "Populate cache before operations");
|
||||
|
||||
DEFINE_uint32(lookup_insert_percent, 87,
|
||||
"Ratio of lookup (+ insert on not found) to total workload "
|
||||
"(expressed as a percentage)");
|
||||
DEFINE_uint32(insert_percent, 2,
|
||||
"Ratio of insert to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(lookup_percent, 10,
|
||||
"Ratio of lookup to total workload (expressed as a percentage)");
|
||||
DEFINE_uint32(erase_percent, 1,
|
||||
"Ratio of erase to total workload (expressed as a percentage)");
|
||||
DEFINE_bool(gather_stats, false,
|
||||
"Whether to periodically simulate gathering block cache stats, "
|
||||
"using one more thread.");
|
||||
DEFINE_uint32(
|
||||
gather_stats_sleep_ms, 1000,
|
||||
"How many milliseconds to sleep between each gathering of stats.");
|
||||
|
||||
DEFINE_uint32(gather_stats_entries_per_lock, 256,
|
||||
"For Cache::ApplyToAllEntries");
|
||||
DEFINE_bool(skewed, false, "If true, skew the key access distribution");
|
||||
#ifndef ROCKSDB_LITE
|
||||
DEFINE_string(secondary_cache_uri, "",
|
||||
"Full URI for creating a custom secondary cache object");
|
||||
static class std::shared_ptr<ROCKSDB_NAMESPACE::SecondaryCache> secondary_cache;
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
DEFINE_bool(use_clock_cache, false, "");
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class CacheBench;
|
||||
namespace {
|
||||
// State shared by all concurrent executions of the same benchmark.
|
||||
class SharedState {
|
||||
public:
|
||||
explicit SharedState(CacheBench* cache_bench)
|
||||
: cv_(&mu_),
|
||||
num_initialized_(0),
|
||||
start_(false),
|
||||
num_done_(0),
|
||||
cache_bench_(cache_bench) {}
|
||||
|
||||
~SharedState() {}
|
||||
|
||||
port::Mutex* GetMutex() { return &mu_; }
|
||||
|
||||
port::CondVar* GetCondVar() { return &cv_; }
|
||||
|
||||
CacheBench* GetCacheBench() const { return cache_bench_; }
|
||||
|
||||
void IncInitialized() { num_initialized_++; }
|
||||
|
||||
void IncDone() { num_done_++; }
|
||||
|
||||
bool AllInitialized() const { return num_initialized_ >= FLAGS_threads; }
|
||||
|
||||
bool AllDone() const { return num_done_ >= FLAGS_threads; }
|
||||
|
||||
void SetStart() { start_ = true; }
|
||||
|
||||
bool Started() const { return start_; }
|
||||
|
||||
private:
|
||||
port::Mutex mu_;
|
||||
port::CondVar cv_;
|
||||
|
||||
uint64_t num_initialized_;
|
||||
bool start_;
|
||||
uint64_t num_done_;
|
||||
|
||||
CacheBench* cache_bench_;
|
||||
};
|
||||
|
||||
// Per-thread state for concurrent executions of the same benchmark.
|
||||
struct ThreadState {
|
||||
uint32_t tid;
|
||||
Random64 rnd;
|
||||
SharedState* shared;
|
||||
HistogramImpl latency_ns_hist;
|
||||
uint64_t duration_us = 0;
|
||||
|
||||
ThreadState(uint32_t index, SharedState* _shared)
|
||||
: tid(index), rnd(1000 + index), shared(_shared) {}
|
||||
};
|
||||
|
||||
struct KeyGen {
|
||||
char key_data[27];
|
||||
|
||||
Slice GetRand(Random64& rnd, uint64_t max_key, int max_log) {
|
||||
uint64_t key = 0;
|
||||
if (!FLAGS_skewed) {
|
||||
uint64_t raw = rnd.Next();
|
||||
// Skew according to setting
|
||||
for (uint32_t i = 0; i < FLAGS_skew; ++i) {
|
||||
raw = std::min(raw, rnd.Next());
|
||||
}
|
||||
key = FastRange64(raw, max_key);
|
||||
} else {
|
||||
key = rnd.Skewed(max_log);
|
||||
if (key > max_key) {
|
||||
key -= max_key;
|
||||
}
|
||||
}
|
||||
// Variable size and alignment
|
||||
size_t off = key % 8;
|
||||
key_data[0] = char{42};
|
||||
EncodeFixed64(key_data + 1, key);
|
||||
key_data[9] = char{11};
|
||||
EncodeFixed64(key_data + 10, key);
|
||||
key_data[18] = char{4};
|
||||
EncodeFixed64(key_data + 19, key);
|
||||
return Slice(&key_data[off], sizeof(key_data) - off);
|
||||
}
|
||||
};
|
||||
|
||||
char* createValue(Random64& rnd) {
|
||||
char* rv = new char[FLAGS_value_bytes];
|
||||
// Fill with some filler data, and take some CPU time
|
||||
for (uint32_t i = 0; i < FLAGS_value_bytes; i += 8) {
|
||||
EncodeFixed64(rv + i, rnd.Next());
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Callbacks for secondary cache
|
||||
size_t SizeFn(void* /*obj*/) { return FLAGS_value_bytes; }
|
||||
|
||||
Status SaveToFn(void* obj, size_t /*offset*/, size_t size, void* out) {
|
||||
memcpy(out, obj, size);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
// Different deleters to simulate using deleter to gather
|
||||
// stats on the code origin and kind of cache entries.
|
||||
void deleter1(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
void deleter2(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
void deleter3(const Slice& /*key*/, void* value) {
|
||||
delete[] static_cast<char*>(value);
|
||||
}
|
||||
|
||||
Cache::CacheItemHelper helper1(SizeFn, SaveToFn, deleter1);
|
||||
Cache::CacheItemHelper helper2(SizeFn, SaveToFn, deleter2);
|
||||
Cache::CacheItemHelper helper3(SizeFn, SaveToFn, deleter3);
|
||||
} // namespace
|
||||
|
||||
class CacheBench {
|
||||
static constexpr uint64_t kHundredthUint64 =
|
||||
std::numeric_limits<uint64_t>::max() / 100U;
|
||||
|
||||
public:
|
||||
CacheBench()
|
||||
: max_key_(static_cast<uint64_t>(FLAGS_cache_size / FLAGS_resident_ratio /
|
||||
FLAGS_value_bytes)),
|
||||
lookup_insert_threshold_(kHundredthUint64 *
|
||||
FLAGS_lookup_insert_percent),
|
||||
insert_threshold_(lookup_insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_insert_percent),
|
||||
lookup_threshold_(insert_threshold_ +
|
||||
kHundredthUint64 * FLAGS_lookup_percent),
|
||||
erase_threshold_(lookup_threshold_ +
|
||||
kHundredthUint64 * FLAGS_erase_percent),
|
||||
skewed_(FLAGS_skewed) {
|
||||
if (erase_threshold_ != 100U * kHundredthUint64) {
|
||||
fprintf(stderr, "Percentages must add to 100.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
max_log_ = 0;
|
||||
if (skewed_) {
|
||||
uint64_t max_key = max_key_;
|
||||
while (max_key >>= 1) max_log_++;
|
||||
if (max_key > (1u << max_log_)) max_log_++;
|
||||
}
|
||||
|
||||
if (FLAGS_use_clock_cache) {
|
||||
cache_ = NewClockCache(FLAGS_cache_size, FLAGS_num_shard_bits);
|
||||
if (!cache_) {
|
||||
fprintf(stderr, "Clock cache not supported.\n");
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
if (secondary_cache == nullptr) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"No secondary cache registered matching string: %s status=%s\n",
|
||||
FLAGS_secondary_cache_uri.c_str(), s.ToString().c_str());
|
||||
exit(1);
|
||||
}
|
||||
opts.secondary_cache = secondary_cache;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
cache_ = NewLRUCache(opts);
|
||||
}
|
||||
}
|
||||
|
||||
~CacheBench() {}
|
||||
|
||||
void PopulateCache() {
|
||||
Random64 rnd(1);
|
||||
KeyGen keygen;
|
||||
for (uint64_t i = 0; i < 2 * FLAGS_cache_size; i += FLAGS_value_bytes) {
|
||||
cache_->Insert(keygen.GetRand(rnd, max_key_, max_log_), createValue(rnd),
|
||||
&helper1, FLAGS_value_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
bool Run() {
|
||||
const auto clock = SystemClock::Default().get();
|
||||
|
||||
PrintEnv();
|
||||
SharedState shared(this);
|
||||
std::vector<std::unique_ptr<ThreadState> > threads(FLAGS_threads);
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
threads[i].reset(new ThreadState(i, &shared));
|
||||
std::thread(ThreadBody, threads[i].get()).detach();
|
||||
}
|
||||
|
||||
HistogramImpl stats_hist;
|
||||
std::string stats_report;
|
||||
std::thread stats_thread(StatsBody, &shared, &stats_hist, &stats_report);
|
||||
|
||||
uint64_t start_time;
|
||||
{
|
||||
MutexLock l(shared.GetMutex());
|
||||
while (!shared.AllInitialized()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
// Record start time
|
||||
start_time = clock->NowMicros();
|
||||
|
||||
// Start all threads
|
||||
shared.SetStart();
|
||||
shared.GetCondVar()->SignalAll();
|
||||
|
||||
// Wait threads to complete
|
||||
while (!shared.AllDone()) {
|
||||
shared.GetCondVar()->Wait();
|
||||
}
|
||||
}
|
||||
|
||||
// Stats gathering is considered background work. This time measurement
|
||||
// is for foreground work, and not really ideal for that. See below.
|
||||
uint64_t end_time = clock->NowMicros();
|
||||
stats_thread.join();
|
||||
|
||||
// Wall clock time - includes idle time if threads
|
||||
// finish at different times (not ideal).
|
||||
double elapsed_secs = static_cast<double>(end_time - start_time) * 1e-6;
|
||||
uint32_t ops_per_sec = static_cast<uint32_t>(
|
||||
1.0 * FLAGS_threads * FLAGS_ops_per_thread / elapsed_secs);
|
||||
printf("Complete in %.3f s; Rough parallel ops/sec = %u\n", elapsed_secs,
|
||||
ops_per_sec);
|
||||
|
||||
// Total time in each thread (more accurate throughput measure)
|
||||
elapsed_secs = 0;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
elapsed_secs += threads[i]->duration_us * 1e-6;
|
||||
}
|
||||
ops_per_sec = static_cast<uint32_t>(1.0 * FLAGS_threads *
|
||||
FLAGS_ops_per_thread / elapsed_secs);
|
||||
printf("Thread ops/sec = %u\n", ops_per_sec);
|
||||
|
||||
printf("\nOperation latency (ns):\n");
|
||||
HistogramImpl combined;
|
||||
for (uint32_t i = 0; i < FLAGS_threads; i++) {
|
||||
combined.Merge(threads[i]->latency_ns_hist);
|
||||
}
|
||||
printf("%s", combined.ToString().c_str());
|
||||
|
||||
if (FLAGS_gather_stats) {
|
||||
printf("\nGather stats latency (us):\n");
|
||||
printf("%s", stats_hist.ToString().c_str());
|
||||
}
|
||||
|
||||
printf("\n%s", stats_report.c_str());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Cache> cache_;
|
||||
const uint64_t max_key_;
|
||||
// Cumulative thresholds in the space of a random uint64_t
|
||||
const uint64_t lookup_insert_threshold_;
|
||||
const uint64_t insert_threshold_;
|
||||
const uint64_t lookup_threshold_;
|
||||
const uint64_t erase_threshold_;
|
||||
const bool skewed_;
|
||||
int max_log_;
|
||||
|
||||
// A benchmark version of gathering stats on an active block cache by
|
||||
// iterating over it. The primary purpose is to measure the impact of
|
||||
// gathering stats with ApplyToAllEntries on throughput- and
|
||||
// latency-sensitive Cache users. Performance of stats gathering is
|
||||
// also reported. The last set of gathered stats is also reported, for
|
||||
// manual sanity checking for logical errors or other unexpected
|
||||
// behavior of cache_bench or the underlying Cache.
|
||||
static void StatsBody(SharedState* shared, HistogramImpl* stats_hist,
|
||||
std::string* stats_report) {
|
||||
if (!FLAGS_gather_stats) {
|
||||
return;
|
||||
}
|
||||
const auto clock = SystemClock::Default().get();
|
||||
uint64_t total_key_size = 0;
|
||||
uint64_t total_charge = 0;
|
||||
uint64_t total_entry_count = 0;
|
||||
std::set<Cache::DeleterFn> deleters;
|
||||
StopWatchNano timer(clock);
|
||||
|
||||
for (;;) {
|
||||
uint64_t time;
|
||||
time = clock->NowMicros();
|
||||
uint64_t deadline = time + uint64_t{FLAGS_gather_stats_sleep_ms} * 1000;
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
for (;;) {
|
||||
if (shared->AllDone()) {
|
||||
std::ostringstream ostr;
|
||||
ostr << "Most recent cache entry stats:\n"
|
||||
<< "Number of entries: " << total_entry_count << "\n"
|
||||
<< "Total charge: " << BytesToHumanString(total_charge) << "\n"
|
||||
<< "Average key size: "
|
||||
<< (1.0 * total_key_size / total_entry_count) << "\n"
|
||||
<< "Average charge: "
|
||||
<< BytesToHumanString(1.0 * total_charge / total_entry_count)
|
||||
<< "\n"
|
||||
<< "Unique deleters: " << deleters.size() << "\n";
|
||||
*stats_report = ostr.str();
|
||||
return;
|
||||
}
|
||||
if (clock->NowMicros() >= deadline) {
|
||||
break;
|
||||
}
|
||||
uint64_t diff = deadline - std::min(clock->NowMicros(), deadline);
|
||||
shared->GetCondVar()->TimedWait(diff + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Now gather stats, outside of mutex
|
||||
total_key_size = 0;
|
||||
total_charge = 0;
|
||||
total_entry_count = 0;
|
||||
deleters.clear();
|
||||
auto fn = [&](const Slice& key, void* /*value*/, size_t charge,
|
||||
Cache::DeleterFn deleter) {
|
||||
total_key_size += key.size();
|
||||
total_charge += charge;
|
||||
++total_entry_count;
|
||||
// Something slightly more expensive as in (future) stats by category
|
||||
deleters.insert(deleter);
|
||||
};
|
||||
timer.Start();
|
||||
Cache::ApplyToAllEntriesOptions opts;
|
||||
opts.average_entries_per_lock = FLAGS_gather_stats_entries_per_lock;
|
||||
shared->GetCacheBench()->cache_->ApplyToAllEntries(fn, opts);
|
||||
stats_hist->Add(timer.ElapsedNanos() / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
static void ThreadBody(ThreadState* thread) {
|
||||
SharedState* shared = thread->shared;
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncInitialized();
|
||||
if (shared->AllInitialized()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
while (!shared->Started()) {
|
||||
shared->GetCondVar()->Wait();
|
||||
}
|
||||
}
|
||||
thread->shared->GetCacheBench()->OperateCache(thread);
|
||||
|
||||
{
|
||||
MutexLock l(shared->GetMutex());
|
||||
shared->IncDone();
|
||||
if (shared->AllDone()) {
|
||||
shared->GetCondVar()->SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OperateCache(ThreadState* thread) {
|
||||
// To use looked-up values
|
||||
uint64_t result = 0;
|
||||
// To hold handles for a non-trivial amount of time
|
||||
Cache::Handle* handle = nullptr;
|
||||
KeyGen gen;
|
||||
const auto clock = SystemClock::Default().get();
|
||||
uint64_t start_time = clock->NowMicros();
|
||||
StopWatchNano timer(clock);
|
||||
|
||||
for (uint64_t i = 0; i < FLAGS_ops_per_thread; i++) {
|
||||
timer.Start();
|
||||
Slice key = gen.GetRand(thread->rnd, max_key_, max_log_);
|
||||
uint64_t random_op = thread->rnd.Next();
|
||||
Cache::CreateCallback create_cb =
|
||||
[](void* buf, size_t size, void** out_obj, size_t* charge) -> Status {
|
||||
*out_obj = reinterpret_cast<void*>(new char[size]);
|
||||
memcpy(*out_obj, buf, size);
|
||||
*charge = size;
|
||||
return Status::OK();
|
||||
};
|
||||
|
||||
if (random_op < lookup_insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key, &helper2, create_cb, Cache::Priority::LOW,
|
||||
true);
|
||||
if (handle) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
} else {
|
||||
// do insert
|
||||
cache_->Insert(key, createValue(thread->rnd), &helper2,
|
||||
FLAGS_value_bytes, &handle);
|
||||
}
|
||||
} else if (random_op < insert_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do insert
|
||||
cache_->Insert(key, createValue(thread->rnd), &helper3,
|
||||
FLAGS_value_bytes, &handle);
|
||||
} else if (random_op < lookup_threshold_) {
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// do lookup
|
||||
handle = cache_->Lookup(key, &helper2, create_cb, Cache::Priority::LOW,
|
||||
true);
|
||||
if (handle) {
|
||||
// do something with the data
|
||||
result += NPHash64(static_cast<char*>(cache_->Value(handle)),
|
||||
FLAGS_value_bytes);
|
||||
}
|
||||
} else if (random_op < erase_threshold_) {
|
||||
// do erase
|
||||
cache_->Erase(key);
|
||||
} else {
|
||||
// Should be extremely unlikely (noop)
|
||||
assert(random_op >= kHundredthUint64 * 100U);
|
||||
}
|
||||
thread->latency_ns_hist.Add(timer.ElapsedNanos());
|
||||
}
|
||||
if (handle) {
|
||||
cache_->Release(handle);
|
||||
handle = nullptr;
|
||||
}
|
||||
// Ensure computations on `result` are not optimized away.
|
||||
if (result == 1) {
|
||||
printf("You are extremely unlucky(2). Try again.\n");
|
||||
exit(1);
|
||||
}
|
||||
thread->duration_us = clock->NowMicros() - start_time;
|
||||
}
|
||||
|
||||
void PrintEnv() const {
|
||||
printf("RocksDB version : %d.%d\n", kMajorVersion, kMinorVersion);
|
||||
printf("Number of threads : %u\n", FLAGS_threads);
|
||||
printf("Ops per thread : %" PRIu64 "\n", FLAGS_ops_per_thread);
|
||||
printf("Cache size : %s\n",
|
||||
BytesToHumanString(FLAGS_cache_size).c_str());
|
||||
printf("Num shard bits : %u\n", FLAGS_num_shard_bits);
|
||||
printf("Max key : %" PRIu64 "\n", max_key_);
|
||||
printf("Resident ratio : %g\n", FLAGS_resident_ratio);
|
||||
printf("Skew degree : %u\n", FLAGS_skew);
|
||||
printf("Populate cache : %d\n", int{FLAGS_populate_cache});
|
||||
printf("Lookup+Insert pct : %u%%\n", FLAGS_lookup_insert_percent);
|
||||
printf("Insert percentage : %u%%\n", FLAGS_insert_percent);
|
||||
printf("Lookup percentage : %u%%\n", FLAGS_lookup_percent);
|
||||
printf("Erase percentage : %u%%\n", FLAGS_erase_percent);
|
||||
std::ostringstream stats;
|
||||
if (FLAGS_gather_stats) {
|
||||
stats << "enabled (" << FLAGS_gather_stats_sleep_ms << "ms, "
|
||||
<< FLAGS_gather_stats_entries_per_lock << "/lock)";
|
||||
} else {
|
||||
stats << "disabled";
|
||||
}
|
||||
printf("Gather stats : %s\n", stats.str().c_str());
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
};
|
||||
|
||||
int cache_bench_tool(int argc, char** argv) {
|
||||
ParseCommandLineFlags(&argc, &argv, true);
|
||||
|
||||
if (FLAGS_threads <= 0) {
|
||||
fprintf(stderr, "threads number <= 0\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::CacheBench bench;
|
||||
if (FLAGS_populate_cache) {
|
||||
bench.PopulateCache();
|
||||
printf("Population complete\n");
|
||||
printf("----------------------------\n");
|
||||
}
|
||||
if (bench.Run()) {
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#endif // GFLAGS
|
||||
Vendored
+66
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. 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 "cache/cache_entry_roles.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "port/lang.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
std::array<const char*, kNumCacheEntryRoles> kCacheEntryRoleToCamelString{{
|
||||
"DataBlock",
|
||||
"FilterBlock",
|
||||
"FilterMetaBlock",
|
||||
"DeprecatedFilterBlock",
|
||||
"IndexBlock",
|
||||
"OtherBlock",
|
||||
"WriteBuffer",
|
||||
"Misc",
|
||||
}};
|
||||
|
||||
std::array<const char*, kNumCacheEntryRoles> kCacheEntryRoleToHyphenString{{
|
||||
"data-block",
|
||||
"filter-block",
|
||||
"filter-meta-block",
|
||||
"deprecated-filter-block",
|
||||
"index-block",
|
||||
"other-block",
|
||||
"write-buffer",
|
||||
"misc",
|
||||
}};
|
||||
|
||||
namespace {
|
||||
|
||||
struct Registry {
|
||||
std::mutex mutex;
|
||||
std::unordered_map<Cache::DeleterFn, CacheEntryRole> role_map;
|
||||
void Register(Cache::DeleterFn fn, CacheEntryRole role) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
role_map[fn] = role;
|
||||
}
|
||||
std::unordered_map<Cache::DeleterFn, CacheEntryRole> Copy() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
return role_map;
|
||||
}
|
||||
};
|
||||
|
||||
Registry& GetRegistry() {
|
||||
STATIC_AVOID_DESTRUCTION(Registry, registry);
|
||||
return registry;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void RegisterCacheDeleterRole(Cache::DeleterFn fn, CacheEntryRole role) {
|
||||
GetRegistry().Register(fn, role);
|
||||
}
|
||||
|
||||
std::unordered_map<Cache::DeleterFn, CacheEntryRole> CopyCacheDeleterRoleMap() {
|
||||
return GetRegistry().Copy();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+122
@@ -0,0 +1,122 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. 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 <array>
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "rocksdb/cache.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Classifications of block cache entries, for reporting statistics
|
||||
enum class CacheEntryRole {
|
||||
// Block-based table data block
|
||||
kDataBlock,
|
||||
// Block-based table filter block (full or partitioned)
|
||||
kFilterBlock,
|
||||
// Block-based table metadata block for partitioned filter
|
||||
kFilterMetaBlock,
|
||||
// Block-based table deprecated filter block (old "block-based" filter)
|
||||
kDeprecatedFilterBlock,
|
||||
// Block-based table index block
|
||||
kIndexBlock,
|
||||
// Other kinds of block-based table block
|
||||
kOtherBlock,
|
||||
// WriteBufferManager reservations to account for memtable usage
|
||||
kWriteBuffer,
|
||||
// Default bucket, for miscellaneous cache entries. Do not use for
|
||||
// entries that could potentially add up to large usage.
|
||||
kMisc,
|
||||
};
|
||||
constexpr uint32_t kNumCacheEntryRoles =
|
||||
static_cast<uint32_t>(CacheEntryRole::kMisc) + 1;
|
||||
|
||||
extern std::array<const char*, kNumCacheEntryRoles>
|
||||
kCacheEntryRoleToCamelString;
|
||||
extern std::array<const char*, kNumCacheEntryRoles>
|
||||
kCacheEntryRoleToHyphenString;
|
||||
|
||||
// To associate cache entries with their role, we use a hack on the
|
||||
// existing Cache interface. Because the deleter of an entry can authenticate
|
||||
// the code origin of an entry, we can elaborate the choice of deleter to
|
||||
// also encode role information, without inferring false role information
|
||||
// from entries not choosing to encode a role.
|
||||
//
|
||||
// The rest of this file is for handling mappings between deleters and
|
||||
// roles.
|
||||
|
||||
// To infer a role from a deleter, the deleter must be registered. This
|
||||
// can be done "manually" with this function. This function is thread-safe,
|
||||
// and the registration mappings go into private but static storage. (Note
|
||||
// that DeleterFn is a function pointer, not std::function. Registrations
|
||||
// should not be too many.)
|
||||
void RegisterCacheDeleterRole(Cache::DeleterFn fn, CacheEntryRole role);
|
||||
|
||||
// Gets a copy of the registered deleter -> role mappings. This is the only
|
||||
// function for reading the mappings made with RegisterCacheDeleterRole.
|
||||
// Why only this interface for reading?
|
||||
// * This function has to be thread safe, which could incur substantial
|
||||
// overhead. We should not pay this overhead for every deleter look-up.
|
||||
// * This is suitable for preparing for batch operations, like with
|
||||
// CacheEntryStatsCollector.
|
||||
// * The number of mappings should be sufficiently small (dozens).
|
||||
std::unordered_map<Cache::DeleterFn, CacheEntryRole> CopyCacheDeleterRoleMap();
|
||||
|
||||
// ************************************************************** //
|
||||
// An automatic registration infrastructure. This enables code
|
||||
// to simply ask for a deleter associated with a particular type
|
||||
// and role, and registration is automatic. In a sense, this is
|
||||
// a small dependency injection infrastructure, because linking
|
||||
// in new deleter instantiations is essentially sufficient for
|
||||
// making stats collection (using CopyCacheDeleterRoleMap) aware
|
||||
// of them.
|
||||
|
||||
namespace cache_entry_roles_detail {
|
||||
|
||||
template <typename T, CacheEntryRole R>
|
||||
struct RegisteredDeleter {
|
||||
RegisteredDeleter() { RegisterCacheDeleterRole(Delete, R); }
|
||||
|
||||
// These have global linkage to help ensure compiler optimizations do not
|
||||
// break uniqueness for each <T,R>
|
||||
static void Delete(const Slice& /* key */, void* value) {
|
||||
delete static_cast<T*>(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <CacheEntryRole R>
|
||||
struct RegisteredNoopDeleter {
|
||||
RegisteredNoopDeleter() { RegisterCacheDeleterRole(Delete, R); }
|
||||
|
||||
static void Delete(const Slice& /* key */, void* value) {
|
||||
(void)value;
|
||||
assert(value == nullptr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cache_entry_roles_detail
|
||||
|
||||
// Get an automatically registered deleter for value type T and role R.
|
||||
// Based on C++ semantics, registration is invoked exactly once in a
|
||||
// thread-safe way on first call to this function, for each <T, R>.
|
||||
template <typename T, CacheEntryRole R>
|
||||
Cache::DeleterFn GetCacheEntryDeleterForRole() {
|
||||
static cache_entry_roles_detail::RegisteredDeleter<T, R> reg;
|
||||
return reg.Delete;
|
||||
}
|
||||
|
||||
// Get an automatically registered no-op deleter (value should be nullptr)
|
||||
// and associated with role R. This is used for Cache "reservation" entries
|
||||
// such as for WriteBufferManager.
|
||||
template <CacheEntryRole R>
|
||||
Cache::DeleterFn GetNoopDeleterForRole() {
|
||||
static cache_entry_roles_detail::RegisteredNoopDeleter<R> reg;
|
||||
return reg.Delete;
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+180
@@ -0,0 +1,180 @@
|
||||
// Copyright (c) Facebook, Inc. and its affiliates. 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 <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "port/lang.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "util/coding_lean.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// A generic helper object for gathering stats about cache entries by
|
||||
// iterating over them with ApplyToAllEntries. This class essentially
|
||||
// solves the problem of slowing down a Cache with too many stats
|
||||
// collectors that could be sharing stat results, such as from multiple
|
||||
// column families or multiple DBs sharing a Cache. We employ a few
|
||||
// mitigations:
|
||||
// * Only one collector for a particular kind of Stats is alive
|
||||
// for each Cache. This is guaranteed using the Cache itself to hold
|
||||
// the collector.
|
||||
// * A mutex ensures only one thread is gathering stats for this
|
||||
// collector.
|
||||
// * The most recent gathered stats are saved and simply copied to
|
||||
// satisfy requests within a time window (default: 3 minutes) of
|
||||
// completion of the most recent stat gathering.
|
||||
//
|
||||
// Template parameter Stats must be copyable and trivially constructable,
|
||||
// as well as...
|
||||
// concept Stats {
|
||||
// // Notification before applying callback to all entries
|
||||
// void BeginCollection(Cache*, SystemClock*, uint64_t start_time_micros);
|
||||
// // Get the callback to apply to all entries. `callback`
|
||||
// // type must be compatible with Cache::ApplyToAllEntries
|
||||
// callback GetEntryCallback();
|
||||
// // Notification after applying callback to all entries
|
||||
// void EndCollection(Cache*, SystemClock*, uint64_t end_time_micros);
|
||||
// // Notification that a collection was skipped because of
|
||||
// // sufficiently recent saved results.
|
||||
// void SkippedCollection();
|
||||
// }
|
||||
template <class Stats>
|
||||
class CacheEntryStatsCollector {
|
||||
public:
|
||||
// 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(working_mutex_);
|
||||
|
||||
uint64_t max_age_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;
|
||||
working_stats_.BeginCollection(cache_, clock_, start_time_micros);
|
||||
|
||||
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;
|
||||
working_stats_.EndCollection(cache_, clock_, end_time_micros);
|
||||
} else {
|
||||
working_stats_.SkippedCollection();
|
||||
}
|
||||
|
||||
// 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_;
|
||||
}
|
||||
|
||||
Cache *GetCache() const { return cache_; }
|
||||
|
||||
// Gets or creates a shared instance of CacheEntryStatsCollector in the
|
||||
// cache itself, and saves into `ptr`. This shared_ptr will hold the
|
||||
// entry in cache until all refs are destroyed.
|
||||
static Status GetShared(Cache *cache, SystemClock *clock,
|
||||
std::shared_ptr<CacheEntryStatsCollector> *ptr) {
|
||||
std::array<uint64_t, 3> cache_key_data{
|
||||
{// First 16 bytes == md5 of class name
|
||||
0x7eba5a8fb5437c90U, 0x8ca68c9b11655855U,
|
||||
// Last 8 bytes based on a function pointer to make unique for each
|
||||
// template instantiation
|
||||
reinterpret_cast<uint64_t>(&CacheEntryStatsCollector::GetShared)}};
|
||||
Slice cache_key = GetSlice(&cache_key_data);
|
||||
|
||||
Cache::Handle *h = cache->Lookup(cache_key);
|
||||
if (h == nullptr) {
|
||||
// Not yet in cache, but Cache doesn't provide a built-in way to
|
||||
// avoid racing insert. So we double-check under a shared mutex,
|
||||
// inspired by TableCache.
|
||||
STATIC_AVOID_DESTRUCTION(std::mutex, static_mutex);
|
||||
std::lock_guard<std::mutex> lock(static_mutex);
|
||||
|
||||
h = cache->Lookup(cache_key);
|
||||
if (h == nullptr) {
|
||||
auto new_ptr = new CacheEntryStatsCollector(cache, clock);
|
||||
// TODO: non-zero charge causes some tests that count block cache
|
||||
// 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,
|
||||
Cache::Priority::HIGH);
|
||||
if (!s.ok()) {
|
||||
assert(h == nullptr);
|
||||
delete new_ptr;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we reach here, shared entry is in cache with handle `h`.
|
||||
assert(cache->GetDeleter(h) == Deleter);
|
||||
|
||||
// Build an aliasing shared_ptr that keeps `ptr` in cache while there
|
||||
// are references.
|
||||
*ptr = MakeSharedCacheHandleGuard<CacheEntryStatsCollector>(cache, h);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
private:
|
||||
explicit CacheEntryStatsCollector(Cache *cache, SystemClock *clock)
|
||||
: saved_stats_(),
|
||||
working_stats_(),
|
||||
last_start_time_micros_(0),
|
||||
last_end_time_micros_(/*pessimistic*/ 10000000),
|
||||
cache_(cache),
|
||||
clock_(clock) {}
|
||||
|
||||
static void Deleter(const Slice &, void *value) {
|
||||
delete static_cast<CacheEntryStatsCollector *>(value);
|
||||
}
|
||||
|
||||
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_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+125
@@ -0,0 +1,125 @@
|
||||
// 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 "rocksdb/cache.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
// Returns the cached value given a cache handle.
|
||||
template <typename T>
|
||||
T* GetFromCacheHandle(Cache* cache, Cache::Handle* handle) {
|
||||
assert(cache);
|
||||
assert(handle);
|
||||
|
||||
return static_cast<T*>(cache->Value(handle));
|
||||
}
|
||||
|
||||
// Simple generic deleter for Cache (to be used with Cache::Insert).
|
||||
template <typename T>
|
||||
void DeleteCacheEntry(const Slice& /* key */, void* value) {
|
||||
delete static_cast<T*>(value);
|
||||
}
|
||||
|
||||
// Turns a T* into a Slice so it can be used as a key with Cache.
|
||||
template <typename T>
|
||||
Slice GetSlice(const T* t) {
|
||||
return Slice(reinterpret_cast<const char*>(t), sizeof(T));
|
||||
}
|
||||
|
||||
// Generic resource management object for cache handles that releases the handle
|
||||
// when destroyed. Has unique ownership of the handle, so copying it is not
|
||||
// allowed, while moving it transfers ownership.
|
||||
template <typename T>
|
||||
class CacheHandleGuard {
|
||||
public:
|
||||
CacheHandleGuard() = default;
|
||||
|
||||
CacheHandleGuard(Cache* cache, Cache::Handle* handle)
|
||||
: cache_(cache),
|
||||
handle_(handle),
|
||||
value_(GetFromCacheHandle<T>(cache, handle)) {
|
||||
assert(cache_ && handle_ && value_);
|
||||
}
|
||||
|
||||
CacheHandleGuard(const CacheHandleGuard&) = delete;
|
||||
CacheHandleGuard& operator=(const CacheHandleGuard&) = delete;
|
||||
|
||||
CacheHandleGuard(CacheHandleGuard&& rhs) noexcept
|
||||
: cache_(rhs.cache_), handle_(rhs.handle_), value_(rhs.value_) {
|
||||
assert((!cache_ && !handle_ && !value_) || (cache_ && handle_ && value_));
|
||||
|
||||
rhs.ResetFields();
|
||||
}
|
||||
|
||||
CacheHandleGuard& operator=(CacheHandleGuard&& rhs) noexcept {
|
||||
if (this == &rhs) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
ReleaseHandle();
|
||||
|
||||
cache_ = rhs.cache_;
|
||||
handle_ = rhs.handle_;
|
||||
value_ = rhs.value_;
|
||||
|
||||
assert((!cache_ && !handle_ && !value_) || (cache_ && handle_ && value_));
|
||||
|
||||
rhs.ResetFields();
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
~CacheHandleGuard() { ReleaseHandle(); }
|
||||
|
||||
bool IsEmpty() const { return !handle_; }
|
||||
|
||||
Cache* GetCache() const { return cache_; }
|
||||
Cache::Handle* GetCacheHandle() const { return handle_; }
|
||||
T* GetValue() const { return value_; }
|
||||
|
||||
void Reset() {
|
||||
ReleaseHandle();
|
||||
ResetFields();
|
||||
}
|
||||
|
||||
private:
|
||||
void ReleaseHandle() {
|
||||
if (IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(cache_);
|
||||
cache_->Release(handle_);
|
||||
}
|
||||
|
||||
void ResetFields() {
|
||||
cache_ = nullptr;
|
||||
handle_ = nullptr;
|
||||
value_ = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Cache* cache_ = nullptr;
|
||||
Cache::Handle* handle_ = nullptr;
|
||||
T* value_ = nullptr;
|
||||
};
|
||||
|
||||
// Build an aliasing shared_ptr that keeps `handle` in cache while there
|
||||
// are references, but the pointer is to the value for that cache entry,
|
||||
// which must be of type T. This is copyable, unlike CacheHandleGuard, but
|
||||
// does not provide access to caching details.
|
||||
template <typename T>
|
||||
std::shared_ptr<T> MakeSharedCacheHandleGuard(Cache* cache,
|
||||
Cache::Handle* handle) {
|
||||
auto wrapper = std::make_shared<CacheHandleGuard<T>>(cache, handle);
|
||||
return std::shared_ptr<T>(wrapper, static_cast<T*>(cache->Value(handle)));
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
Vendored
+82
-8
@@ -712,25 +712,98 @@ TEST_P(CacheTest, OverCapacity) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::vector<std::pair<int, int>> callback_state;
|
||||
void callback(void* entry, size_t charge) {
|
||||
callback_state.push_back({DecodeValue(entry), static_cast<int>(charge)});
|
||||
std::vector<std::pair<int, int>> legacy_callback_state;
|
||||
void legacy_callback(void* value, size_t charge) {
|
||||
legacy_callback_state.push_back(
|
||||
{DecodeValue(value), static_cast<int>(charge)});
|
||||
}
|
||||
};
|
||||
|
||||
TEST_P(CacheTest, ApplyToAllCacheEntiresTest) {
|
||||
TEST_P(CacheTest, ApplyToAllCacheEntriesTest) {
|
||||
std::vector<std::pair<int, int>> inserted;
|
||||
callback_state.clear();
|
||||
legacy_callback_state.clear();
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Insert(i, i * 2, i + 1);
|
||||
inserted.push_back({i * 2, i + 1});
|
||||
}
|
||||
cache_->ApplyToAllCacheEntries(callback, true);
|
||||
cache_->ApplyToAllCacheEntries(legacy_callback, true);
|
||||
|
||||
std::sort(inserted.begin(), inserted.end());
|
||||
std::sort(legacy_callback_state.begin(), legacy_callback_state.end());
|
||||
ASSERT_EQ(inserted.size(), legacy_callback_state.size());
|
||||
for (size_t i = 0; i < inserted.size(); ++i) {
|
||||
EXPECT_EQ(inserted[i], legacy_callback_state[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, ApplyToAllEntriesTest) {
|
||||
std::vector<std::string> callback_state;
|
||||
const auto callback = [&](const Slice& key, void* value, size_t charge,
|
||||
Cache::DeleterFn deleter) {
|
||||
callback_state.push_back(ToString(DecodeKey(key)) + "," +
|
||||
ToString(DecodeValue(value)) + "," +
|
||||
ToString(charge));
|
||||
assert(deleter == &CacheTest::Deleter);
|
||||
};
|
||||
|
||||
std::vector<std::string> inserted;
|
||||
callback_state.clear();
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Insert(i, i * 2, i + 1);
|
||||
inserted.push_back(ToString(i) + "," + ToString(i * 2) + "," +
|
||||
ToString(i + 1));
|
||||
}
|
||||
cache_->ApplyToAllEntries(callback, /*opts*/ {});
|
||||
|
||||
std::sort(inserted.begin(), inserted.end());
|
||||
std::sort(callback_state.begin(), callback_state.end());
|
||||
ASSERT_TRUE(inserted == callback_state);
|
||||
ASSERT_EQ(inserted.size(), callback_state.size());
|
||||
for (size_t i = 0; i < inserted.size(); ++i) {
|
||||
EXPECT_EQ(inserted[i], callback_state[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, ApplyToAllEntriesDuringResize) {
|
||||
// This is a mini-stress test of ApplyToAllEntries, to ensure
|
||||
// items in the cache that are neither added nor removed
|
||||
// during ApplyToAllEntries are counted exactly once.
|
||||
|
||||
// Insert some entries that we expect to be seen exactly once
|
||||
// during iteration.
|
||||
constexpr int kSpecialCharge = 2;
|
||||
constexpr int kNotSpecialCharge = 1;
|
||||
constexpr int kSpecialCount = 100;
|
||||
for (int i = 0; i < kSpecialCount; ++i) {
|
||||
Insert(i, i * 2, kSpecialCharge);
|
||||
}
|
||||
|
||||
// For callback
|
||||
int special_count = 0;
|
||||
const auto callback = [&](const Slice&, void*, size_t charge,
|
||||
Cache::DeleterFn) {
|
||||
if (charge == static_cast<size_t>(kSpecialCharge)) {
|
||||
++special_count;
|
||||
}
|
||||
};
|
||||
|
||||
// Start counting
|
||||
std::thread apply_thread([&]() {
|
||||
// Use small average_entries_per_lock to make the problem difficult
|
||||
Cache::ApplyToAllEntriesOptions opts;
|
||||
opts.average_entries_per_lock = 2;
|
||||
cache_->ApplyToAllEntries(callback, opts);
|
||||
});
|
||||
|
||||
// In parallel, add more entries, enough to cause resize but not enough
|
||||
// to cause ejections
|
||||
for (int i = kSpecialCount * 1; i < kSpecialCount * 6; ++i) {
|
||||
Insert(i, i * 2, kNotSpecialCharge);
|
||||
}
|
||||
|
||||
apply_thread.join();
|
||||
ASSERT_EQ(special_count, kSpecialCount);
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, DefaultShardBits) {
|
||||
@@ -749,11 +822,12 @@ TEST_P(CacheTest, DefaultShardBits) {
|
||||
ASSERT_EQ(6, sc->GetNumShardBits());
|
||||
}
|
||||
|
||||
TEST_P(CacheTest, GetCharge) {
|
||||
TEST_P(CacheTest, GetChargeAndDeleter) {
|
||||
Insert(1, 2);
|
||||
Cache::Handle* h1 = cache_->Lookup(EncodeKey(1));
|
||||
ASSERT_EQ(2, DecodeValue(cache_->Value(h1)));
|
||||
ASSERT_EQ(1, cache_->GetCharge(h1));
|
||||
ASSERT_EQ(&CacheTest::Deleter, cache_->GetDeleter(h1));
|
||||
cache_->Release(h1);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+101
-34
@@ -176,10 +176,13 @@ namespace {
|
||||
// Cache entry meta data.
|
||||
struct CacheHandle {
|
||||
Slice key;
|
||||
uint32_t hash;
|
||||
void* value;
|
||||
size_t charge;
|
||||
void (*deleter)(const Slice&, void* value);
|
||||
Cache::DeleterFn deleter;
|
||||
uint32_t hash;
|
||||
|
||||
// Addition to "charge" to get "total charge" under metadata policy.
|
||||
uint32_t meta_charge;
|
||||
|
||||
// Flags and counters associated with the cache handle:
|
||||
// lowest bit: in-cache bit
|
||||
@@ -205,9 +208,8 @@ struct CacheHandle {
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline static size_t CalcTotalCharge(
|
||||
Slice key, size_t charge,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
inline static uint32_t CalcMetadataCharge(
|
||||
Slice key, CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
size_t meta_charge = 0;
|
||||
if (metadata_charge_policy == kFullChargeCacheMetadata) {
|
||||
meta_charge += sizeof(CacheHandle);
|
||||
@@ -218,13 +220,11 @@ struct CacheHandle {
|
||||
meta_charge += key.size();
|
||||
#endif
|
||||
}
|
||||
return charge + meta_charge;
|
||||
assert(meta_charge <= UINT32_MAX);
|
||||
return static_cast<uint32_t>(meta_charge);
|
||||
}
|
||||
|
||||
inline size_t CalcTotalCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
return CalcTotalCharge(key, charge, metadata_charge_policy);
|
||||
}
|
||||
inline size_t GetTotalCharge() { return charge + meta_charge; }
|
||||
};
|
||||
|
||||
// Key of hash map. We store hash value with the key for convenience.
|
||||
@@ -271,7 +271,25 @@ class ClockCacheShard final : public CacheShard {
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Cache::Handle** handle, Cache::Priority priority) override;
|
||||
Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
Cache::Handle** handle, Cache::Priority priority) override {
|
||||
return Insert(key, hash, value, charge, helper->del_cb, handle, priority);
|
||||
}
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
Cache::Handle* Lookup(const Slice& key, uint32_t hash,
|
||||
const Cache::CacheItemHelper* /*helper*/,
|
||||
const Cache::CreateCallback& /*create_cb*/,
|
||||
Cache::Priority /*priority*/, bool /*wait*/) override {
|
||||
return Lookup(key, hash);
|
||||
}
|
||||
bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
bool IsReady(Cache::Handle* /*handle*/) override { return true; }
|
||||
void Wait(Cache::Handle* /*handle*/) override {}
|
||||
|
||||
// If the entry in in cache, increase reference count and return true.
|
||||
// Return false otherwise.
|
||||
//
|
||||
@@ -284,8 +302,10 @@ class ClockCacheShard final : public CacheShard {
|
||||
size_t GetUsage() const override;
|
||||
size_t GetPinnedUsage() const override;
|
||||
void EraseUnRefEntries() override;
|
||||
void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) override;
|
||||
void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
uint32_t average_entries_per_lock, uint32_t* state) override;
|
||||
|
||||
private:
|
||||
static const uint32_t kInCacheBit = 1;
|
||||
@@ -404,22 +424,46 @@ size_t ClockCacheShard::GetPinnedUsage() const {
|
||||
return pinned_usage_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void ClockCacheShard::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) {
|
||||
if (thread_safe) {
|
||||
mutex_.Lock();
|
||||
void ClockCacheShard::ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
uint32_t average_entries_per_lock, uint32_t* state) {
|
||||
assert(average_entries_per_lock > 0);
|
||||
MutexLock lock(&mutex_);
|
||||
|
||||
// Figure out the range to iterate, update `state`
|
||||
size_t list_size = list_.size();
|
||||
size_t start_idx = *state;
|
||||
size_t end_idx = start_idx + average_entries_per_lock;
|
||||
if (start_idx > list_size) {
|
||||
// Shouldn't reach here, but recoverable
|
||||
assert(false);
|
||||
// Mark finished with all
|
||||
*state = UINT32_MAX;
|
||||
return;
|
||||
}
|
||||
for (auto& handle : list_) {
|
||||
// Use relaxed semantics instead of acquire semantics since we are either
|
||||
// holding mutex, or don't have thread safe requirement.
|
||||
if (end_idx >= list_size || end_idx >= UINT32_MAX) {
|
||||
// This also includes the hypothetical case of >4 billion
|
||||
// cache handles.
|
||||
end_idx = list_size;
|
||||
// Mark finished with all
|
||||
*state = UINT32_MAX;
|
||||
} else {
|
||||
*state = static_cast<uint32_t>(end_idx);
|
||||
}
|
||||
|
||||
// Do the iteration
|
||||
auto cur = list_.begin() + start_idx;
|
||||
auto end = list_.begin() + end_idx;
|
||||
for (; cur != end; ++cur) {
|
||||
const CacheHandle& handle = *cur;
|
||||
// Use relaxed semantics instead of acquire semantics since we are
|
||||
// holding mutex
|
||||
uint32_t flags = handle.flags.load(std::memory_order_relaxed);
|
||||
if (InCache(flags)) {
|
||||
callback(handle.value, handle.charge);
|
||||
callback(handle.key, handle.value, handle.charge, handle.deleter);
|
||||
}
|
||||
}
|
||||
if (thread_safe) {
|
||||
mutex_.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void ClockCacheShard::RecycleHandle(CacheHandle* handle,
|
||||
@@ -428,10 +472,8 @@ void ClockCacheShard::RecycleHandle(CacheHandle* handle,
|
||||
assert(!InCache(handle->flags) && CountRefs(handle->flags) == 0);
|
||||
context->to_delete_key.push_back(handle->key.data());
|
||||
context->to_delete_value.emplace_back(*handle);
|
||||
size_t total_charge = handle->CalcTotalCharge(metadata_charge_policy_);
|
||||
handle->key.clear();
|
||||
handle->value = nullptr;
|
||||
handle->deleter = nullptr;
|
||||
size_t total_charge = handle->GetTotalCharge();
|
||||
// clearing `handle` fields would go here but not strictly required
|
||||
recycle_.push_back(handle);
|
||||
usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
||||
}
|
||||
@@ -459,7 +501,7 @@ bool ClockCacheShard::Ref(Cache::Handle* h) {
|
||||
std::memory_order_relaxed)) {
|
||||
if (CountRefs(flags) == 0) {
|
||||
// No reference count before the operation.
|
||||
size_t total_charge = handle->CalcTotalCharge(metadata_charge_policy_);
|
||||
size_t total_charge = handle->GetTotalCharge();
|
||||
pinned_usage_.fetch_add(total_charge, std::memory_order_relaxed);
|
||||
}
|
||||
return true;
|
||||
@@ -473,6 +515,11 @@ bool ClockCacheShard::Unref(CacheHandle* handle, bool set_usage,
|
||||
if (set_usage) {
|
||||
handle->flags.fetch_or(kUsageBit, std::memory_order_relaxed);
|
||||
}
|
||||
// If the handle reaches state refs=0 and InCache=true after this
|
||||
// atomic operation then we cannot access `handle` afterward, because
|
||||
// it could be evicted before we access the `handle`.
|
||||
size_t total_charge = handle->GetTotalCharge();
|
||||
|
||||
// Use acquire-release semantics as previous operations on the cache entry
|
||||
// has to be order before reference count is decreased, and potential cleanup
|
||||
// of the entry has to be order after.
|
||||
@@ -480,7 +527,6 @@ bool ClockCacheShard::Unref(CacheHandle* handle, bool set_usage,
|
||||
assert(CountRefs(flags) > 0);
|
||||
if (CountRefs(flags) == 1) {
|
||||
// this is the last reference.
|
||||
size_t total_charge = handle->CalcTotalCharge(metadata_charge_policy_);
|
||||
pinned_usage_.fetch_sub(total_charge, std::memory_order_relaxed);
|
||||
// Cleanup if it is the last reference.
|
||||
if (!InCache(flags)) {
|
||||
@@ -567,8 +613,9 @@ CacheHandle* ClockCacheShard::Insert(
|
||||
void (*deleter)(const Slice& key, void* value), bool hold_reference,
|
||||
CleanupContext* context, bool* overwritten) {
|
||||
assert(overwritten != nullptr && *overwritten == false);
|
||||
size_t total_charge =
|
||||
CacheHandle::CalcTotalCharge(key, charge, metadata_charge_policy_);
|
||||
uint32_t meta_charge =
|
||||
CacheHandle::CalcMetadataCharge(key, metadata_charge_policy_);
|
||||
size_t total_charge = charge + meta_charge;
|
||||
MutexLock l(&mutex_);
|
||||
bool success = EvictFromCache(total_charge, context);
|
||||
bool strict = strict_capacity_limit_.load(std::memory_order_relaxed);
|
||||
@@ -594,8 +641,18 @@ CacheHandle* ClockCacheShard::Insert(
|
||||
handle->hash = hash;
|
||||
handle->value = value;
|
||||
handle->charge = charge;
|
||||
handle->meta_charge = meta_charge;
|
||||
handle->deleter = deleter;
|
||||
uint32_t flags = hold_reference ? kInCacheBit + kOneRef : kInCacheBit;
|
||||
|
||||
// TODO investigate+fix suspected race condition:
|
||||
// [thread 1] Lookup starts, up to Ref()
|
||||
// [thread 2] Erase/evict the entry just looked up
|
||||
// [thread 1] Ref() the handle, even though it's in the recycle bin
|
||||
// [thread 2] Insert with recycling that handle
|
||||
// Here we obliterate the other thread's Ref
|
||||
// Possible fix: never blindly overwrite the flags, but only make
|
||||
// relative updates (fetch_add, etc).
|
||||
handle->flags.store(flags, std::memory_order_relaxed);
|
||||
HashTable::accessor accessor;
|
||||
if (table_.find(accessor, CacheKey(key, hash))) {
|
||||
@@ -726,11 +783,11 @@ class ClockCache final : public ShardedCache {
|
||||
|
||||
const char* Name() const override { return "ClockCache"; }
|
||||
|
||||
CacheShard* GetShard(int shard) override {
|
||||
CacheShard* GetShard(uint32_t shard) override {
|
||||
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
||||
}
|
||||
|
||||
const CacheShard* GetShard(int shard) const override {
|
||||
const CacheShard* GetShard(uint32_t shard) const override {
|
||||
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
||||
}
|
||||
|
||||
@@ -746,7 +803,17 @@ class ClockCache final : public ShardedCache {
|
||||
return reinterpret_cast<const CacheHandle*>(handle)->hash;
|
||||
}
|
||||
|
||||
void DisownData() override { shards_ = nullptr; }
|
||||
DeleterFn GetDeleter(Handle* handle) const override {
|
||||
return reinterpret_cast<const CacheHandle*>(handle)->deleter;
|
||||
}
|
||||
|
||||
void DisownData() override {
|
||||
#ifndef MUST_FREE_HEAP_ALLOCATIONS
|
||||
shards_ = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void WaitAll(std::vector<Handle*>& /*handles*/) override {}
|
||||
|
||||
private:
|
||||
ClockCacheShard* shards_;
|
||||
|
||||
Vendored
+342
-130
@@ -9,26 +9,28 @@
|
||||
|
||||
#include "cache/lru_cache.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
LRUHandleTable::LRUHandleTable() : list_(nullptr), length_(0), elems_(0) {
|
||||
Resize();
|
||||
}
|
||||
LRUHandleTable::LRUHandleTable(int max_upper_hash_bits)
|
||||
: length_bits_(/* historical starting size*/ 4),
|
||||
list_(new LRUHandle* [size_t{1} << length_bits_] {}),
|
||||
elems_(0),
|
||||
max_length_bits_(max_upper_hash_bits) {}
|
||||
|
||||
LRUHandleTable::~LRUHandleTable() {
|
||||
ApplyToAllCacheEntries([](LRUHandle* h) {
|
||||
if (!h->HasRefs()) {
|
||||
h->Free();
|
||||
}
|
||||
});
|
||||
delete[] list_;
|
||||
ApplyToEntriesRange(
|
||||
[](LRUHandle* h) {
|
||||
if (!h->HasRefs()) {
|
||||
h->Free();
|
||||
}
|
||||
},
|
||||
0, uint32_t{1} << length_bits_);
|
||||
}
|
||||
|
||||
LRUHandle* LRUHandleTable::Lookup(const Slice& key, uint32_t hash) {
|
||||
@@ -42,7 +44,7 @@ LRUHandle* LRUHandleTable::Insert(LRUHandle* h) {
|
||||
*ptr = h;
|
||||
if (old == nullptr) {
|
||||
++elems_;
|
||||
if (elems_ > length_) {
|
||||
if ((elems_ >> length_bits_) > 0) { // elems_ >= length
|
||||
// Since each cache entry is fairly large, we aim for a small
|
||||
// average linked list length (<= 1).
|
||||
Resize();
|
||||
@@ -62,7 +64,7 @@ LRUHandle* LRUHandleTable::Remove(const Slice& key, uint32_t hash) {
|
||||
}
|
||||
|
||||
LRUHandle** LRUHandleTable::FindPointer(const Slice& key, uint32_t hash) {
|
||||
LRUHandle** ptr = &list_[hash & (length_ - 1)];
|
||||
LRUHandle** ptr = &list_[hash >> (32 - length_bits_)];
|
||||
while (*ptr != nullptr && ((*ptr)->hash != hash || key != (*ptr)->key())) {
|
||||
ptr = &(*ptr)->next_hash;
|
||||
}
|
||||
@@ -70,19 +72,29 @@ LRUHandle** LRUHandleTable::FindPointer(const Slice& key, uint32_t hash) {
|
||||
}
|
||||
|
||||
void LRUHandleTable::Resize() {
|
||||
uint32_t new_length = 16;
|
||||
while (new_length < elems_ * 1.5) {
|
||||
new_length *= 2;
|
||||
if (length_bits_ >= max_length_bits_) {
|
||||
// Due to reaching limit of hash information, if we made the table
|
||||
// bigger, we would allocate more addresses but only the same
|
||||
// number would be used.
|
||||
return;
|
||||
}
|
||||
LRUHandle** new_list = new LRUHandle*[new_length];
|
||||
memset(new_list, 0, sizeof(new_list[0]) * new_length);
|
||||
if (length_bits_ >= 31) {
|
||||
// Avoid undefined behavior shifting uint32_t by 32
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t old_length = uint32_t{1} << length_bits_;
|
||||
int new_length_bits = length_bits_ + 1;
|
||||
std::unique_ptr<LRUHandle* []> new_list {
|
||||
new LRUHandle* [size_t{1} << new_length_bits] {}
|
||||
};
|
||||
uint32_t count = 0;
|
||||
for (uint32_t i = 0; i < length_; i++) {
|
||||
for (uint32_t i = 0; i < old_length; i++) {
|
||||
LRUHandle* h = list_[i];
|
||||
while (h != nullptr) {
|
||||
LRUHandle* next = h->next_hash;
|
||||
uint32_t hash = h->hash;
|
||||
LRUHandle** ptr = &new_list[hash & (new_length - 1)];
|
||||
LRUHandle** ptr = &new_list[hash >> (32 - new_length_bits)];
|
||||
h->next_hash = *ptr;
|
||||
*ptr = h;
|
||||
h = next;
|
||||
@@ -90,23 +102,25 @@ void LRUHandleTable::Resize() {
|
||||
}
|
||||
}
|
||||
assert(elems_ == count);
|
||||
delete[] list_;
|
||||
list_ = new_list;
|
||||
length_ = new_length;
|
||||
list_ = std::move(new_list);
|
||||
length_bits_ = new_length_bits;
|
||||
}
|
||||
|
||||
LRUCacheShard::LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
LRUCacheShard::LRUCacheShard(
|
||||
size_t capacity, bool strict_capacity_limit, double high_pri_pool_ratio,
|
||||
bool use_adaptive_mutex, CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache)
|
||||
: capacity_(0),
|
||||
high_pri_pool_usage_(0),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
high_pri_pool_ratio_(high_pri_pool_ratio),
|
||||
high_pri_pool_capacity_(0),
|
||||
table_(max_upper_hash_bits),
|
||||
usage_(0),
|
||||
lru_usage_(0),
|
||||
mutex_(use_adaptive_mutex) {
|
||||
mutex_(use_adaptive_mutex),
|
||||
secondary_cache_(secondary_cache) {
|
||||
set_metadata_charge_policy(metadata_charge_policy);
|
||||
// Make empty circular linked list
|
||||
lru_.next = &lru_;
|
||||
@@ -138,19 +152,40 @@ void LRUCacheShard::EraseUnRefEntries() {
|
||||
}
|
||||
}
|
||||
|
||||
void LRUCacheShard::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) {
|
||||
const auto applyCallback = [&]() {
|
||||
table_.ApplyToAllCacheEntries(
|
||||
[callback](LRUHandle* h) { callback(h->value, h->charge); });
|
||||
};
|
||||
void LRUCacheShard::ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
uint32_t average_entries_per_lock, uint32_t* state) {
|
||||
// The state is essentially going to be the starting hash, which works
|
||||
// nicely even if we resize between calls because we use upper-most
|
||||
// hash bits for table indexes.
|
||||
MutexLock l(&mutex_);
|
||||
uint32_t length_bits = table_.GetLengthBits();
|
||||
uint32_t length = uint32_t{1} << length_bits;
|
||||
|
||||
if (thread_safe) {
|
||||
MutexLock l(&mutex_);
|
||||
applyCallback();
|
||||
assert(average_entries_per_lock > 0);
|
||||
// Assuming we are called with same average_entries_per_lock repeatedly,
|
||||
// this simplifies some logic (index_end will not overflow)
|
||||
assert(average_entries_per_lock < length || *state == 0);
|
||||
|
||||
uint32_t index_begin = *state >> (32 - length_bits);
|
||||
uint32_t index_end = index_begin + average_entries_per_lock;
|
||||
if (index_end >= length) {
|
||||
// Going to end
|
||||
index_end = length;
|
||||
*state = UINT32_MAX;
|
||||
} else {
|
||||
applyCallback();
|
||||
*state = index_end << (32 - length_bits);
|
||||
}
|
||||
|
||||
table_.ApplyToEntriesRange(
|
||||
[callback](LRUHandle* h) {
|
||||
DeleterFn deleter = h->IsSecondaryCacheCompatible()
|
||||
? h->info_.helper->del_cb
|
||||
: h->info_.deleter;
|
||||
callback(h->key(), h->value, h->charge, deleter);
|
||||
},
|
||||
index_begin, index_end);
|
||||
}
|
||||
|
||||
void LRUCacheShard::TEST_GetLRUList(LRUHandle** lru, LRUHandle** lru_low_pri) {
|
||||
@@ -257,8 +292,14 @@ void LRUCacheShard::SetCapacity(size_t capacity) {
|
||||
EvictFromLRU(0, &last_reference_list);
|
||||
}
|
||||
|
||||
// Try to insert the evicted entries into tiered cache
|
||||
// Free the entries outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
if (secondary_cache_ && entry->IsSecondaryCacheCompatible() &&
|
||||
!entry->IsPromoted()) {
|
||||
secondary_cache_->Insert(entry->key(), entry->value, entry->info_.helper)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
entry->Free();
|
||||
}
|
||||
}
|
||||
@@ -268,17 +309,176 @@ void LRUCacheShard::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCacheShard::Lookup(const Slice& key, uint32_t hash) {
|
||||
MutexLock l(&mutex_);
|
||||
LRUHandle* e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
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_);
|
||||
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
|
||||
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.
|
||||
last_reference_list.push_back(e);
|
||||
} else {
|
||||
if (free_handle_on_fail) {
|
||||
delete[] reinterpret_cast<char*>(e);
|
||||
*handle = nullptr;
|
||||
}
|
||||
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
usage_ += total_charge;
|
||||
if (old != nullptr) {
|
||||
s = Status::OkOverwritten();
|
||||
assert(old->InCache());
|
||||
old->SetInCache(false);
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0
|
||||
LRU_Remove(old);
|
||||
size_t old_total_charge =
|
||||
old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(e);
|
||||
} else {
|
||||
e->Ref();
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to insert the evicted entries into the secondary cache
|
||||
// Free the entries here outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
if (secondary_cache_ && entry->IsSecondaryCacheCompatible() &&
|
||||
!entry->IsPromoted()) {
|
||||
secondary_cache_->Insert(entry->key(), entry->value, entry->info_.helper)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
entry->Free();
|
||||
}
|
||||
|
||||
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()) {
|
||||
// InsertItem would have taken a reference on the item, so decrement it
|
||||
// here as we expect the caller to already hold a reference
|
||||
e->Unref();
|
||||
} else {
|
||||
// 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
|
||||
// and charge the cache only for metadata usage, i.e handle, key etc
|
||||
MutexLock l(&mutex_);
|
||||
e->charge = 0;
|
||||
e->SetInCache(false);
|
||||
usage_ += e->CalcTotalCharge(metadata_charge_policy_);
|
||||
}
|
||||
}
|
||||
|
||||
Cache::Handle* LRUCacheShard::Lookup(
|
||||
const Slice& key, uint32_t hash,
|
||||
const ShardedCache::CacheItemHelper* helper,
|
||||
const ShardedCache::CreateCallback& create_cb, Cache::Priority priority,
|
||||
bool wait) {
|
||||
LRUHandle* e = nullptr;
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
e = table_.Lookup(key, hash);
|
||||
if (e != nullptr) {
|
||||
assert(e->InCache());
|
||||
if (!e->HasRefs()) {
|
||||
// The entry is in LRU since it's in hash and has no external references
|
||||
LRU_Remove(e);
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
}
|
||||
|
||||
// If handle table lookup failed, then allocate a handle outside the
|
||||
// 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) {
|
||||
// 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<SecondaryCacheResultHandle> secondary_handle =
|
||||
secondary_cache_->Lookup(key, create_cb, wait);
|
||||
if (secondary_handle != nullptr) {
|
||||
e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
|
||||
e->flags = 0;
|
||||
e->SetSecondaryCacheCompatible(true);
|
||||
e->info_.helper = helper;
|
||||
e->key_length = key.size();
|
||||
e->hash = hash;
|
||||
e->refs = 0;
|
||||
e->next = e->prev = nullptr;
|
||||
e->SetPriority(priority);
|
||||
memcpy(e->key_data, key.data(), key.size());
|
||||
e->value = nullptr;
|
||||
e->sec_handle = secondary_handle.release();
|
||||
e->Ref();
|
||||
|
||||
if (wait) {
|
||||
Promote(e);
|
||||
if (!e->value) {
|
||||
// The secondary cache returned a handle, but the lookup failed
|
||||
e->Unref();
|
||||
e->Free();
|
||||
e = nullptr;
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
e->Ref();
|
||||
e->SetHit();
|
||||
}
|
||||
return reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
@@ -339,81 +539,35 @@ bool LRUCacheShard::Release(Cache::Handle* handle, bool force_erase) {
|
||||
Status LRUCacheShard::Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
const Cache::CacheItemHelper* helper,
|
||||
Cache::Handle** handle, Cache::Priority priority) {
|
||||
// Allocate the memory here outside of the mutex
|
||||
// If the cache is full, we'll have to release it
|
||||
// It shouldn't happen very often though.
|
||||
LRUHandle* e = reinterpret_cast<LRUHandle*>(
|
||||
new char[sizeof(LRUHandle) - 1 + key.size()]);
|
||||
Status s = Status::OK();
|
||||
autovector<LRUHandle*> last_reference_list;
|
||||
|
||||
e->value = value;
|
||||
e->deleter = deleter;
|
||||
e->flags = 0;
|
||||
if (helper) {
|
||||
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;
|
||||
e->key_length = key.size();
|
||||
e->flags = 0;
|
||||
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());
|
||||
size_t total_charge = e->CalcTotalCharge(metadata_charge_policy_);
|
||||
|
||||
{
|
||||
MutexLock l(&mutex_);
|
||||
|
||||
// Free the space following strict LRU policy until enough space
|
||||
// is freed or the lru list is empty
|
||||
EvictFromLRU(total_charge, &last_reference_list);
|
||||
|
||||
if ((usage_ + total_charge) > capacity_ &&
|
||||
(strict_capacity_limit_ || handle == nullptr)) {
|
||||
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;
|
||||
s = Status::Incomplete("Insert failed due to LRU cache being full.");
|
||||
}
|
||||
} else {
|
||||
// Insert into the cache. Note that the cache might get larger than its
|
||||
// capacity if not enough space was freed up.
|
||||
LRUHandle* old = table_.Insert(e);
|
||||
usage_ += total_charge;
|
||||
if (old != nullptr) {
|
||||
s = Status::OkOverwritten();
|
||||
assert(old->InCache());
|
||||
old->SetInCache(false);
|
||||
if (!old->HasRefs()) {
|
||||
// old is on LRU because it's in cache and its reference count is 0
|
||||
LRU_Remove(old);
|
||||
size_t old_total_charge =
|
||||
old->CalcTotalCharge(metadata_charge_policy_);
|
||||
assert(usage_ >= old_total_charge);
|
||||
usage_ -= old_total_charge;
|
||||
last_reference_list.push_back(old);
|
||||
}
|
||||
}
|
||||
if (handle == nullptr) {
|
||||
LRU_Insert(e);
|
||||
} else {
|
||||
e->Ref();
|
||||
*handle = reinterpret_cast<Cache::Handle*>(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free the entries here outside of mutex for performance reasons
|
||||
for (auto entry : last_reference_list) {
|
||||
entry->Free();
|
||||
}
|
||||
|
||||
return s;
|
||||
return InsertItem(e, handle, /* free_handle_on_fail */ true);
|
||||
}
|
||||
|
||||
void LRUCacheShard::Erase(const Slice& key, uint32_t hash) {
|
||||
@@ -443,6 +597,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_;
|
||||
@@ -469,7 +635,8 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit, double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> allocator,
|
||||
bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy)
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache)
|
||||
: ShardedCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
std::move(allocator)) {
|
||||
num_shards_ = 1 << num_shard_bits;
|
||||
@@ -477,10 +644,12 @@ LRUCache::LRUCache(size_t capacity, int num_shard_bits,
|
||||
port::cacheline_aligned_alloc(sizeof(LRUCacheShard) * num_shards_));
|
||||
size_t per_shard = (capacity + (num_shards_ - 1)) / num_shards_;
|
||||
for (int i = 0; i < num_shards_; i++) {
|
||||
new (&shards_[i])
|
||||
LRUCacheShard(per_shard, strict_capacity_limit, high_pri_pool_ratio,
|
||||
use_adaptive_mutex, metadata_charge_policy);
|
||||
new (&shards_[i]) LRUCacheShard(
|
||||
per_shard, strict_capacity_limit, high_pri_pool_ratio,
|
||||
use_adaptive_mutex, metadata_charge_policy,
|
||||
/* max_upper_hash_bits */ 32 - num_shard_bits, secondary_cache);
|
||||
}
|
||||
secondary_cache_ = secondary_cache;
|
||||
}
|
||||
|
||||
LRUCache::~LRUCache() {
|
||||
@@ -493,11 +662,11 @@ LRUCache::~LRUCache() {
|
||||
}
|
||||
}
|
||||
|
||||
CacheShard* LRUCache::GetShard(int shard) {
|
||||
CacheShard* LRUCache::GetShard(uint32_t shard) {
|
||||
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
||||
}
|
||||
|
||||
const CacheShard* LRUCache::GetShard(int shard) const {
|
||||
const CacheShard* LRUCache::GetShard(uint32_t shard) const {
|
||||
return reinterpret_cast<CacheShard*>(&shards_[shard]);
|
||||
}
|
||||
|
||||
@@ -509,23 +678,25 @@ size_t LRUCache::GetCharge(Handle* handle) const {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->charge;
|
||||
}
|
||||
|
||||
Cache::DeleterFn LRUCache::GetDeleter(Handle* handle) const {
|
||||
auto h = reinterpret_cast<const LRUHandle*>(handle);
|
||||
if (h->IsSecondaryCacheCompatible()) {
|
||||
return h->info_.helper->del_cb;
|
||||
} else {
|
||||
return h->info_.deleter;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t LRUCache::GetHash(Handle* handle) const {
|
||||
return reinterpret_cast<const LRUHandle*>(handle)->hash;
|
||||
}
|
||||
|
||||
void LRUCache::DisownData() {
|
||||
// Do not drop data if compile with ASAN to suppress leak warning.
|
||||
#if defined(__clang__)
|
||||
#if !defined(__has_feature) || !__has_feature(address_sanitizer)
|
||||
#ifndef MUST_FREE_HEAP_ALLOCATIONS
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
#endif
|
||||
#else // __clang__
|
||||
#ifndef __SANITIZE_ADDRESS__
|
||||
shards_ = nullptr;
|
||||
num_shards_ = 0;
|
||||
#endif // !__SANITIZE_ADDRESS__
|
||||
#endif // __clang__
|
||||
}
|
||||
|
||||
size_t LRUCache::TEST_GetLRUSize() {
|
||||
@@ -544,19 +715,42 @@ double LRUCache::GetHighPriPoolRatio() {
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit,
|
||||
cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy);
|
||||
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,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache) {
|
||||
if (num_shard_bits >= 20) {
|
||||
return nullptr; // the cache cannot be sharded into too many fine pieces
|
||||
}
|
||||
@@ -569,7 +763,25 @@ std::shared_ptr<Cache> NewLRUCache(
|
||||
}
|
||||
return std::make_shared<LRUCache>(
|
||||
capacity, num_shard_bits, strict_capacity_limit, high_pri_pool_ratio,
|
||||
std::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy);
|
||||
std::move(memory_allocator), use_adaptive_mutex, metadata_charge_policy,
|
||||
secondary_cache);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(const LRUCacheOptions& cache_opts) {
|
||||
return NewLRUCache(
|
||||
cache_opts.capacity, cache_opts.num_shard_bits,
|
||||
cache_opts.strict_capacity_limit, cache_opts.high_pri_pool_ratio,
|
||||
cache_opts.memory_allocator, cache_opts.use_adaptive_mutex,
|
||||
cache_opts.metadata_charge_policy, cache_opts.secondary_cache);
|
||||
}
|
||||
|
||||
std::shared_ptr<Cache> NewLRUCache(
|
||||
size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
return NewLRUCache(capacity, num_shard_bits, strict_capacity_limit,
|
||||
high_pri_pool_ratio, memory_allocator, use_adaptive_mutex,
|
||||
metadata_charge_policy, nullptr);
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+166
-25
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2011-present, Facebook, Inc. All rights reserved.
|
||||
// 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).
|
||||
@@ -8,12 +8,14 @@
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "cache/sharded_cache.h"
|
||||
|
||||
#include "port/lang.h"
|
||||
#include "port/malloc.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/secondary_cache.h"
|
||||
#include "util/autovector.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
@@ -49,8 +51,18 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct LRUHandle {
|
||||
void* value;
|
||||
void (*deleter)(const Slice&, void* value);
|
||||
LRUHandle* next_hash;
|
||||
union Info {
|
||||
Info() {}
|
||||
~Info() {}
|
||||
Cache::DeleterFn deleter;
|
||||
const ShardedCache::CacheItemHelper* helper;
|
||||
} info_;
|
||||
// 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?
|
||||
@@ -67,12 +79,26 @@ struct LRUHandle {
|
||||
IS_HIGH_PRI = (1 << 1),
|
||||
// Whether this entry is in high-pri pool.
|
||||
IN_HIGH_PRI_POOL = (1 << 2),
|
||||
// Wwhether this entry has had any lookups (hits).
|
||||
// Whether this entry has had any lookups (hits).
|
||||
HAS_HIT = (1 << 3),
|
||||
// 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
|
||||
IS_PROMOTED = (1 << 6),
|
||||
};
|
||||
|
||||
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];
|
||||
|
||||
@@ -95,6 +121,15 @@ struct LRUHandle {
|
||||
bool IsHighPri() const { return flags & IS_HIGH_PRI; }
|
||||
bool InHighPriPool() const { return flags & IN_HIGH_PRI_POOL; }
|
||||
bool HasHit() const { return flags & HAS_HIT; }
|
||||
bool IsSecondaryCacheCompatible() const {
|
||||
#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; }
|
||||
|
||||
void SetInCache(bool in_cache) {
|
||||
if (in_cache) {
|
||||
@@ -122,15 +157,58 @@ struct LRUHandle {
|
||||
|
||||
void SetHit() { flags |= HAS_HIT; }
|
||||
|
||||
void SetSecondaryCacheCompatible(bool compat) {
|
||||
if (compat) {
|
||||
flags |= IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
} else {
|
||||
flags &= ~IS_SECONDARY_CACHE_COMPATIBLE;
|
||||
}
|
||||
#ifdef __SANITIZE_THREAD__
|
||||
is_secondary_cache_compatible_for_tsan = compat;
|
||||
#endif // __SANITIZE_THREAD__
|
||||
}
|
||||
|
||||
void SetIncomplete(bool incomp) {
|
||||
if (incomp) {
|
||||
flags |= IS_PENDING;
|
||||
} else {
|
||||
flags &= ~IS_PENDING;
|
||||
}
|
||||
}
|
||||
|
||||
void SetPromoted(bool promoted) {
|
||||
if (promoted) {
|
||||
flags |= IS_PROMOTED;
|
||||
} else {
|
||||
flags &= ~IS_PROMOTED;
|
||||
}
|
||||
}
|
||||
|
||||
void Free() {
|
||||
assert(refs == 0);
|
||||
if (deleter) {
|
||||
(*deleter)(key(), value);
|
||||
#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()) {
|
||||
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);
|
||||
}
|
||||
|
||||
// Caclculate the memory usage by metadata
|
||||
// Calculate the memory usage by metadata
|
||||
inline size_t CalcTotalCharge(
|
||||
CacheMetadataChargePolicy metadata_charge_policy) {
|
||||
size_t meta_charge = 0;
|
||||
@@ -153,7 +231,10 @@ struct LRUHandle {
|
||||
// 4.4.3's builtin hashtable.
|
||||
class LRUHandleTable {
|
||||
public:
|
||||
LRUHandleTable();
|
||||
// If the table uses more hash bits than `max_upper_hash_bits`,
|
||||
// it will eat into the bits used for sharding, which are constant
|
||||
// for a given LRUHandleTable.
|
||||
explicit LRUHandleTable(int max_upper_hash_bits);
|
||||
~LRUHandleTable();
|
||||
|
||||
LRUHandle* Lookup(const Slice& key, uint32_t hash);
|
||||
@@ -161,8 +242,8 @@ class LRUHandleTable {
|
||||
LRUHandle* Remove(const Slice& key, uint32_t hash);
|
||||
|
||||
template <typename T>
|
||||
void ApplyToAllCacheEntries(T func) {
|
||||
for (uint32_t i = 0; i < length_; i++) {
|
||||
void ApplyToEntriesRange(T func, uint32_t index_begin, uint32_t index_end) {
|
||||
for (uint32_t i = index_begin; i < index_end; i++) {
|
||||
LRUHandle* h = list_[i];
|
||||
while (h != nullptr) {
|
||||
auto n = h->next_hash;
|
||||
@@ -173,6 +254,8 @@ class LRUHandleTable {
|
||||
}
|
||||
}
|
||||
|
||||
int GetLengthBits() const { return length_bits_; }
|
||||
|
||||
private:
|
||||
// Return a pointer to slot that points to a cache entry that
|
||||
// matches key/hash. If there is no such cache entry, return a
|
||||
@@ -181,11 +264,19 @@ class LRUHandleTable {
|
||||
|
||||
void Resize();
|
||||
|
||||
// Number of hash bits (upper because lower bits used for sharding)
|
||||
// used for table index. Length == 1 << length_bits_
|
||||
int length_bits_;
|
||||
|
||||
// The table consists of an array of buckets where each bucket is
|
||||
// a linked list of cache entries that hash into the bucket.
|
||||
LRUHandle** list_;
|
||||
uint32_t length_;
|
||||
std::unique_ptr<LRUHandle*[]> list_;
|
||||
|
||||
// Number of elements currently in the table
|
||||
uint32_t elems_;
|
||||
|
||||
// Set from max_upper_hash_bits (see constructor)
|
||||
const int max_length_bits_;
|
||||
};
|
||||
|
||||
// A single shard of sharded cache.
|
||||
@@ -193,7 +284,9 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
public:
|
||||
LRUCacheShard(size_t capacity, bool strict_capacity_limit,
|
||||
double high_pri_pool_ratio, bool use_adaptive_mutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy);
|
||||
CacheMetadataChargePolicy metadata_charge_policy,
|
||||
int max_upper_hash_bits,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache);
|
||||
virtual ~LRUCacheShard() override = default;
|
||||
|
||||
// Separate from constructor so caller can easily make an array of LRUCache
|
||||
@@ -209,11 +302,34 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
|
||||
// Like Cache methods, but with an extra "hash" parameter.
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
size_t charge, Cache::DeleterFn deleter,
|
||||
Cache::Handle** handle,
|
||||
Cache::Priority priority) override;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override;
|
||||
Cache::Priority priority) override {
|
||||
return Insert(key, hash, value, charge, deleter, nullptr, handle, priority);
|
||||
}
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
Cache::Handle** handle,
|
||||
Cache::Priority priority) override {
|
||||
assert(helper);
|
||||
return Insert(key, hash, value, charge, nullptr, helper, handle, priority);
|
||||
}
|
||||
// If helper_cb is null, the values of the following arguments don't
|
||||
// matter
|
||||
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;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) override {
|
||||
return Lookup(key, hash, nullptr, nullptr, Cache::Priority::LOW, true);
|
||||
}
|
||||
virtual bool Release(Cache::Handle* handle, bool /*useful*/,
|
||||
bool force_erase) override {
|
||||
return Release(handle, force_erase);
|
||||
}
|
||||
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,
|
||||
bool force_erase = false) override;
|
||||
@@ -226,8 +342,10 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
virtual size_t GetUsage() const override;
|
||||
virtual size_t GetPinnedUsage() const override;
|
||||
|
||||
virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) override;
|
||||
virtual void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
uint32_t average_entries_per_lock, uint32_t* state) override;
|
||||
|
||||
virtual void EraseUnRefEntries() override;
|
||||
|
||||
@@ -239,10 +357,27 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// not threadsafe
|
||||
size_t TEST_GetLRUSize();
|
||||
|
||||
// Retrives high pri pool ratio
|
||||
// Retrieves high pri pool ratio
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
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);
|
||||
|
||||
@@ -303,6 +438,8 @@ class ALIGN_AS(CACHE_LINE_SIZE) LRUCacheShard final : public CacheShard {
|
||||
// We don't count mutex_ as the cache's internal state so semantically we
|
||||
// don't mind mutex_ invoking the non-const actions.
|
||||
mutable port::Mutex mutex_;
|
||||
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
class LRUCache
|
||||
@@ -316,24 +453,28 @@ class LRUCache
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr,
|
||||
bool use_adaptive_mutex = kDefaultToAdaptiveMutex,
|
||||
CacheMetadataChargePolicy metadata_charge_policy =
|
||||
kDontChargeCacheMetadata);
|
||||
kDontChargeCacheMetadata,
|
||||
const std::shared_ptr<SecondaryCache>& secondary_cache = nullptr);
|
||||
virtual ~LRUCache();
|
||||
virtual const char* Name() const override { return "LRUCache"; }
|
||||
virtual CacheShard* GetShard(int shard) override;
|
||||
virtual const CacheShard* GetShard(int shard) const override;
|
||||
virtual CacheShard* GetShard(uint32_t shard) override;
|
||||
virtual const CacheShard* GetShard(uint32_t shard) const override;
|
||||
virtual void* Value(Handle* handle) override;
|
||||
virtual size_t GetCharge(Handle* handle) const override;
|
||||
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;
|
||||
|
||||
// Retrieves number of elements in LRU, for unit test purpose only
|
||||
size_t TEST_GetLRUSize();
|
||||
// Retrives high pri pool ratio
|
||||
// Retrieves high pri pool ratio
|
||||
double GetHighPriPoolRatio();
|
||||
|
||||
private:
|
||||
LRUCacheShard* shards_ = nullptr;
|
||||
int num_shards_ = 0;
|
||||
std::shared_ptr<SecondaryCache> secondary_cache_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+986
-3
File diff suppressed because it is too large
Load Diff
Vendored
+91
-21
@@ -9,53 +9,96 @@
|
||||
|
||||
#include "cache/sharded_cache.h"
|
||||
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include "util/hash.h"
|
||||
#include "util/math.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
inline uint32_t HashSlice(const Slice& s) {
|
||||
return Lower32of64(GetSliceNPHash64(s));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ShardedCache::ShardedCache(size_t capacity, int num_shard_bits,
|
||||
bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> allocator)
|
||||
: Cache(std::move(allocator)),
|
||||
num_shard_bits_(num_shard_bits),
|
||||
shard_mask_((uint32_t{1} << num_shard_bits) - 1),
|
||||
capacity_(capacity),
|
||||
strict_capacity_limit_(strict_capacity_limit),
|
||||
last_id_(1) {}
|
||||
|
||||
void ShardedCache::SetCapacity(size_t capacity) {
|
||||
int num_shards = 1 << num_shard_bits_;
|
||||
uint32_t num_shards = GetNumShards();
|
||||
const size_t per_shard = (capacity + (num_shards - 1)) / num_shards;
|
||||
MutexLock l(&capacity_mutex_);
|
||||
for (int s = 0; s < num_shards; s++) {
|
||||
for (uint32_t s = 0; s < num_shards; s++) {
|
||||
GetShard(s)->SetCapacity(per_shard);
|
||||
}
|
||||
capacity_ = capacity;
|
||||
}
|
||||
|
||||
void ShardedCache::SetStrictCapacityLimit(bool strict_capacity_limit) {
|
||||
int num_shards = 1 << num_shard_bits_;
|
||||
uint32_t num_shards = GetNumShards();
|
||||
MutexLock l(&capacity_mutex_);
|
||||
for (int s = 0; s < num_shards; s++) {
|
||||
for (uint32_t s = 0; s < num_shards; s++) {
|
||||
GetShard(s)->SetStrictCapacityLimit(strict_capacity_limit);
|
||||
}
|
||||
strict_capacity_limit_ = strict_capacity_limit;
|
||||
}
|
||||
|
||||
Status ShardedCache::Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle, Priority priority) {
|
||||
DeleterFn deleter, Handle** handle,
|
||||
Priority priority) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))
|
||||
->Insert(key, hash, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
Status ShardedCache::Insert(const Slice& key, void* value,
|
||||
const CacheItemHelper* helper, size_t charge,
|
||||
Handle** handle, Priority priority) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
if (!helper) {
|
||||
return Status::InvalidArgument();
|
||||
}
|
||||
return GetShard(Shard(hash))
|
||||
->Insert(key, hash, value, helper, charge, handle, priority);
|
||||
}
|
||||
|
||||
Cache::Handle* ShardedCache::Lookup(const Slice& key, Statistics* /*stats*/) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))->Lookup(key, hash);
|
||||
}
|
||||
|
||||
Cache::Handle* ShardedCache::Lookup(const Slice& key,
|
||||
const CacheItemHelper* helper,
|
||||
const CreateCallback& create_cb,
|
||||
Priority priority, bool wait,
|
||||
Statistics* /*stats*/) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
return GetShard(Shard(hash))
|
||||
->Lookup(key, hash, helper, create_cb, priority, wait);
|
||||
}
|
||||
|
||||
bool ShardedCache::IsReady(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->IsReady(handle);
|
||||
}
|
||||
|
||||
void ShardedCache::Wait(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
GetShard(Shard(hash))->Wait(handle);
|
||||
}
|
||||
|
||||
bool ShardedCache::Ref(Handle* handle) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->Ref(handle);
|
||||
@@ -66,6 +109,11 @@ bool ShardedCache::Release(Handle* handle, bool force_erase) {
|
||||
return GetShard(Shard(hash))->Release(handle, force_erase);
|
||||
}
|
||||
|
||||
bool ShardedCache::Release(Handle* handle, bool useful, bool force_erase) {
|
||||
uint32_t hash = GetHash(handle);
|
||||
return GetShard(Shard(hash))->Release(handle, useful, force_erase);
|
||||
}
|
||||
|
||||
void ShardedCache::Erase(const Slice& key) {
|
||||
uint32_t hash = HashSlice(key);
|
||||
GetShard(Shard(hash))->Erase(key, hash);
|
||||
@@ -87,9 +135,9 @@ bool ShardedCache::HasStrictCapacityLimit() const {
|
||||
|
||||
size_t ShardedCache::GetUsage() const {
|
||||
// We will not lock the cache when getting the usage from shards.
|
||||
int num_shards = 1 << num_shard_bits_;
|
||||
uint32_t num_shards = GetNumShards();
|
||||
size_t usage = 0;
|
||||
for (int s = 0; s < num_shards; s++) {
|
||||
for (uint32_t s = 0; s < num_shards; s++) {
|
||||
usage += GetShard(s)->GetUsage();
|
||||
}
|
||||
return usage;
|
||||
@@ -101,25 +149,42 @@ size_t ShardedCache::GetUsage(Handle* handle) const {
|
||||
|
||||
size_t ShardedCache::GetPinnedUsage() const {
|
||||
// We will not lock the cache when getting the usage from shards.
|
||||
int num_shards = 1 << num_shard_bits_;
|
||||
uint32_t num_shards = GetNumShards();
|
||||
size_t usage = 0;
|
||||
for (int s = 0; s < num_shards; s++) {
|
||||
for (uint32_t s = 0; s < num_shards; s++) {
|
||||
usage += GetShard(s)->GetPinnedUsage();
|
||||
}
|
||||
return usage;
|
||||
}
|
||||
|
||||
void ShardedCache::ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) {
|
||||
int num_shards = 1 << num_shard_bits_;
|
||||
for (int s = 0; s < num_shards; s++) {
|
||||
GetShard(s)->ApplyToAllCacheEntries(callback, thread_safe);
|
||||
}
|
||||
void ShardedCache::ApplyToAllEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
const ApplyToAllEntriesOptions& opts) {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
// Iterate over part of each shard, rotating between shards, to
|
||||
// minimize impact on latency of concurrent operations.
|
||||
std::unique_ptr<uint32_t[]> states(new uint32_t[num_shards]{});
|
||||
|
||||
uint32_t aepl_in_32 = static_cast<uint32_t>(
|
||||
std::min(size_t{UINT32_MAX}, opts.average_entries_per_lock));
|
||||
aepl_in_32 = std::min(aepl_in_32, uint32_t{1});
|
||||
|
||||
bool remaining_work;
|
||||
do {
|
||||
remaining_work = false;
|
||||
for (uint32_t s = 0; s < num_shards; s++) {
|
||||
if (states[s] != UINT32_MAX) {
|
||||
GetShard(s)->ApplyToSomeEntries(callback, aepl_in_32, &states[s]);
|
||||
remaining_work |= states[s] != UINT32_MAX;
|
||||
}
|
||||
}
|
||||
} while (remaining_work);
|
||||
}
|
||||
|
||||
void ShardedCache::EraseUnRefEntries() {
|
||||
int num_shards = 1 << num_shard_bits_;
|
||||
for (int s = 0; s < num_shards; s++) {
|
||||
uint32_t num_shards = GetNumShards();
|
||||
for (uint32_t s = 0; s < num_shards; s++) {
|
||||
GetShard(s)->EraseUnRefEntries();
|
||||
}
|
||||
}
|
||||
@@ -134,7 +199,8 @@ std::string ShardedCache::GetPrintableOptions() const {
|
||||
snprintf(buffer, kBufferSize, " capacity : %" ROCKSDB_PRIszt "\n",
|
||||
capacity_);
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " num_shard_bits : %d\n", num_shard_bits_);
|
||||
snprintf(buffer, kBufferSize, " num_shard_bits : %d\n",
|
||||
GetNumShardBits());
|
||||
ret.append(buffer);
|
||||
snprintf(buffer, kBufferSize, " strict_capacity_limit : %d\n",
|
||||
strict_capacity_limit_);
|
||||
@@ -159,4 +225,8 @@ int GetDefaultCacheShardBits(size_t capacity) {
|
||||
return num_shard_bits;
|
||||
}
|
||||
|
||||
int ShardedCache::GetNumShardBits() const { return BitsSetToOne(shard_mask_); }
|
||||
|
||||
uint32_t ShardedCache::GetNumShards() const { return shard_mask_ + 1; }
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
Vendored
+47
-27
@@ -14,7 +14,6 @@
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "util/hash.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -24,20 +23,37 @@ class CacheShard {
|
||||
CacheShard() = default;
|
||||
virtual ~CacheShard() = default;
|
||||
|
||||
using DeleterFn = Cache::DeleterFn;
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
size_t charge, DeleterFn deleter,
|
||||
Cache::Handle** handle, Cache::Priority priority) = 0;
|
||||
virtual Status Insert(const Slice& key, uint32_t hash, void* value,
|
||||
const Cache::CacheItemHelper* helper, size_t charge,
|
||||
Cache::Handle** handle, Cache::Priority priority) = 0;
|
||||
virtual Cache::Handle* Lookup(const Slice& key, uint32_t hash) = 0;
|
||||
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;
|
||||
virtual bool Release(Cache::Handle* handle, bool useful,
|
||||
bool force_erase) = 0;
|
||||
virtual bool IsReady(Cache::Handle* handle) = 0;
|
||||
virtual void Wait(Cache::Handle* handle) = 0;
|
||||
virtual bool Ref(Cache::Handle* handle) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool force_erase = false) = 0;
|
||||
virtual bool Release(Cache::Handle* handle, bool force_erase) = 0;
|
||||
virtual void Erase(const Slice& key, uint32_t hash) = 0;
|
||||
virtual void SetCapacity(size_t capacity) = 0;
|
||||
virtual void SetStrictCapacityLimit(bool strict_capacity_limit) = 0;
|
||||
virtual size_t GetUsage() const = 0;
|
||||
virtual size_t GetPinnedUsage() const = 0;
|
||||
virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) = 0;
|
||||
// Handles iterating over roughly `average_entries_per_lock` entries, using
|
||||
// `state` to somehow record where it last ended up. Caller initially uses
|
||||
// *state == 0 and implementation sets *state = UINT32_MAX to indicate
|
||||
// completion.
|
||||
virtual void ApplyToSomeEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
uint32_t average_entries_per_lock, uint32_t* state) = 0;
|
||||
virtual void EraseUnRefEntries() = 0;
|
||||
virtual std::string GetPrintableOptions() const { return ""; }
|
||||
void set_metadata_charge_policy(
|
||||
@@ -57,22 +73,29 @@ class ShardedCache : public Cache {
|
||||
ShardedCache(size_t capacity, int num_shard_bits, bool strict_capacity_limit,
|
||||
std::shared_ptr<MemoryAllocator> memory_allocator = nullptr);
|
||||
virtual ~ShardedCache() = default;
|
||||
virtual const char* Name() const override = 0;
|
||||
virtual CacheShard* GetShard(int shard) = 0;
|
||||
virtual const CacheShard* GetShard(int shard) const = 0;
|
||||
virtual void* Value(Handle* handle) override = 0;
|
||||
virtual size_t GetCharge(Handle* handle) const override = 0;
|
||||
virtual CacheShard* GetShard(uint32_t shard) = 0;
|
||||
virtual const CacheShard* GetShard(uint32_t shard) const = 0;
|
||||
|
||||
virtual uint32_t GetHash(Handle* handle) const = 0;
|
||||
virtual void DisownData() override = 0;
|
||||
|
||||
virtual void SetCapacity(size_t capacity) override;
|
||||
virtual void SetStrictCapacityLimit(bool strict_capacity_limit) override;
|
||||
|
||||
virtual Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle, Priority priority) override;
|
||||
DeleterFn deleter, Handle** handle,
|
||||
Priority priority) override;
|
||||
virtual Status Insert(const Slice& key, void* value,
|
||||
const CacheItemHelper* helper, size_t chargge,
|
||||
Handle** handle = nullptr,
|
||||
Priority priority = Priority::LOW) override;
|
||||
virtual Handle* Lookup(const Slice& key, Statistics* stats) override;
|
||||
virtual Handle* Lookup(const Slice& key, const CacheItemHelper* helper,
|
||||
const CreateCallback& create_cb, Priority priority,
|
||||
bool wait, Statistics* stats = nullptr) override;
|
||||
virtual bool Release(Handle* handle, bool useful,
|
||||
bool force_erase = false) override;
|
||||
virtual bool IsReady(Handle* handle) override;
|
||||
virtual void Wait(Handle* handle) override;
|
||||
virtual bool Ref(Handle* handle) override;
|
||||
virtual bool Release(Handle* handle, bool force_erase = false) override;
|
||||
virtual void Erase(const Slice& key) override;
|
||||
@@ -82,24 +105,21 @@ class ShardedCache : public Cache {
|
||||
virtual size_t GetUsage() const override;
|
||||
virtual size_t GetUsage(Handle* handle) const override;
|
||||
virtual size_t GetPinnedUsage() const override;
|
||||
virtual void ApplyToAllCacheEntries(void (*callback)(void*, size_t),
|
||||
bool thread_safe) override;
|
||||
virtual void ApplyToAllEntries(
|
||||
const std::function<void(const Slice& key, void* value, size_t charge,
|
||||
DeleterFn deleter)>& callback,
|
||||
const ApplyToAllEntriesOptions& opts) override;
|
||||
virtual void EraseUnRefEntries() override;
|
||||
virtual std::string GetPrintableOptions() const override;
|
||||
|
||||
int GetNumShardBits() const { return num_shard_bits_; }
|
||||
int GetNumShardBits() const;
|
||||
uint32_t GetNumShards() const;
|
||||
|
||||
protected:
|
||||
inline uint32_t Shard(uint32_t hash) { return hash & shard_mask_; }
|
||||
|
||||
private:
|
||||
static inline uint32_t HashSlice(const Slice& s) {
|
||||
return static_cast<uint32_t>(GetSliceNPHash64(s));
|
||||
}
|
||||
|
||||
uint32_t Shard(uint32_t hash) {
|
||||
// Note, hash >> 32 yields hash in gcc, not the zero we expect!
|
||||
return (num_shard_bits_ > 0) ? (hash >> (32 - num_shard_bits_)) : 0;
|
||||
}
|
||||
|
||||
int num_shard_bits_;
|
||||
const uint32_t shard_mask_;
|
||||
mutable port::Mutex capacity_mutex_;
|
||||
size_t capacity_;
|
||||
bool strict_capacity_limit_;
|
||||
|
||||
+22
-24
@@ -30,20 +30,18 @@ Status ArenaWrappedDBIter::GetProperty(std::string prop_name,
|
||||
return db_iter_->GetProperty(prop_name, prop);
|
||||
}
|
||||
|
||||
void ArenaWrappedDBIter::Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iteration,
|
||||
uint64_t version_number,
|
||||
ReadCallback* read_callback, DBImpl* db_impl,
|
||||
ColumnFamilyData* cfd, bool allow_blob,
|
||||
bool allow_refresh) {
|
||||
void ArenaWrappedDBIter::Init(
|
||||
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, nullptr, sequence,
|
||||
true, max_sequential_skip_in_iteration,
|
||||
read_callback, db_impl, cfd, allow_blob);
|
||||
db_iter_ =
|
||||
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;
|
||||
read_options_ = read_options;
|
||||
allow_refresh_ = allow_refresh;
|
||||
@@ -72,8 +70,9 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
read_callback_->Refresh(latest_seq);
|
||||
}
|
||||
Init(env, read_options_, *(cfd_->ioptions()), sv->mutable_cf_options,
|
||||
latest_seq, sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number, read_callback_, db_impl_, cfd_, allow_blob_,
|
||||
sv->current, latest_seq,
|
||||
sv->mutable_cf_options.max_sequential_skip_in_iterations,
|
||||
cur_sv_number, read_callback_, db_impl_, cfd_, expose_blob_index_,
|
||||
allow_refresh_);
|
||||
|
||||
InternalIterator* internal_iter = db_impl_->NewInternalIterator(
|
||||
@@ -88,18 +87,17 @@ Status ArenaWrappedDBIter::Refresh() {
|
||||
}
|
||||
|
||||
ArenaWrappedDBIter* NewArenaWrappedDbIterator(
|
||||
Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options,
|
||||
const MutableCFOptions& mutable_cf_options, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
bool allow_blob, bool allow_refresh) {
|
||||
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, sequence,
|
||||
iter->Init(env, read_options, ioptions, mutable_cf_options, version, sequence,
|
||||
max_sequential_skip_in_iterations, version_number, read_callback,
|
||||
db_impl, cfd, allow_blob, allow_refresh);
|
||||
db_impl, cfd, expose_blob_index, allow_refresh);
|
||||
if (db_impl != nullptr && cfd != nullptr && allow_refresh) {
|
||||
iter->StoreRefreshInfo(db_impl, cfd, read_callback, allow_blob);
|
||||
iter->StoreRefreshInfo(db_impl, cfd, read_callback, expose_blob_index);
|
||||
}
|
||||
|
||||
return iter;
|
||||
|
||||
+13
-13
@@ -23,6 +23,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Arena;
|
||||
class Version;
|
||||
|
||||
// A wrapper iterator which wraps DB Iterator and the arena, with which the DB
|
||||
// iterator is supposed to be allocated. This class is used as an entry point of
|
||||
@@ -71,21 +72,21 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
Status Refresh() override;
|
||||
|
||||
void Init(Env* env, const ReadOptions& read_options,
|
||||
const ImmutableCFOptions& cf_options,
|
||||
const MutableCFOptions& mutable_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,
|
||||
ReadCallback* read_callback, DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
bool allow_blob, bool allow_refresh);
|
||||
bool expose_blob_index, bool allow_refresh);
|
||||
|
||||
// Store some parameters so we can refresh the iterator at a later point
|
||||
// with these same params
|
||||
void StoreRefreshInfo(DBImpl* db_impl, ColumnFamilyData* cfd,
|
||||
ReadCallback* read_callback, bool allow_blob) {
|
||||
ReadCallback* read_callback, bool expose_blob_index) {
|
||||
db_impl_ = db_impl;
|
||||
cfd_ = cfd;
|
||||
read_callback_ = read_callback;
|
||||
allow_blob_ = allow_blob;
|
||||
expose_blob_index_ = expose_blob_index;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -96,7 +97,7 @@ class ArenaWrappedDBIter : public Iterator {
|
||||
DBImpl* db_impl_ = nullptr;
|
||||
ReadOptions read_options_;
|
||||
ReadCallback* read_callback_;
|
||||
bool allow_blob_ = false;
|
||||
bool expose_blob_index_ = false;
|
||||
bool allow_refresh_ = true;
|
||||
};
|
||||
|
||||
@@ -104,11 +105,10 @@ 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 ImmutableCFOptions& cf_options,
|
||||
const MutableCFOptions& mutable_cf_options, const SequenceNumber& sequence,
|
||||
uint64_t max_sequential_skip_in_iterations, uint64_t version_number,
|
||||
ReadCallback* read_callback, DBImpl* db_impl = nullptr,
|
||||
ColumnFamilyData* cfd = nullptr, bool allow_blob = false,
|
||||
bool allow_refresh = true);
|
||||
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 = nullptr, ColumnFamilyData* cfd = nullptr,
|
||||
bool expose_blob_index = false, bool allow_refresh = true);
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -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
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <cassert>
|
||||
|
||||
#include "db/blob/blob_file_addition.h"
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
@@ -17,41 +18,46 @@
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "logging/logging.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/status.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "trace_replay/io_tracer.h"
|
||||
#include "util/compression.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
VersionSet* versions, Env* env, FileSystem* fs,
|
||||
const ImmutableCFOptions* immutable_cf_options,
|
||||
VersionSet* versions, FileSystem* fs,
|
||||
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,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, env,
|
||||
fs, immutable_cf_options, mutable_cf_options,
|
||||
file_options, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint,
|
||||
blob_file_paths, blob_file_additions) {}
|
||||
: BlobFileBuilder([versions]() { return versions->NewFileNumber(); }, fs,
|
||||
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) {}
|
||||
|
||||
BlobFileBuilder::BlobFileBuilder(
|
||||
std::function<uint64_t()> file_number_generator, Env* env, FileSystem* fs,
|
||||
const ImmutableCFOptions* immutable_cf_options,
|
||||
std::function<uint64_t()> file_number_generator, FileSystem* fs,
|
||||
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,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions)
|
||||
: file_number_generator_(std::move(file_number_generator)),
|
||||
env_(env),
|
||||
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),
|
||||
@@ -61,14 +67,15 @@ BlobFileBuilder::BlobFileBuilder(
|
||||
column_family_name_(column_family_name),
|
||||
io_priority_(io_priority),
|
||||
write_hint_(write_hint),
|
||||
io_tracer_(io_tracer),
|
||||
blob_callback_(blob_callback),
|
||||
blob_file_paths_(blob_file_paths),
|
||||
blob_file_additions_(blob_file_additions),
|
||||
blob_count_(0),
|
||||
blob_bytes_(0) {
|
||||
assert(file_number_generator_);
|
||||
assert(env_);
|
||||
assert(fs_);
|
||||
assert(immutable_cf_options_);
|
||||
assert(immutable_options_);
|
||||
assert(file_options_);
|
||||
assert(blob_file_paths_);
|
||||
assert(blob_file_paths_->empty());
|
||||
@@ -149,19 +156,20 @@ 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);
|
||||
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
|
||||
{
|
||||
TEST_SYNC_POINT("BlobFileBuilder::OpenBlobFileIfNeeded:NewWritableFile");
|
||||
|
||||
assert(file_options_);
|
||||
const Status s =
|
||||
NewWritableFile(fs_, blob_file_path, &file, *file_options_);
|
||||
Status s = NewWritableFile(fs_, blob_file_path, &file, *file_options_);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"BlobFileBuilder::OpenBlobFileIfNeeded:NewWritableFile", &s);
|
||||
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -176,17 +184,20 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
assert(file);
|
||||
file->SetIOPriority(io_priority_);
|
||||
file->SetWriteLifeTimeHint(write_hint_);
|
||||
|
||||
Statistics* const statistics = immutable_cf_options_->statistics;
|
||||
|
||||
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_, env_,
|
||||
nullptr /*IOTracer*/, statistics, immutable_cf_options_->listeners,
|
||||
immutable_cf_options_->file_checksum_gen_factory));
|
||||
std::move(file), blob_file_paths_->back(), *file_options_,
|
||||
immutable_options_->clock, io_tracer_, statistics,
|
||||
immutable_options_->listeners,
|
||||
immutable_options_->file_checksum_gen_factory.get(),
|
||||
tmp_set.Contains(FileType::kBlobFile)));
|
||||
|
||||
std::unique_ptr<BlobLogWriter> blob_log_writer(
|
||||
new BlobLogWriter(std::move(file_writer), env_, statistics,
|
||||
blob_file_number, immutable_cf_options_->use_fsync));
|
||||
constexpr bool do_flush = false;
|
||||
|
||||
std::unique_ptr<BlobLogWriter> blob_log_writer(new BlobLogWriter(
|
||||
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;
|
||||
@@ -195,9 +206,11 @@ Status BlobFileBuilder::OpenBlobFileIfNeeded() {
|
||||
expiration_range);
|
||||
|
||||
{
|
||||
TEST_SYNC_POINT("BlobFileBuilder::OpenBlobFileIfNeeded:WriteHeader");
|
||||
Status s = blob_log_writer->WriteHeader(header);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK(
|
||||
"BlobFileBuilder::OpenBlobFileIfNeeded:WriteHeader", &s);
|
||||
|
||||
const Status s = blob_log_writer->WriteHeader(header);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -247,9 +260,10 @@ Status BlobFileBuilder::WriteBlobToFile(const Slice& key, const Slice& blob,
|
||||
|
||||
uint64_t key_offset = 0;
|
||||
|
||||
TEST_SYNC_POINT("BlobFileBuilder::WriteBlobToFile:AddRecord");
|
||||
Status s = writer_->AddRecord(key, blob, &key_offset, blob_offset);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("BlobFileBuilder::WriteBlobToFile:AddRecord", &s);
|
||||
|
||||
const Status s = writer_->AddRecord(key, blob, &key_offset, blob_offset);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -271,10 +285,10 @@ Status BlobFileBuilder::CloseBlobFile() {
|
||||
std::string checksum_method;
|
||||
std::string checksum_value;
|
||||
|
||||
TEST_SYNC_POINT("BlobFileBuilder::WriteBlobToFile:AppendFooter");
|
||||
Status s = writer_->AppendFooter(footer, &checksum_method, &checksum_value);
|
||||
|
||||
TEST_SYNC_POINT_CALLBACK("BlobFileBuilder::WriteBlobToFile:AppendFooter", &s);
|
||||
|
||||
const Status s =
|
||||
writer_->AppendFooter(footer, &checksum_method, &checksum_value);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -286,18 +300,21 @@ Status BlobFileBuilder::CloseBlobFile() {
|
||||
std::move(checksum_method),
|
||||
std::move(checksum_value));
|
||||
|
||||
assert(immutable_cf_options_);
|
||||
ROCKS_LOG_INFO(immutable_cf_options_->info_log,
|
||||
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;
|
||||
blob_bytes_ = 0;
|
||||
|
||||
return Status::OK();
|
||||
return s;
|
||||
}
|
||||
|
||||
Status BlobFileBuilder::CloseBlobFileIfNeeded() {
|
||||
@@ -313,4 +330,20 @@ Status BlobFileBuilder::CloseBlobFileIfNeeded() {
|
||||
return CloseBlobFile();
|
||||
}
|
||||
|
||||
void BlobFileBuilder::Abandon() {
|
||||
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())
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
|
||||
writer_.reset();
|
||||
blob_count_ = 0;
|
||||
blob_bytes_ = 0;
|
||||
}
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -18,36 +18,42 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class VersionSet;
|
||||
class FileSystem;
|
||||
struct ImmutableCFOptions;
|
||||
class SystemClock;
|
||||
struct ImmutableOptions;
|
||||
struct MutableCFOptions;
|
||||
struct FileOptions;
|
||||
class BlobFileAddition;
|
||||
class Status;
|
||||
class Slice;
|
||||
class BlobLogWriter;
|
||||
class IOTracer;
|
||||
class BlobFileCompletionCallback;
|
||||
|
||||
class BlobFileBuilder {
|
||||
public:
|
||||
BlobFileBuilder(VersionSet* versions, Env* env, FileSystem* fs,
|
||||
const ImmutableCFOptions* immutable_cf_options,
|
||||
BlobFileBuilder(VersionSet* versions, FileSystem* fs,
|
||||
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,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions);
|
||||
|
||||
BlobFileBuilder(std::function<uint64_t()> file_number_generator, Env* env,
|
||||
FileSystem* fs,
|
||||
const ImmutableCFOptions* immutable_cf_options,
|
||||
BlobFileBuilder(std::function<uint64_t()> file_number_generator,
|
||||
FileSystem* fs, 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,
|
||||
std::vector<std::string>* blob_file_paths,
|
||||
std::vector<BlobFileAddition>* blob_file_additions);
|
||||
|
||||
@@ -58,6 +64,7 @@ class BlobFileBuilder {
|
||||
|
||||
Status Add(const Slice& key, const Slice& value, std::string* blob_index);
|
||||
Status Finish();
|
||||
void Abandon();
|
||||
|
||||
private:
|
||||
bool IsBlobFileOpen() const;
|
||||
@@ -69,9 +76,8 @@ class BlobFileBuilder {
|
||||
Status CloseBlobFileIfNeeded();
|
||||
|
||||
std::function<uint64_t()> file_number_generator_;
|
||||
Env* env_;
|
||||
FileSystem* fs_;
|
||||
const ImmutableCFOptions* immutable_cf_options_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
uint64_t min_blob_size_;
|
||||
uint64_t blob_file_size_;
|
||||
CompressionType blob_compression_type_;
|
||||
@@ -81,6 +87,8 @@ class BlobFileBuilder {
|
||||
std::string column_family_name_;
|
||||
Env::IOPriority io_priority_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
std::vector<std::string>* blob_file_paths_;
|
||||
std::vector<BlobFileAddition>* blob_file_additions_;
|
||||
std::unique_ptr<BlobLogWriter> writer_;
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_sequential_reader.h"
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/random_access_file_reader.h"
|
||||
@@ -40,7 +39,10 @@ class TestFileNumberGenerator {
|
||||
|
||||
class BlobFileBuilderTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileBuilderTest() : mock_env_(Env::Default()), fs_(&mock_env_) {}
|
||||
BlobFileBuilderTest() : mock_env_(Env::Default()) {
|
||||
fs_ = mock_env_.GetFileSystem().get();
|
||||
clock_ = mock_env_.GetSystemClock().get();
|
||||
}
|
||||
|
||||
void VerifyBlobFile(uint64_t blob_file_number,
|
||||
const std::string& blob_file_path,
|
||||
@@ -54,14 +56,13 @@ class BlobFileBuilderTest : public testing::Test {
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
ASSERT_OK(
|
||||
fs_.NewRandomAccessFile(blob_file_path, file_options_, &file, dbg));
|
||||
fs_->NewRandomAccessFile(blob_file_path, file_options_, &file, dbg));
|
||||
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(std::move(file), blob_file_path,
|
||||
&mock_env_));
|
||||
new RandomAccessFileReader(std::move(file), blob_file_path, clock_));
|
||||
|
||||
constexpr Statistics* statistics = nullptr;
|
||||
BlobLogSequentialReader blob_log_reader(std::move(file_reader), &mock_env_,
|
||||
BlobLogSequentialReader blob_log_reader(std::move(file_reader), clock_,
|
||||
statistics);
|
||||
|
||||
BlobLogHeader header;
|
||||
@@ -108,7 +109,8 @@ class BlobFileBuilderTest : public testing::Test {
|
||||
}
|
||||
|
||||
MockEnv mock_env_;
|
||||
LegacyFileSystemWrapper fs_;
|
||||
FileSystem* fs_;
|
||||
SystemClock* clock_;
|
||||
FileOptions file_options_;
|
||||
};
|
||||
|
||||
@@ -125,8 +127,9 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
"BlobFileBuilderTest_BuildAndCheckOneFile"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -138,11 +141,11 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckOneFile) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_,
|
||||
&immutable_cf_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint,
|
||||
&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*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -174,9 +177,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);
|
||||
|
||||
@@ -208,8 +211,9 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -221,11 +225,11 @@ TEST_F(BlobFileBuilderTest, BuildAndCheckMultipleFiles) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_,
|
||||
&immutable_cf_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint,
|
||||
&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*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> expected_key_value_pairs(
|
||||
number_of_blobs);
|
||||
@@ -258,7 +262,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];
|
||||
@@ -293,8 +297,9 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 1024;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -306,11 +311,11 @@ TEST_F(BlobFileBuilderTest, InlinedValues) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_,
|
||||
&immutable_cf_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint,
|
||||
&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*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
for (size_t i = 0; i < number_of_blobs; ++i) {
|
||||
const std::string key = std::to_string(i);
|
||||
@@ -345,8 +350,9 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderTest_Compression"), 0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -358,11 +364,11 @@ TEST_F(BlobFileBuilderTest, Compression) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_,
|
||||
&immutable_cf_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint,
|
||||
&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*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string uncompressed_value(value_size, 'x');
|
||||
@@ -381,9 +387,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);
|
||||
|
||||
@@ -427,8 +433,8 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_compression_type = kSnappyCompression;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
options.env = &mock_env_;
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -440,11 +446,11 @@ TEST_F(BlobFileBuilderTest, CompressionError) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_,
|
||||
&immutable_cf_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint,
|
||||
&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*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack("CompressData:TamperWithReturnValue",
|
||||
[](void* arg) {
|
||||
@@ -466,9 +472,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());
|
||||
}
|
||||
@@ -504,8 +510,9 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
options.enable_blob_files = true;
|
||||
options.file_checksum_gen_factory =
|
||||
std::make_shared<DummyFileChecksumGenFactory>();
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -517,11 +524,11 @@ TEST_F(BlobFileBuilderTest, Checksum) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &mock_env_, &fs_,
|
||||
&immutable_cf_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint,
|
||||
&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*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
const std::string key("1");
|
||||
const std::string value("deadbeef");
|
||||
@@ -540,9 +547,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);
|
||||
|
||||
@@ -570,13 +577,11 @@ class BlobFileBuilderIOErrorTest
|
||||
protected:
|
||||
BlobFileBuilderIOErrorTest()
|
||||
: mock_env_(Env::Default()),
|
||||
fault_injection_env_(&mock_env_),
|
||||
fs_(&fault_injection_env_),
|
||||
fs_(mock_env_.GetFileSystem().get()),
|
||||
sync_point_(GetParam()) {}
|
||||
|
||||
MockEnv mock_env_;
|
||||
FaultInjectionTestEnv fault_injection_env_;
|
||||
LegacyFileSystemWrapper fs_;
|
||||
FileSystem* fs_;
|
||||
FileOptions file_options_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
@@ -597,13 +602,13 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
|
||||
Options options;
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&fault_injection_env_,
|
||||
"BlobFileBuilderIOErrorTest_IOError"),
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileBuilderIOErrorTest_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
options.blob_file_size = value_size;
|
||||
options.env = &mock_env_;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
MutableCFOptions mutable_cf_options(options);
|
||||
|
||||
constexpr int job_id = 1;
|
||||
@@ -615,15 +620,17 @@ TEST_P(BlobFileBuilderIOErrorTest, IOError) {
|
||||
std::vector<std::string> blob_file_paths;
|
||||
std::vector<BlobFileAddition> blob_file_additions;
|
||||
|
||||
BlobFileBuilder builder(TestFileNumberGenerator(), &fault_injection_env_,
|
||||
&fs_, &immutable_cf_options, &mutable_cf_options,
|
||||
&file_options_, job_id, column_family_id,
|
||||
column_family_name, io_priority, write_hint,
|
||||
&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*/,
|
||||
&blob_file_paths, &blob_file_additions);
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_.SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* arg) {
|
||||
Status* const s = static_cast<Status*>(arg);
|
||||
assert(s);
|
||||
|
||||
(*s) = Status::IOError(sync_point_);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
@@ -644,7 +651,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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
#include "db/blob/blob_file_reader.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "trace_replay/io_tracer.h"
|
||||
#include "util/hash.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobFileCache::BlobFileCache(Cache* cache,
|
||||
const ImmutableOptions* immutable_options,
|
||||
const FileOptions* file_options,
|
||||
uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist,
|
||||
const std::shared_ptr<IOTracer>& io_tracer)
|
||||
: cache_(cache),
|
||||
mutex_(kNumberOfMutexStripes, kGetSliceNPHash64UnseededFnPtr),
|
||||
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_options_);
|
||||
assert(file_options_);
|
||||
}
|
||||
|
||||
Status BlobFileCache::GetBlobFileReader(
|
||||
uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader) {
|
||||
assert(blob_file_reader);
|
||||
assert(blob_file_reader->IsEmpty());
|
||||
|
||||
const Slice key = GetSlice(&blob_file_number);
|
||||
|
||||
assert(cache_);
|
||||
|
||||
Cache::Handle* handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("BlobFileCache::GetBlobFileReader:DoubleCheck");
|
||||
|
||||
// Check again while holding mutex
|
||||
MutexLock lock(mutex_.get(key));
|
||||
|
||||
handle = cache_->Lookup(key);
|
||||
if (handle) {
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
assert(immutable_options_);
|
||||
Statistics* const statistics = immutable_options_->stats;
|
||||
|
||||
RecordTick(statistics, NO_FILE_OPENS);
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
{
|
||||
assert(file_options_);
|
||||
const Status s = BlobFileReader::Create(
|
||||
*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);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
constexpr size_t charge = 1;
|
||||
|
||||
const Status s = cache_->Insert(key, reader.get(), charge,
|
||||
&DeleteCacheEntry<BlobFileReader>, &handle);
|
||||
if (!s.ok()) {
|
||||
RecordTick(statistics, NO_FILE_ERRORS);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
reader.release();
|
||||
|
||||
*blob_file_reader = CacheHandleGuard<BlobFileReader>(cache_, handle);
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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 <cinttypes>
|
||||
|
||||
#include "cache/cache_helpers.h"
|
||||
#include "rocksdb/rocksdb_namespace.h"
|
||||
#include "util/mutexlock.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Cache;
|
||||
struct ImmutableOptions;
|
||||
struct FileOptions;
|
||||
class HistogramImpl;
|
||||
class Status;
|
||||
class BlobFileReader;
|
||||
class Slice;
|
||||
class IOTracer;
|
||||
|
||||
class BlobFileCache {
|
||||
public:
|
||||
BlobFileCache(Cache* cache, const ImmutableOptions* immutable_options,
|
||||
const FileOptions* file_options, uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist,
|
||||
const std::shared_ptr<IOTracer>& io_tracer);
|
||||
|
||||
BlobFileCache(const BlobFileCache&) = delete;
|
||||
BlobFileCache& operator=(const BlobFileCache&) = delete;
|
||||
|
||||
Status GetBlobFileReader(uint64_t blob_file_number,
|
||||
CacheHandleGuard<BlobFileReader>* blob_file_reader);
|
||||
|
||||
private:
|
||||
Cache* cache_;
|
||||
// Note: mutex_ below is used to guard against multiple threads racing to open
|
||||
// the same file.
|
||||
Striped<port::Mutex, Slice> mutex_;
|
||||
const ImmutableOptions* immutable_options_;
|
||||
const FileOptions* file_options_;
|
||||
uint32_t column_family_id_;
|
||||
HistogramImpl* blob_file_read_hist_;
|
||||
std::shared_ptr<IOTracer> io_tracer_;
|
||||
|
||||
static constexpr size_t kNumberOfMutexStripes = 1 << 7;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
@@ -0,0 +1,267 @@
|
||||
// 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_file_cache.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "db/blob/blob_log_writer.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/read_write_util.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "options/cf_options.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
namespace {
|
||||
|
||||
// Creates a test blob file with a single blob in it.
|
||||
void WriteBlobFile(uint32_t column_family_id,
|
||||
const ImmutableOptions& immutable_options,
|
||||
uint64_t blob_file_number) {
|
||||
assert(!immutable_options.cf_paths.empty());
|
||||
|
||||
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_options.fs.get(), blob_file_path, &file,
|
||||
FileOptions()));
|
||||
|
||||
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_options.clock,
|
||||
statistics, blob_file_number, use_fsync,
|
||||
do_flush);
|
||||
|
||||
constexpr bool has_ttl = false;
|
||||
constexpr ExpirationRange expiration_range;
|
||||
|
||||
BlobLogHeader header(column_family_id, kNoCompression, has_ttl,
|
||||
expiration_range);
|
||||
|
||||
ASSERT_OK(blob_log_writer.WriteHeader(header));
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
|
||||
std::string compressed_blob;
|
||||
|
||||
uint64_t key_offset = 0;
|
||||
uint64_t blob_offset = 0;
|
||||
|
||||
ASSERT_OK(blob_log_writer.AddRecord(key, blob, &key_offset, &blob_offset));
|
||||
|
||||
BlobLogFooter footer;
|
||||
footer.blob_count = 1;
|
||||
footer.expiration_range = expiration_range;
|
||||
|
||||
std::string checksum_method;
|
||||
std::string checksum_value;
|
||||
|
||||
ASSERT_OK(
|
||||
blob_log_writer.AppendFooter(footer, &checksum_method, &checksum_value));
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
class BlobFileCacheTest : public testing::Test {
|
||||
protected:
|
||||
BlobFileCacheTest() : mock_env_(Env::Default()) {}
|
||||
|
||||
MockEnv mock_env_;
|
||||
};
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileCacheTest_GetBlobFileReader"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
// First try: reader should be opened and put in cache
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
|
||||
// Second try: reader should be served from cache
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
|
||||
ASSERT_EQ(first.GetValue(), second.GetValue());
|
||||
}
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_Race) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
"BlobFileCacheTest_GetBlobFileReader_Race"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
CacheHandleGuard<BlobFileReader> first;
|
||||
CacheHandleGuard<BlobFileReader> second;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileCache::GetBlobFileReader:DoubleCheck", [&](void* /* arg */) {
|
||||
// Disabling sync points to prevent infinite recursion
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &second));
|
||||
ASSERT_NE(second.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_OK(blob_file_cache.GetBlobFileReader(blob_file_number, &first));
|
||||
ASSERT_NE(first.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 0);
|
||||
|
||||
ASSERT_EQ(first.GetValue(), second.GetValue());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_IOError) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
"BlobFileCacheTest_GetBlobFileReader_IOError"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr size_t capacity = 10;
|
||||
std::shared_ptr<Cache> backing_cache = NewLRUCache(capacity);
|
||||
|
||||
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_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
// Note: there is no blob file with the below number
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(
|
||||
blob_file_cache.GetBlobFileReader(blob_file_number, &reader).IsIOError());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
}
|
||||
|
||||
TEST_F(BlobFileCacheTest, GetBlobFileReader_CacheFull) {
|
||||
Options options;
|
||||
options.env = &mock_env_;
|
||||
options.statistics = CreateDBStatistics();
|
||||
options.cf_paths.emplace_back(
|
||||
test::PerThreadDBPath(&mock_env_,
|
||||
"BlobFileCacheTest_GetBlobFileReader_CacheFull"),
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
ImmutableOptions immutable_options(options);
|
||||
constexpr uint64_t blob_file_number = 123;
|
||||
|
||||
WriteBlobFile(column_family_id, immutable_options, blob_file_number);
|
||||
|
||||
constexpr size_t capacity = 0;
|
||||
constexpr int num_shard_bits = -1; // determined automatically
|
||||
constexpr bool strict_capacity_limit = true;
|
||||
std::shared_ptr<Cache> backing_cache =
|
||||
NewLRUCache(capacity, num_shard_bits, strict_capacity_limit);
|
||||
|
||||
FileOptions file_options;
|
||||
constexpr HistogramImpl* blob_file_read_hist = nullptr;
|
||||
|
||||
BlobFileCache blob_file_cache(backing_cache.get(), &immutable_options,
|
||||
&file_options, column_family_id,
|
||||
blob_file_read_hist, nullptr /*IOTracer*/);
|
||||
|
||||
// Insert into cache should fail since it has zero capacity and
|
||||
// strict_capacity_limit is set
|
||||
CacheHandleGuard<BlobFileReader> reader;
|
||||
|
||||
ASSERT_TRUE(blob_file_cache.GetBlobFileReader(blob_file_number, &reader)
|
||||
.IsIncomplete());
|
||||
ASSERT_EQ(reader.GetValue(), nullptr);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_OPENS), 1);
|
||||
ASSERT_EQ(options.statistics->getTickerCount(NO_FILE_ERRORS), 1);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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 "db/error_handler.h"
|
||||
#include "file/sst_file_manager_impl.h"
|
||||
#include "rocksdb/status.h"
|
||||
|
||||
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();
|
||||
}
|
||||
#else
|
||||
BlobFileCompletionCallback(SstFileManager* sst_file_manager,
|
||||
InstrumentedMutex* mutex,
|
||||
ErrorHandler* error_handler)
|
||||
: sst_file_manager_(sst_file_manager),
|
||||
mutex_(mutex),
|
||||
error_handler_(error_handler) {}
|
||||
|
||||
Status OnBlobFileCompleted(const std::string& file_name) {
|
||||
Status s;
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(sst_file_manager_);
|
||||
if (sfm) {
|
||||
// Report new blob files to SstFileManagerImpl
|
||||
s = sfm->OnAddFile(file_name);
|
||||
if (sfm->IsMaxAllowedSpaceReached()) {
|
||||
s = Status::SpaceLimit("Max allowed space was reached");
|
||||
TEST_SYNC_POINT(
|
||||
"BlobFileCompletionCallback::CallBack::MaxAllowedSpaceReached");
|
||||
InstrumentedMutexLock l(mutex_);
|
||||
error_handler_->SetBGError(s, BackgroundErrorReason::kFlush);
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
SstFileManager* sst_file_manager_;
|
||||
InstrumentedMutex* mutex_;
|
||||
ErrorHandler* error_handler_;
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
+24
-19
@@ -21,9 +21,9 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
Status BlobFileReader::Create(
|
||||
const ImmutableCFOptions& immutable_cf_options,
|
||||
const FileOptions& file_options, uint32_t column_family_id,
|
||||
HistogramImpl* blob_file_read_hist, uint64_t blob_file_number,
|
||||
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);
|
||||
@@ -33,8 +33,8 @@ Status BlobFileReader::Create(
|
||||
|
||||
{
|
||||
const Status s =
|
||||
OpenFile(immutable_cf_options, file_options, blob_file_read_hist,
|
||||
blob_file_number, &file_size, &file_reader);
|
||||
OpenFile(immutable_options, file_options, blob_file_read_hist,
|
||||
blob_file_number, io_tracer, &file_size, &file_reader);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -66,20 +66,20 @@ Status BlobFileReader::Create(
|
||||
}
|
||||
|
||||
Status BlobFileReader::OpenFile(
|
||||
const ImmutableCFOptions& immutable_cf_options,
|
||||
const FileOptions& file_opts, HistogramImpl* blob_file_read_hist,
|
||||
uint64_t blob_file_number, uint64_t* file_size,
|
||||
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;
|
||||
FileSystem* const fs = immutable_options.fs.get();
|
||||
assert(fs);
|
||||
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
@@ -112,15 +112,15 @@ 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.env,
|
||||
std::shared_ptr<IOTracer>(), immutable_cf_options.statistics,
|
||||
BLOB_DB_BLOB_FILE_READ_MICROS, blob_file_read_hist,
|
||||
immutable_cf_options.rate_limiter, 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();
|
||||
}
|
||||
@@ -269,7 +269,8 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
const Slice& user_key, uint64_t offset,
|
||||
uint64_t value_size,
|
||||
CompressionType compression_type,
|
||||
PinnableSlice* value) const {
|
||||
PinnableSlice* value,
|
||||
uint64_t* bytes_read) const {
|
||||
assert(value);
|
||||
|
||||
const uint64_t key_size = user_key.size();
|
||||
@@ -292,6 +293,9 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
: 0;
|
||||
assert(offset >= adjustment);
|
||||
|
||||
const uint64_t record_offset = offset - adjustment;
|
||||
const uint64_t record_size = value_size + adjustment;
|
||||
|
||||
Slice record_slice;
|
||||
Buffer buf;
|
||||
AlignedBuf aligned_buf;
|
||||
@@ -299,9 +303,6 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
{
|
||||
TEST_SYNC_POINT("BlobFileReader::GetBlob:ReadFromFile");
|
||||
|
||||
const uint64_t record_offset = offset - adjustment;
|
||||
const uint64_t record_size = value_size + adjustment;
|
||||
|
||||
const Status s = ReadFromFile(file_reader_.get(), record_offset,
|
||||
static_cast<size_t>(record_size),
|
||||
&record_slice, &buf, &aligned_buf);
|
||||
@@ -330,6 +331,10 @@ Status BlobFileReader::GetBlob(const ReadOptions& read_options,
|
||||
}
|
||||
}
|
||||
|
||||
if (bytes_read) {
|
||||
*bytes_read = record_size;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class Status;
|
||||
struct ImmutableCFOptions;
|
||||
struct ImmutableOptions;
|
||||
struct FileOptions;
|
||||
class HistogramImpl;
|
||||
struct ReadOptions;
|
||||
@@ -24,11 +24,12 @@ class PinnableSlice;
|
||||
|
||||
class BlobFileReader {
|
||||
public:
|
||||
static Status Create(const ImmutableCFOptions& immutable_cf_options,
|
||||
static Status Create(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>* reader);
|
||||
|
||||
BlobFileReader(const BlobFileReader&) = delete;
|
||||
@@ -38,16 +39,19 @@ class BlobFileReader {
|
||||
|
||||
Status GetBlob(const ReadOptions& read_options, const Slice& user_key,
|
||||
uint64_t offset, uint64_t value_size,
|
||||
CompressionType compression_type, PinnableSlice* value) const;
|
||||
CompressionType compression_type, PinnableSlice* value,
|
||||
uint64_t* bytes_read) const;
|
||||
|
||||
private:
|
||||
BlobFileReader(std::unique_ptr<RandomAccessFileReader>&& file_reader,
|
||||
uint64_t file_size, CompressionType compression_type);
|
||||
|
||||
static Status OpenFile(const ImmutableCFOptions& immutable_cf_options,
|
||||
static Status OpenFile(const ImmutableOptions& immutable_options,
|
||||
const FileOptions& file_opts,
|
||||
HistogramImpl* blob_file_read_hist,
|
||||
uint64_t blob_file_number, uint64_t* file_size,
|
||||
uint64_t blob_file_number,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
uint64_t* file_size,
|
||||
std::unique_ptr<RandomAccessFileReader>* file_reader);
|
||||
|
||||
static Status ReadHeader(const RandomAccessFileReader* file_reader,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "util/compression.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
@@ -29,34 +30,34 @@ 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 ImmutableCFOptions& immutable_cf_options,
|
||||
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(!immutable_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);
|
||||
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, blob_file_path, &file,
|
||||
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.env));
|
||||
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.env, statistics,
|
||||
blob_file_number, use_fsync);
|
||||
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,
|
||||
expiration_range_header);
|
||||
@@ -120,7 +121,7 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -132,17 +133,17 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
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, &reader));
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
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;
|
||||
@@ -150,83 +151,103 @@ TEST_F(BlobFileReaderTest, CreateReaderAndGetBlob) {
|
||||
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kNoCompression, &value));
|
||||
kNoCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_EQ(bytes_read, blob_size);
|
||||
}
|
||||
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kNoCompression, &value));
|
||||
kNoCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
|
||||
constexpr uint64_t key_size = sizeof(key) - 1;
|
||||
ASSERT_EQ(bytes_read,
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size);
|
||||
}
|
||||
|
||||
// Invalid offset (too close to start of file)
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset - 1, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Invalid offset (too close to end of file)
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset + 1, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect compression type
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(
|
||||
reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size, kZSTD, &value)
|
||||
.IsCorruption());
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size, kZSTD,
|
||||
&value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect key size
|
||||
{
|
||||
constexpr char shorter_key[] = "k";
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, shorter_key,
|
||||
blob_offset - (sizeof(key) - sizeof(shorter_key)),
|
||||
blob_size, kNoCompression, &value)
|
||||
blob_size, kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect key
|
||||
{
|
||||
constexpr char incorrect_key[] = "foo";
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, incorrect_key, blob_offset,
|
||||
blob_size, kNoCompression, &value)
|
||||
blob_size, kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
// Incorrect value size
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(read_options, key, blob_offset, blob_size + 1,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +261,7 @@ TEST_F(BlobFileReaderTest, Malformed) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_Malformed"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr uint64_t blob_file_number = 1;
|
||||
@@ -249,23 +270,24 @@ 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, blob_file_path, &file,
|
||||
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.env));
|
||||
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.env, statistics,
|
||||
blob_file_number, use_fsync);
|
||||
immutable_options.clock, statistics,
|
||||
blob_file_number, use_fsync, do_flush);
|
||||
|
||||
BlobLogHeader header(column_family_id, kNoCompression, has_ttl,
|
||||
expiration_range);
|
||||
@@ -277,9 +299,10 @@ 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, &reader)
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -290,7 +313,7 @@ TEST_F(BlobFileReaderTest, TTL) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_TTL"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = true;
|
||||
@@ -302,17 +325,18 @@ 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, &reader)
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -325,7 +349,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInHeader) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -339,7 +363,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);
|
||||
@@ -348,9 +372,10 @@ 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, &reader)
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -363,7 +388,7 @@ TEST_F(BlobFileReaderTest, ExpirationRangeInFooter) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -377,7 +402,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);
|
||||
@@ -386,9 +411,10 @@ 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, &reader)
|
||||
blob_file_number, nullptr /*IOTracer*/,
|
||||
&reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -401,7 +427,7 @@ TEST_F(BlobFileReaderTest, IncorrectColumnFamily) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -413,9 +439,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;
|
||||
|
||||
@@ -423,10 +449,10 @@ 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,
|
||||
&reader)
|
||||
nullptr /*IOTracer*/, &reader)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
@@ -437,7 +463,7 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_BlobCRCError"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -449,17 +475,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, &reader));
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::VerifyBlob:CheckBlobCRC", [](void* arg) {
|
||||
@@ -472,11 +498,13 @@ TEST_F(BlobFileReaderTest, BlobCRCError) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
@@ -493,7 +521,7 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
test::PerThreadDBPath(&mock_env_, "BlobFileReaderTest_Compression"), 0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -505,17 +533,17 @@ 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;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, &reader));
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
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;
|
||||
@@ -523,20 +551,28 @@ TEST_F(BlobFileReaderTest, Compression) {
|
||||
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kSnappyCompression, &value));
|
||||
kSnappyCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
ASSERT_EQ(bytes_read, blob_size);
|
||||
}
|
||||
|
||||
read_options.verify_checksums = true;
|
||||
|
||||
{
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_OK(reader->GetBlob(read_options, key, blob_offset, blob_size,
|
||||
kSnappyCompression, &value));
|
||||
kSnappyCompression, &value, &bytes_read));
|
||||
ASSERT_EQ(value, blob);
|
||||
|
||||
constexpr uint64_t key_size = sizeof(key) - 1;
|
||||
ASSERT_EQ(bytes_read,
|
||||
BlobLogRecord::CalculateAdjustmentForRecordHeader(key_size) +
|
||||
blob_size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,7 +589,7 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -565,17 +601,17 @@ 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;
|
||||
|
||||
std::unique_ptr<BlobFileReader> reader;
|
||||
|
||||
ASSERT_OK(BlobFileReader::Create(immutable_cf_options, FileOptions(),
|
||||
column_family_id, blob_file_read_hist,
|
||||
blob_file_number, &reader));
|
||||
ASSERT_OK(BlobFileReader::Create(
|
||||
immutable_options, FileOptions(), column_family_id, blob_file_read_hist,
|
||||
blob_file_number, nullptr /*IOTracer*/, &reader));
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"BlobFileReader::UncompressBlobIfNeeded:TamperWithResult", [](void* arg) {
|
||||
@@ -589,11 +625,13 @@ TEST_F(BlobFileReaderTest, UncompressionError) {
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kSnappyCompression, &value)
|
||||
kSnappyCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
@@ -632,7 +670,7 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -644,9 +682,9 @@ 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,
|
||||
@@ -658,9 +696,9 @@ 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, &reader);
|
||||
const Status s = BlobFileReader::Create(
|
||||
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");
|
||||
@@ -671,11 +709,13 @@ TEST_P(BlobFileReaderIOErrorTest, IOError) {
|
||||
ASSERT_OK(s);
|
||||
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsIOError());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -708,7 +748,7 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
0);
|
||||
options.enable_blob_files = true;
|
||||
|
||||
ImmutableCFOptions immutable_cf_options(options);
|
||||
ImmutableOptions immutable_options(options);
|
||||
|
||||
constexpr uint32_t column_family_id = 1;
|
||||
constexpr bool has_ttl = false;
|
||||
@@ -720,9 +760,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);
|
||||
@@ -738,9 +778,9 @@ 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, &reader);
|
||||
const Status s = BlobFileReader::Create(
|
||||
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";
|
||||
@@ -751,11 +791,13 @@ TEST_P(BlobFileReaderDecodingErrorTest, DecodingError) {
|
||||
ASSERT_OK(s);
|
||||
|
||||
PinnableSlice value;
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
ASSERT_TRUE(reader
|
||||
->GetBlob(ReadOptions(), key, blob_offset, blob_size,
|
||||
kNoCompression, &value)
|
||||
kNoCompression, &value, &bytes_read)
|
||||
.IsCorruption());
|
||||
ASSERT_EQ(bytes_read, 0);
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
@@ -95,10 +95,6 @@ Status BlobLogFooter::DecodeFrom(Slice src) {
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
uint64_t BlobLogRecord::CalculateAdjustmentForRecordHeader(uint64_t key_size) {
|
||||
return key_size + kHeaderSize;
|
||||
}
|
||||
|
||||
void BlobLogRecord::EncodeHeaderTo(std::string* dst) {
|
||||
assert(dst != nullptr);
|
||||
dst->clear();
|
||||
|
||||
@@ -107,7 +107,9 @@ 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 uint64_t CalculateAdjustmentForRecordHeader(uint64_t key_size) {
|
||||
return key_size + kHeaderSize;
|
||||
}
|
||||
|
||||
uint64_t key_size = 0;
|
||||
uint64_t value_size = 0;
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#include "db/blob/blob_log_sequential_reader.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "file/random_access_file_reader.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "util/stop_watch.h"
|
||||
@@ -15,10 +13,10 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobLogSequentialReader::BlobLogSequentialReader(
|
||||
std::unique_ptr<RandomAccessFileReader>&& file_reader, Env* env,
|
||||
std::unique_ptr<RandomAccessFileReader>&& file_reader, SystemClock* clock,
|
||||
Statistics* statistics)
|
||||
: file_(std::move(file_reader)),
|
||||
env_(env),
|
||||
clock_(clock),
|
||||
statistics_(statistics),
|
||||
next_byte_(0) {}
|
||||
|
||||
@@ -29,7 +27,7 @@ Status BlobLogSequentialReader::ReadSlice(uint64_t size, Slice* slice,
|
||||
assert(slice);
|
||||
assert(file_);
|
||||
|
||||
StopWatch read_sw(env_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
|
||||
StopWatch read_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_READ_MICROS);
|
||||
Status s = file_->Read(IOOptions(), next_byte_, static_cast<size_t>(size),
|
||||
slice, buf, nullptr);
|
||||
next_byte_ += size;
|
||||
|
||||
@@ -16,6 +16,7 @@ class RandomAccessFileReader;
|
||||
class Env;
|
||||
class Statistics;
|
||||
class Status;
|
||||
class SystemClock;
|
||||
|
||||
/**
|
||||
* BlobLogSequentialReader is a general purpose log stream reader
|
||||
@@ -35,7 +36,7 @@ class BlobLogSequentialReader {
|
||||
|
||||
// Create a reader that will return log records from "*file_reader".
|
||||
BlobLogSequentialReader(std::unique_ptr<RandomAccessFileReader>&& file_reader,
|
||||
Env* env, Statistics* statistics);
|
||||
SystemClock* clock, Statistics* statistics);
|
||||
|
||||
// No copying allowed
|
||||
BlobLogSequentialReader(const BlobLogSequentialReader&) = delete;
|
||||
@@ -63,7 +64,8 @@ class BlobLogSequentialReader {
|
||||
Status ReadSlice(uint64_t size, Slice* slice, char* buf);
|
||||
|
||||
const std::unique_ptr<RandomAccessFileReader> file_;
|
||||
Env* env_;
|
||||
SystemClock* clock_;
|
||||
|
||||
Statistics* statistics_;
|
||||
|
||||
Slice buffer_;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/system_clock.h"
|
||||
#include "test_util/sync_point.h"
|
||||
#include "util/coding.h"
|
||||
#include "util/stop_watch.h"
|
||||
@@ -19,14 +19,16 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
BlobLogWriter::BlobLogWriter(std::unique_ptr<WritableFileWriter>&& dest,
|
||||
Env* env, Statistics* statistics,
|
||||
uint64_t log_number, bool use_fs, uint64_t boffset)
|
||||
SystemClock* clock, Statistics* statistics,
|
||||
uint64_t log_number, bool use_fs, bool do_flush,
|
||||
uint64_t boffset)
|
||||
: dest_(std::move(dest)),
|
||||
env_(env),
|
||||
clock_(clock),
|
||||
statistics_(statistics),
|
||||
log_number_(log_number),
|
||||
block_offset_(boffset),
|
||||
use_fsync_(use_fs),
|
||||
do_flush_(do_flush),
|
||||
last_elem_type_(kEtNone) {}
|
||||
|
||||
BlobLogWriter::~BlobLogWriter() = default;
|
||||
@@ -34,7 +36,7 @@ BlobLogWriter::~BlobLogWriter() = default;
|
||||
Status BlobLogWriter::Sync() {
|
||||
TEST_SYNC_POINT("BlobLogWriter::Sync");
|
||||
|
||||
StopWatch sync_sw(env_, statistics_, BLOB_DB_BLOB_FILE_SYNC_MICROS);
|
||||
StopWatch sync_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_SYNC_MICROS);
|
||||
Status s = dest_->Sync(use_fsync_);
|
||||
RecordTick(statistics_, BLOB_DB_BLOB_FILE_SYNCED);
|
||||
return s;
|
||||
@@ -49,7 +51,9 @@ Status BlobLogWriter::WriteHeader(BlobLogHeader& header) {
|
||||
Status s = dest_->Append(Slice(str));
|
||||
if (s.ok()) {
|
||||
block_offset_ += str.size();
|
||||
s = dest_->Flush();
|
||||
if (do_flush_) {
|
||||
s = dest_->Flush();
|
||||
}
|
||||
}
|
||||
last_elem_type_ = kEtFileHdr;
|
||||
RecordTick(statistics_, BLOB_DB_BLOB_FILE_BYTES_WRITTEN,
|
||||
@@ -144,7 +148,7 @@ Status BlobLogWriter::EmitPhysicalRecord(const std::string& headerbuf,
|
||||
const Slice& key, const Slice& val,
|
||||
uint64_t* key_offset,
|
||||
uint64_t* blob_offset) {
|
||||
StopWatch write_sw(env_, statistics_, BLOB_DB_BLOB_FILE_WRITE_MICROS);
|
||||
StopWatch write_sw(clock_, statistics_, BLOB_DB_BLOB_FILE_WRITE_MICROS);
|
||||
Status s = dest_->Append(Slice(headerbuf));
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(key);
|
||||
@@ -152,7 +156,7 @@ Status BlobLogWriter::EmitPhysicalRecord(const std::string& headerbuf,
|
||||
if (s.ok()) {
|
||||
s = dest_->Append(val);
|
||||
}
|
||||
if (s.ok()) {
|
||||
if (do_flush_ && s.ok()) {
|
||||
s = dest_->Flush();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "db/blob/blob_log_format.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/slice.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
#include "rocksdb/status.h"
|
||||
@@ -18,7 +17,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class WritableFileWriter;
|
||||
|
||||
class SystemClock;
|
||||
/**
|
||||
* BlobLogWriter is the blob log stream writer. It provides an append-only
|
||||
* abstraction for writing blob data.
|
||||
@@ -32,9 +31,9 @@ class BlobLogWriter {
|
||||
// Create a writer that will append data to "*dest".
|
||||
// "*dest" must be initially empty.
|
||||
// "*dest" must remain live while this BlobLogWriter is in use.
|
||||
BlobLogWriter(std::unique_ptr<WritableFileWriter>&& dest, Env* env,
|
||||
BlobLogWriter(std::unique_ptr<WritableFileWriter>&& dest, SystemClock* clock,
|
||||
Statistics* statistics, uint64_t log_number, bool use_fsync,
|
||||
uint64_t boffset = 0);
|
||||
bool do_flush, uint64_t boffset = 0);
|
||||
// No copying allowed
|
||||
BlobLogWriter(const BlobLogWriter&) = delete;
|
||||
BlobLogWriter& operator=(const BlobLogWriter&) = delete;
|
||||
@@ -69,11 +68,12 @@ class BlobLogWriter {
|
||||
|
||||
private:
|
||||
std::unique_ptr<WritableFileWriter> dest_;
|
||||
Env* env_;
|
||||
SystemClock* clock_;
|
||||
Statistics* statistics_;
|
||||
uint64_t log_number_;
|
||||
uint64_t block_offset_; // Current offset in block
|
||||
bool use_fsync_;
|
||||
bool do_flush_;
|
||||
|
||||
public:
|
||||
enum ElemType { kEtNone, kEtFileHdr, kEtRecord, kEtFileFooter };
|
||||
|
||||
@@ -0,0 +1,517 @@
|
||||
// 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 <array>
|
||||
|
||||
#include "db/blob/blob_index.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 DBBlobBasicTest : public DBTestBase {
|
||||
protected:
|
||||
DBBlobBasicTest()
|
||||
: DBTestBase("/db_blob_basic_test", /* env_do_fsync */ false) {}
|
||||
};
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(Get(key), blob_value);
|
||||
|
||||
// Try again with no I/O allowed. The table and the necessary blocks should
|
||||
// already be in their respective caches; however, the blob itself can only be
|
||||
// read from the blob file, so the read should return Incomplete.
|
||||
ReadOptions read_options;
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(read_options, db_->DefaultColumnFamily(), key, &result)
|
||||
.IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, MultiGetBlobs) {
|
||||
constexpr size_t min_blob_size = 6;
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = min_blob_size;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
// Put then retrieve three key-values. The first value is below the size limit
|
||||
// and is thus stored inline; the other two are stored separately as blobs.
|
||||
constexpr size_t num_keys = 3;
|
||||
|
||||
constexpr char first_key[] = "first_key";
|
||||
constexpr char first_value[] = "short";
|
||||
static_assert(sizeof(first_value) - 1 < min_blob_size,
|
||||
"first_value too long to be inlined");
|
||||
|
||||
ASSERT_OK(Put(first_key, first_value));
|
||||
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char second_value[] = "long_value";
|
||||
static_assert(sizeof(second_value) - 1 >= min_blob_size,
|
||||
"second_value too short to be stored as blob");
|
||||
|
||||
ASSERT_OK(Put(second_key, second_value));
|
||||
|
||||
constexpr char third_key[] = "third_key";
|
||||
constexpr char third_value[] = "other_long_value";
|
||||
static_assert(sizeof(third_value) - 1 >= min_blob_size,
|
||||
"third_value too short to be stored as blob");
|
||||
|
||||
ASSERT_OK(Put(third_key, third_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_options;
|
||||
|
||||
std::array<Slice, num_keys> keys{{first_key, second_key, third_key}};
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(read_options, db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], first_value);
|
||||
|
||||
ASSERT_OK(statuses[1]);
|
||||
ASSERT_EQ(values[1], second_value);
|
||||
|
||||
ASSERT_OK(statuses[2]);
|
||||
ASSERT_EQ(values[2], third_value);
|
||||
}
|
||||
|
||||
// Try again with no I/O allowed. The table and the necessary blocks should
|
||||
// already be in their respective caches. The first (inlined) value should be
|
||||
// successfully read; however, the two blob values could only be read from the
|
||||
// blob file, so for those the read should return Incomplete.
|
||||
read_options.read_tier = kBlockCacheTier;
|
||||
|
||||
{
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
db_->MultiGet(read_options, db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
ASSERT_OK(statuses[0]);
|
||||
ASSERT_EQ(values[0], first_value);
|
||||
|
||||
ASSERT_TRUE(statuses[1].IsIncomplete());
|
||||
|
||||
ASSERT_TRUE(statuses[2].IsIncomplete());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_CorruptIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
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));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_InlinedTTLIndex) {
|
||||
constexpr uint64_t min_blob_size = 10;
|
||||
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = min_blob_size;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "short";
|
||||
static_assert(sizeof(short) - 1 < min_blob_size,
|
||||
"Blob too long to be inlined");
|
||||
|
||||
// Fake an inlined TTL blob index.
|
||||
std::string blob_index;
|
||||
|
||||
constexpr uint64_t expiration = 1234567890;
|
||||
|
||||
BlobIndex::EncodeInlinedTTL(&blob_index, expiration, blob);
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(DBBlobBasicTest, GetBlob_IndexWithInvalidFileNumber) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
|
||||
// Fake a blob index referencing a non-existent blob file.
|
||||
std::string blob_index;
|
||||
|
||||
constexpr uint64_t blob_file_number = 1000;
|
||||
constexpr uint64_t offset = 1234;
|
||||
constexpr uint64_t size = 5678;
|
||||
|
||||
BlobIndex::EncodeBlob(&blob_index, blob_file_number, offset, size,
|
||||
kNoCompression);
|
||||
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsCorruption());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobBasicTest, GenerateIOTracing) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
std::string trace_file = dbname_ + "/io_trace_file";
|
||||
|
||||
Reopen(options);
|
||||
{
|
||||
// Create IO trace file
|
||||
std::unique_ptr<TraceWriter> trace_writer;
|
||||
ASSERT_OK(
|
||||
NewFileTraceWriter(env_, EnvOptions(), trace_file, &trace_writer));
|
||||
ASSERT_OK(db_->StartIOTrace(TraceOptions(), std::move(trace_writer)));
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ(Get(key), blob_value);
|
||||
|
||||
ASSERT_OK(db_->EndIOTrace());
|
||||
ASSERT_OK(env_->FileExists(trace_file));
|
||||
}
|
||||
{
|
||||
// Parse trace file to check file operations related to blob files are
|
||||
// recorded.
|
||||
std::unique_ptr<TraceReader> trace_reader;
|
||||
ASSERT_OK(
|
||||
NewFileTraceReader(env_, EnvOptions(), trace_file, &trace_reader));
|
||||
IOTraceReader reader(std::move(trace_reader));
|
||||
|
||||
IOTraceHeader header;
|
||||
ASSERT_OK(reader.ReadHeader(&header));
|
||||
ASSERT_EQ(kMajorVersion, static_cast<int>(header.rocksdb_major_version));
|
||||
ASSERT_EQ(kMinorVersion, static_cast<int>(header.rocksdb_minor_version));
|
||||
|
||||
// Read records.
|
||||
int blob_files_op_count = 0;
|
||||
Status status;
|
||||
while (true) {
|
||||
IOTraceRecord record;
|
||||
status = reader.ReadIOOp(&record);
|
||||
if (!status.ok()) {
|
||||
break;
|
||||
}
|
||||
if (record.file_name.find("blob") != std::string::npos) {
|
||||
blob_files_op_count++;
|
||||
}
|
||||
}
|
||||
// Assuming blob files will have Append, Close and then Read operations.
|
||||
ASSERT_GT(blob_files_op_count, 2);
|
||||
}
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBlobBasicTest, BestEffortsRecovery_MissingNewestBlobFile) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(dbfull()->DisableFileDeletions());
|
||||
constexpr int kNumTableFiles = 2;
|
||||
for (int i = 0; i < kNumTableFiles; ++i) {
|
||||
for (char ch = 'a'; ch != 'c'; ++ch) {
|
||||
std::string key(1, ch);
|
||||
ASSERT_OK(Put(key, "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
Close();
|
||||
|
||||
std::vector<std::string> files;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &files));
|
||||
std::string blob_file_path;
|
||||
uint64_t max_blob_file_num = kInvalidBlobFileNumber;
|
||||
for (const auto& fname : files) {
|
||||
uint64_t file_num = 0;
|
||||
FileType type;
|
||||
if (ParseFileName(fname, &file_num, /*info_log_name_prefix=*/"", &type) &&
|
||||
type == kBlobFile) {
|
||||
if (file_num > max_blob_file_num) {
|
||||
max_blob_file_num = file_num;
|
||||
blob_file_path = dbname_ + "/" + fname;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_OK(env_->DeleteFile(blob_file_path));
|
||||
|
||||
options.best_efforts_recovery = true;
|
||||
Reopen(options);
|
||||
std::string value;
|
||||
ASSERT_OK(db_->Get(ReadOptions(), "a", &value));
|
||||
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");
|
||||
}
|
||||
|
||||
class DBBlobBasicIOErrorTest : public DBBlobBasicTest,
|
||||
public testing::WithParamInterface<std::string> {
|
||||
protected:
|
||||
DBBlobBasicIOErrorTest() : sync_point_(GetParam()) {
|
||||
fault_injection_env_.reset(new FaultInjectionTestEnv(env_));
|
||||
}
|
||||
~DBBlobBasicIOErrorTest() { Close(); }
|
||||
|
||||
std::unique_ptr<FaultInjectionTestEnv> fault_injection_env_;
|
||||
std::string sync_point_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBlobBasicTest, DBBlobBasicIOErrorTest,
|
||||
::testing::ValuesIn(std::vector<std::string>{
|
||||
"BlobFileReader::OpenFile:NewRandomAccessFile",
|
||||
"BlobFileReader::GetBlob:ReadFromFile"}));
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, GetBlob_IOError) {
|
||||
Options options;
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
|
||||
Reopen(options);
|
||||
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob_value[] = "blob_value";
|
||||
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
PinnableSlice result;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), db_->DefaultColumnFamily(), key, &result)
|
||||
.IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, MultiGetBlobs_IOError) {
|
||||
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 first_key[] = "first_key";
|
||||
constexpr char first_value[] = "first_value";
|
||||
|
||||
ASSERT_OK(Put(first_key, first_value));
|
||||
|
||||
constexpr char second_key[] = "second_key";
|
||||
constexpr char second_value[] = "second_value";
|
||||
|
||||
ASSERT_OK(Put(second_key, second_value));
|
||||
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::array<Slice, num_keys> keys{{first_key, second_key}};
|
||||
std::array<PinnableSlice, num_keys> values;
|
||||
std::array<Status, num_keys> statuses;
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
db_->MultiGet(ReadOptions(), db_->DefaultColumnFamily(), num_keys, &keys[0],
|
||||
&values[0], &statuses[0]);
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
|
||||
ASSERT_TRUE(statuses[0].IsIOError());
|
||||
ASSERT_TRUE(statuses[1].IsIOError());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class ReadBlobCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
ReadBlobCompactionFilter() = default;
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.read.blob";
|
||||
}
|
||||
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 {
|
||||
if (value_type != CompactionFilter::ValueType::kValue) {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
assert(new_value);
|
||||
new_value->assign(existing_value.data(), existing_value.size());
|
||||
return CompactionFilter::Decision::kChangeValue;
|
||||
}
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
TEST_P(DBBlobBasicIOErrorTest, CompactionFilterReadBlob_IOError) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.env = fault_injection_env_.get();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ReadBlobCompactionFilter);
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
constexpr char key[] = "foo";
|
||||
constexpr char blob_value[] = "foo_blob_value";
|
||||
ASSERT_OK(Put(key, blob_value));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(sync_point_, [this](void* /* arg */) {
|
||||
fault_injection_env_->SetFilesystemActive(false,
|
||||
Status::IOError(sync_point_));
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsIOError());
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
// 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_index.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) {}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const std::vector<InternalStats::CompactionStats>& GetCompactionStats() {
|
||||
VersionSet* const versions = dbfull()->TEST_GetVersionSet();
|
||||
assert(versions);
|
||||
assert(versions->GetColumnFamilySet());
|
||||
|
||||
ColumnFamilyData* const cfd = versions->GetColumnFamilySet()->GetDefault();
|
||||
assert(cfd);
|
||||
|
||||
const InternalStats* const internal_stats = cfd->internal_stats();
|
||||
assert(internal_stats);
|
||||
|
||||
return internal_stats->TEST_GetCompactionStats();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
class FilterByKeyLength : public CompactionFilter {
|
||||
public:
|
||||
explicit FilterByKeyLength(size_t len) : length_threshold_(len) {}
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.by.key.length";
|
||||
}
|
||||
CompactionFilter::Decision FilterBlobByKey(
|
||||
int /*level*/, const Slice& key, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
if (key.size() < length_threshold_) {
|
||||
return CompactionFilter::Decision::kRemove;
|
||||
}
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t length_threshold_;
|
||||
};
|
||||
|
||||
class BadBlobCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit BadBlobCompactionFilter(std::string prefix,
|
||||
CompactionFilter::Decision filter_by_key,
|
||||
CompactionFilter::Decision filter_v2)
|
||||
: prefix_(std::move(prefix)),
|
||||
filter_blob_by_key_(filter_by_key),
|
||||
filter_v2_(filter_v2) {}
|
||||
const char* Name() const override { return "rocksdb.compaction.filter.bad"; }
|
||||
CompactionFilter::Decision FilterBlobByKey(
|
||||
int /*level*/, const Slice& key, std::string* /*new_value*/,
|
||||
std::string* /*skip_until*/) const override {
|
||||
if (key.size() >= prefix_.size() &&
|
||||
0 == strncmp(prefix_.data(), key.data(), prefix_.size())) {
|
||||
return CompactionFilter::Decision::kUndetermined;
|
||||
}
|
||||
return filter_blob_by_key_;
|
||||
}
|
||||
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 {
|
||||
return filter_v2_;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string prefix_;
|
||||
const CompactionFilter::Decision filter_blob_by_key_;
|
||||
const CompactionFilter::Decision filter_v2_;
|
||||
};
|
||||
|
||||
class ValueBlindWriteFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ValueBlindWriteFilter(std::string new_val)
|
||||
: new_value_(std::move(new_val)) {}
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.blind.write";
|
||||
}
|
||||
CompactionFilter::Decision FilterBlobByKey(
|
||||
int level, const Slice& key, std::string* new_value,
|
||||
std::string* skip_until) const override;
|
||||
|
||||
private:
|
||||
const std::string new_value_;
|
||||
};
|
||||
|
||||
CompactionFilter::Decision ValueBlindWriteFilter::FilterBlobByKey(
|
||||
int /*level*/, const Slice& /*key*/, std::string* new_value,
|
||||
std::string* /*skip_until*/) const {
|
||||
assert(new_value);
|
||||
new_value->assign(new_value_);
|
||||
return CompactionFilter::Decision::kChangeValue;
|
||||
}
|
||||
|
||||
class ValueMutationFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit ValueMutationFilter(std::string padding)
|
||||
: padding_(std::move(padding)) {}
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.value.mutation";
|
||||
}
|
||||
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;
|
||||
|
||||
private:
|
||||
const std::string padding_;
|
||||
};
|
||||
|
||||
CompactionFilter::Decision ValueMutationFilter::FilterV2(
|
||||
int /*level*/, const Slice& /*key*/, ValueType value_type,
|
||||
const Slice& existing_value, std::string* new_value,
|
||||
std::string* /*skip_until*/) const {
|
||||
assert(CompactionFilter::ValueType::kBlobIndex != value_type);
|
||||
if (CompactionFilter::ValueType::kValue != value_type) {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
assert(new_value);
|
||||
new_value->assign(existing_value.data(), existing_value.size());
|
||||
new_value->append(padding_);
|
||||
return CompactionFilter::Decision::kChangeValue;
|
||||
}
|
||||
|
||||
class AlwaysKeepFilter : public CompactionFilter {
|
||||
public:
|
||||
explicit AlwaysKeepFilter() = default;
|
||||
const char* Name() const override {
|
||||
return "rocksdb.compaction.filter.always.keep";
|
||||
}
|
||||
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 {
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
};
|
||||
} // anonymous namespace
|
||||
|
||||
class DBBlobBadCompactionFilterTest
|
||||
: public DBBlobCompactionTest,
|
||||
public testing::WithParamInterface<
|
||||
std::tuple<std::string, CompactionFilter::Decision,
|
||||
CompactionFilter::Decision>> {
|
||||
public:
|
||||
explicit DBBlobBadCompactionFilterTest()
|
||||
: compaction_filter_guard_(new BadBlobCompactionFilter(
|
||||
std::get<0>(GetParam()), std::get<1>(GetParam()),
|
||||
std::get<2>(GetParam()))) {}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard_;
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
BadCompactionFilter, DBBlobBadCompactionFilterTest,
|
||||
testing::Combine(
|
||||
testing::Values("a"),
|
||||
testing::Values(CompactionFilter::Decision::kChangeBlobIndex,
|
||||
CompactionFilter::Decision::kIOError),
|
||||
testing::Values(CompactionFilter::Decision::kUndetermined,
|
||||
CompactionFilter::Decision::kChangeBlobIndex,
|
||||
CompactionFilter::Decision::kIOError)));
|
||||
|
||||
TEST_F(DBBlobCompactionTest, FilterByKeyLength) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
constexpr size_t kKeyLength = 2;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new FilterByKeyLength(kKeyLength));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
|
||||
constexpr char short_key[] = "a";
|
||||
constexpr char long_key[] = "abc";
|
||||
constexpr char blob_value[] = "value";
|
||||
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put(short_key, blob_value));
|
||||
ASSERT_OK(Put(long_key, blob_value));
|
||||
ASSERT_OK(Flush());
|
||||
CompactRangeOptions cro;
|
||||
ASSERT_OK(db_->CompactRange(cro, /*begin=*/nullptr, /*end=*/nullptr));
|
||||
std::string value;
|
||||
ASSERT_TRUE(db_->Get(ReadOptions(), short_key, &value).IsNotFound());
|
||||
value.clear();
|
||||
ASSERT_OK(db_->Get(ReadOptions(), long_key, &value));
|
||||
ASSERT_EQ("value", value);
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter decides between kKeep and kRemove solely based on key;
|
||||
// this involves neither reading nor writing blobs
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, BlindWriteFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
constexpr char new_blob_value[] = "new_blob_value";
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueBlindWriteFilter(new_blob_value));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(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());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
for (const auto& key : keys) {
|
||||
ASSERT_EQ(new_blob_value, Get(key));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter unconditionally changes value in FilterBlobByKey;
|
||||
// this involves writing but not reading blobs
|
||||
ASSERT_EQ(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_GT(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_P(DBBlobBadCompactionFilterTest, BadDecisionFromCompactionFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
options.compaction_filter = compaction_filter_guard_.get();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("b", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsNotSupported());
|
||||
Close();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
std::string key(std::get<0>(GetParam()));
|
||||
ASSERT_OK(Put(key, "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsNotSupported());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionFilter_InlinedTTLIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueMutationFilter(""));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
constexpr char key[] = "key";
|
||||
constexpr char blob[] = "blob";
|
||||
// Fake an inlined TTL blob index.
|
||||
std::string blob_index;
|
||||
constexpr uint64_t expiration = 1234567890;
|
||||
BlobIndex::EncodeInlinedTTL(&blob_index, expiration, blob);
|
||||
WriteBatch batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&batch, 0, key, blob_index));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsCorruption());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionFilter) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
constexpr char padding[] = "_delta";
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueMutationFilter(padding));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
const std::vector<std::pair<std::string, std::string>> kvs = {
|
||||
{"a", "a_value"}, {"b", "b_value"}, {"c", "c_value"}};
|
||||
for (const auto& kv : kvs) {
|
||||
ASSERT_OK(Put(kv.first, kv.second));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
for (const auto& kv : kvs) {
|
||||
ASSERT_EQ(kv.second + std::string(padding), Get(kv.first));
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter changes the value using the previous value in FilterV2;
|
||||
// this involves reading and writing blobs
|
||||
ASSERT_GT(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_GT(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CorruptedBlobIndex) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new ValueMutationFilter(""));
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
// Mock a corrupted blob index
|
||||
constexpr char key[] = "key";
|
||||
std::string blob_idx("blob_idx");
|
||||
WriteBatch write_batch;
|
||||
ASSERT_OK(WriteBatchInternal::PutBlobIndex(&write_batch, 0, key, blob_idx));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &write_batch));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr)
|
||||
.IsCorruption());
|
||||
Close();
|
||||
}
|
||||
|
||||
TEST_F(DBBlobCompactionTest, CompactionFilterReadBlobAndKeep) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_guard(
|
||||
new AlwaysKeepFilter());
|
||||
options.compaction_filter = compaction_filter_guard.get();
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("foo", "foo_value"));
|
||||
ASSERT_OK(Flush());
|
||||
std::vector<uint64_t> blob_files = GetBlobFileNumbers();
|
||||
ASSERT_EQ(1, blob_files.size());
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), /*begin=*/nullptr,
|
||||
/*end=*/nullptr));
|
||||
ASSERT_EQ(blob_files, GetBlobFileNumbers());
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
const auto& compaction_stats = GetCompactionStats();
|
||||
ASSERT_GE(compaction_stats.size(), 2);
|
||||
|
||||
// Filter decides to keep the existing value in FilterV2;
|
||||
// this involves reading but not writing blobs
|
||||
ASSERT_GT(compaction_stats[1].bytes_read_blob, 0);
|
||||
ASSERT_EQ(compaction_stats[1].bytes_written_blob, 0);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// 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/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "test_util/sync_point.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class DBBlobCorruptionTest : public DBTestBase {
|
||||
protected:
|
||||
DBBlobCorruptionTest()
|
||||
: DBTestBase("/db_blob_corruption_test", /* env_do_fsync */ false) {}
|
||||
|
||||
void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) {
|
||||
// Pick file to corrupt
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
std::string fname;
|
||||
uint64_t picked_number = kInvalidBlobFileNumber;
|
||||
for (size_t i = 0; i < filenames.size(); i++) {
|
||||
if (ParseFileName(filenames[i], &number, &type) && type == filetype &&
|
||||
number > picked_number) { // Pick latest file
|
||||
fname = dbname_ + "/" + filenames[i];
|
||||
picked_number = number;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(!fname.empty()) << filetype;
|
||||
ASSERT_OK(test::CorruptFile(env_, fname, offset, bytes_to_corrupt));
|
||||
}
|
||||
};
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBlobCorruptionTest, VerifyWholeBlobFileChecksum) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.enable_blob_files = true;
|
||||
options.min_blob_size = 0;
|
||||
options.create_if_missing = true;
|
||||
options.file_checksum_gen_factory =
|
||||
ROCKSDB_NAMESPACE::GetFileChecksumGenCrc32cFactory();
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put(Slice("key_1"), Slice("blob_value_1")));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put(Slice("key_2"), Slice("blob_value_2")));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
Close();
|
||||
|
||||
Corrupt(kBlobFile, 0, 2);
|
||||
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
int count{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::VerifyFullFileChecksum:mismatch", [&](void* arg) {
|
||||
const Status* s = static_cast<Status*>(arg);
|
||||
ASSERT_NE(s, nullptr);
|
||||
++count;
|
||||
ASSERT_NOK(*s);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsCorruption());
|
||||
ASSERT_EQ(1, count);
|
||||
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
ROCKSDB_NAMESPACE::port::InstallStackTraceHandler();
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+151
-16
@@ -73,6 +73,9 @@ class DBBlobIndexTest : public DBTestBase {
|
||||
if (s.IsNotFound()) {
|
||||
return "NOT_FOUND";
|
||||
}
|
||||
if (s.IsCorruption()) {
|
||||
return "CORRUPTION";
|
||||
}
|
||||
if (s.IsNotSupported()) {
|
||||
return "NOT_SUPPORTED";
|
||||
}
|
||||
@@ -95,11 +98,12 @@ class DBBlobIndexTest : public DBTestBase {
|
||||
ArenaWrappedDBIter* GetBlobIterator() {
|
||||
return dbfull()->NewIteratorImpl(
|
||||
ReadOptions(), cfd(), dbfull()->GetLatestSequenceNumber(),
|
||||
nullptr /*read_callback*/, true /*allow_blob*/);
|
||||
nullptr /*read_callback*/, true /*expose_blob_index*/);
|
||||
}
|
||||
|
||||
Options GetTestOptions() {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -153,8 +157,13 @@ TEST_F(DBBlobIndexTest, Write) {
|
||||
}
|
||||
}
|
||||
|
||||
// Get should be able to return blob index if is_blob_index is provided,
|
||||
// otherwise return Status::NotSupported status.
|
||||
// Note: the following test case pertains to the StackableDB-based BlobDB
|
||||
// implementation. Get should be able to return blob index if is_blob_index is
|
||||
// provided, otherwise it should return Status::NotSupported (when reading from
|
||||
// memtable) or Status::Corruption (when reading from SST). Reading from SST
|
||||
// returns Corruption because we can't differentiate between the application
|
||||
// accidentally opening the base DB of a stacked BlobDB and actual corruption
|
||||
// when using the integrated BlobDB.
|
||||
TEST_F(DBBlobIndexTest, Get) {
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
@@ -171,15 +180,22 @@ TEST_F(DBBlobIndexTest, Get) {
|
||||
ASSERT_EQ("value", GetImpl("key", &is_blob_index));
|
||||
ASSERT_FALSE(is_blob_index);
|
||||
// Verify blob index
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsNotSupported());
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("blob_key"));
|
||||
if (tier <= kImmutableMemtables) {
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsNotSupported());
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("blob_key"));
|
||||
} else {
|
||||
ASSERT_TRUE(Get("blob_key", &value).IsCorruption());
|
||||
ASSERT_EQ("CORRUPTION", GetImpl("blob_key"));
|
||||
}
|
||||
ASSERT_EQ("blob_index", GetImpl("blob_key", &is_blob_index));
|
||||
ASSERT_TRUE(is_blob_index);
|
||||
}
|
||||
}
|
||||
|
||||
// Get should NOT return Status::NotSupported if blob index is updated with
|
||||
// a normal value.
|
||||
// Note: the following test case pertains to the StackableDB-based BlobDB
|
||||
// implementation. Get should NOT return Status::NotSupported/Status::Corruption
|
||||
// if blob index is updated with a normal value. See the test case above for
|
||||
// more details.
|
||||
TEST_F(DBBlobIndexTest, Updated) {
|
||||
for (auto tier : kAllTiers) {
|
||||
DestroyAndReopen(GetTestOptions());
|
||||
@@ -206,7 +222,11 @@ TEST_F(DBBlobIndexTest, Updated) {
|
||||
ASSERT_EQ("blob_index", GetBlobIndex("key" + ToString(i), snapshot));
|
||||
}
|
||||
ASSERT_EQ("new_value", Get("key1"));
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("key2"));
|
||||
if (tier <= kImmutableMemtables) {
|
||||
ASSERT_EQ("NOT_SUPPORTED", GetImpl("key2"));
|
||||
} else {
|
||||
ASSERT_EQ("CORRUPTION", GetImpl("key2"));
|
||||
}
|
||||
ASSERT_EQ("NOT_FOUND", Get("key3"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("key4"));
|
||||
ASSERT_EQ("a,b,c", GetImpl("key5"));
|
||||
@@ -218,8 +238,11 @@ TEST_F(DBBlobIndexTest, Updated) {
|
||||
}
|
||||
}
|
||||
|
||||
// Iterator should get blob value if allow_blob flag is set,
|
||||
// otherwise return Status::NotSupported status.
|
||||
// Note: the following test case pertains to the StackableDB-based BlobDB
|
||||
// implementation. When a blob iterator is used, it should set the
|
||||
// expose_blob_index flag for the underlying DBIter, and retrieve/return the
|
||||
// corresponding blob value. If a regular DBIter is created (i.e.
|
||||
// expose_blob_index is not set), it should return Status::Corruption.
|
||||
TEST_F(DBBlobIndexTest, Iterate) {
|
||||
const std::vector<std::vector<ValueType>> data = {
|
||||
/*00*/ {kTypeValue},
|
||||
@@ -282,6 +305,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
std::function<void(Iterator*)> extra_check = nullptr) {
|
||||
// Seek
|
||||
auto* iterator = create_iterator();
|
||||
ASSERT_OK(iterator->status());
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->Seek(get_key(index));
|
||||
check_iterator(iterator, expected_status, forward_value);
|
||||
@@ -295,6 +319,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
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, forward_value);
|
||||
if (extra_check) {
|
||||
@@ -304,6 +329,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
|
||||
// SeekForPrev
|
||||
iterator = create_iterator();
|
||||
ASSERT_OK(iterator->status());
|
||||
ASSERT_OK(iterator->Refresh());
|
||||
iterator->SeekForPrev(get_key(index));
|
||||
check_iterator(iterator, expected_status, backward_value);
|
||||
@@ -316,6 +342,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
iterator = create_iterator();
|
||||
iterator->Seek(get_key(index + 1));
|
||||
ASSERT_TRUE(iterator->Valid());
|
||||
ASSERT_OK(iterator->status());
|
||||
iterator->Prev();
|
||||
check_iterator(iterator, expected_status, backward_value);
|
||||
if (extra_check) {
|
||||
@@ -353,7 +380,7 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
ASSERT_OK(Write(&batch));
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
FAIL();
|
||||
};
|
||||
}
|
||||
snapshots.push_back(dbfull()->GetSnapshot());
|
||||
@@ -364,15 +391,15 @@ TEST_F(DBBlobIndexTest, Iterate) {
|
||||
MoveDataTo(tier);
|
||||
|
||||
// Normal iterator
|
||||
verify(1, Status::kNotSupported, "", "", create_normal_iterator);
|
||||
verify(3, Status::kNotSupported, "", "", create_normal_iterator);
|
||||
verify(1, Status::kCorruption, "", "", create_normal_iterator);
|
||||
verify(3, Status::kCorruption, "", "", create_normal_iterator);
|
||||
verify(5, Status::kOk, get_value(5, 0), get_value(5, 0),
|
||||
create_normal_iterator);
|
||||
verify(7, Status::kOk, get_value(8, 0), get_value(6, 0),
|
||||
create_normal_iterator);
|
||||
verify(9, Status::kOk, get_value(10, 0), get_value(8, 0),
|
||||
create_normal_iterator);
|
||||
verify(11, Status::kNotSupported, "", "", create_normal_iterator);
|
||||
verify(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),
|
||||
@@ -391,7 +418,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),
|
||||
@@ -413,7 +444,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),
|
||||
@@ -428,6 +463,106 @@ 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
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+135
-92
@@ -23,11 +23,13 @@
|
||||
#include "db/range_del_aggregator.h"
|
||||
#include "db/table_cache.h"
|
||||
#include "db/version_edit.h"
|
||||
#include "file/file_util.h"
|
||||
#include "file/filename.h"
|
||||
#include "file/read_write_util.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "monitoring/iostats_context_imp.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/env.h"
|
||||
#include "rocksdb/iterator.h"
|
||||
@@ -43,62 +45,41 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class TableFactory;
|
||||
|
||||
TableBuilder* NewTableBuilder(
|
||||
const ImmutableCFOptions& ioptions, const MutableCFOptions& moptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
int level, const bool skip_filters, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, const uint64_t target_file_size,
|
||||
const uint64_t file_creation_time, const std::string& db_id,
|
||||
const std::string& db_session_id) {
|
||||
assert((column_family_id ==
|
||||
TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
WritableFileWriter* file) {
|
||||
assert((tboptions.column_family_id ==
|
||||
TablePropertiesCollectorFactory::Context::kUnknownColumnFamily) ==
|
||||
column_family_name.empty());
|
||||
return ioptions.table_factory->NewTableBuilder(
|
||||
TableBuilderOptions(ioptions, moptions, internal_comparator,
|
||||
int_tbl_prop_collector_factories, compression_type,
|
||||
sample_for_compression, compression_opts,
|
||||
skip_filters, column_family_name, level,
|
||||
creation_time, oldest_key_time, target_file_size,
|
||||
file_creation_time, db_id, db_session_id),
|
||||
column_family_id, file);
|
||||
tboptions.column_family_name.empty());
|
||||
return tboptions.ioptions.table_factory->NewTableBuilder(tboptions, file);
|
||||
}
|
||||
|
||||
Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions, Env* env, FileSystem* fs,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const MutableCFOptions& mutable_cf_options, const FileOptions& file_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
|
||||
const FileOptions& file_options, TableCache* table_cache,
|
||||
InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, std::vector<BlobFileAddition>* blob_file_additions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
uint64_t sample_for_compression, const CompressionOptions& compression_opts,
|
||||
bool paranoid_file_checks, InternalStats* internal_stats,
|
||||
TableFileCreationReason reason, IOStatus* io_status,
|
||||
SnapshotChecker* snapshot_checker, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, IOStatus* io_status,
|
||||
const std::shared_ptr<IOTracer>& io_tracer, EventLogger* event_logger,
|
||||
int job_id, const Env::IOPriority io_priority,
|
||||
TableProperties* table_properties, int level, const uint64_t creation_time,
|
||||
const uint64_t oldest_key_time, Env::WriteLifeTimeHint write_hint,
|
||||
const uint64_t file_creation_time, const std::string& db_id,
|
||||
const std::string& db_session_id) {
|
||||
assert((column_family_id ==
|
||||
TableProperties* table_properties, Env::WriteLifeTimeHint write_hint,
|
||||
const std::string* full_history_ts_low,
|
||||
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) ==
|
||||
column_family_name.empty());
|
||||
tboptions.column_family_name.empty());
|
||||
auto& mutable_cf_options = tboptions.moptions;
|
||||
auto& ioptions = tboptions.ioptions;
|
||||
// Reports the IOStats for flush for every following bytes.
|
||||
const size_t kReportFlushIOStatsEvery = 1048576;
|
||||
OutputValidator output_validator(
|
||||
internal_comparator,
|
||||
tboptions.internal_comparator,
|
||||
/*enable_order_check=*/
|
||||
mutable_cf_options.check_flush_compaction_key_order,
|
||||
/*enable_hash=*/paranoid_file_checks);
|
||||
@@ -106,8 +87,15 @@ Status BuildTable(
|
||||
meta->fd.file_size = 0;
|
||||
iter->SeekToFirst();
|
||||
std::unique_ptr<CompactionRangeDelAggregator> range_del_agg(
|
||||
new CompactionRangeDelAggregator(&internal_comparator, snapshots));
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -117,18 +105,39 @@ Status BuildTable(
|
||||
std::string file_checksum = kUnknownFileChecksum;
|
||||
std::string file_checksum_func_name = kUnknownFileChecksumFuncName;
|
||||
#ifndef ROCKSDB_LITE
|
||||
EventHelpers::NotifyTableFileCreationStarted(
|
||||
ioptions.listeners, dbname, column_family_name, fname, job_id, reason);
|
||||
EventHelpers::NotifyTableFileCreationStarted(ioptions.listeners, dbname,
|
||||
tboptions.column_family_name,
|
||||
fname, job_id, tboptions.reason);
|
||||
#endif // !ROCKSDB_LITE
|
||||
Env* env = db_options.env;
|
||||
assert(env);
|
||||
FileSystem* fs = db_options.fs.get();
|
||||
assert(fs);
|
||||
|
||||
TableProperties tp;
|
||||
if (iter->Valid() || !range_del_agg->IsEmpty()) {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter;
|
||||
if (ioptions.compaction_filter_factory != nullptr &&
|
||||
ioptions.compaction_filter_factory->ShouldFilterTableFileCreation(
|
||||
tboptions.reason)) {
|
||||
CompactionFilter::Context context;
|
||||
context.is_full_compaction = false;
|
||||
context.is_manual_compaction = false;
|
||||
context.column_family_id = tboptions.column_family_id;
|
||||
context.reason = tboptions.reason;
|
||||
compaction_filter =
|
||||
ioptions.compaction_filter_factory->CreateCompactionFilter(context);
|
||||
if (compaction_filter != nullptr &&
|
||||
!compaction_filter->IgnoreSnapshots()) {
|
||||
s.PermitUncheckedError();
|
||||
return Status::NotSupported(
|
||||
"CompactionFilter::IgnoreSnapshots() = false is not supported "
|
||||
"anymore.");
|
||||
}
|
||||
}
|
||||
|
||||
TableBuilder* builder;
|
||||
std::unique_ptr<WritableFileWriter> file_writer;
|
||||
// Currently we only enable dictionary compression during compaction to the
|
||||
// bottommost level.
|
||||
CompressionOptions compression_opts_for_flush(compression_opts);
|
||||
compression_opts_for_flush.max_dict_bytes = 0;
|
||||
compression_opts_for_flush.zstd_max_train_bytes = 0;
|
||||
{
|
||||
std::unique_ptr<FSWritableFile> file;
|
||||
#ifndef NDEBUG
|
||||
@@ -143,49 +152,51 @@ Status BuildTable(
|
||||
}
|
||||
if (!s.ok()) {
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, kInvalidBlobFileNumber, tp, reason, s,
|
||||
file_checksum, file_checksum_func_name);
|
||||
event_logger, ioptions.listeners, dbname,
|
||||
tboptions.column_family_name, fname, job_id, meta->fd,
|
||||
kInvalidBlobFileNumber, tp, tboptions.reason, s, file_checksum,
|
||||
file_checksum_func_name);
|
||||
return s;
|
||||
}
|
||||
FileTypeSet tmp_set = ioptions.checksum_handoff_file_types;
|
||||
file->SetIOPriority(io_priority);
|
||||
file->SetWriteLifeTimeHint(write_hint);
|
||||
|
||||
file_writer.reset(new WritableFileWriter(
|
||||
std::move(file), fname, file_options, env, io_tracer,
|
||||
ioptions.statistics, ioptions.listeners,
|
||||
ioptions.file_checksum_gen_factory));
|
||||
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)));
|
||||
|
||||
builder = NewTableBuilder(
|
||||
ioptions, mutable_cf_options, internal_comparator,
|
||||
int_tbl_prop_collector_factories, column_family_id,
|
||||
column_family_name, file_writer.get(), compression,
|
||||
sample_for_compression, compression_opts_for_flush, level,
|
||||
false /* skip_filters */, creation_time, oldest_key_time,
|
||||
0 /*target_file_size*/, file_creation_time, db_id, db_session_id);
|
||||
builder = NewTableBuilder(tboptions, file_writer.get());
|
||||
}
|
||||
|
||||
MergeHelper merge(env, internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator, nullptr, ioptions.info_log,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back(),
|
||||
snapshot_checker);
|
||||
MergeHelper merge(
|
||||
env, tboptions.internal_comparator.user_comparator(),
|
||||
ioptions.merge_operator.get(), compaction_filter.get(), ioptions.logger,
|
||||
true /* internal key corruption is not ok */,
|
||||
snapshots.empty() ? 0 : snapshots.back(), snapshot_checker);
|
||||
|
||||
std::unique_ptr<BlobFileBuilder> blob_file_builder(
|
||||
(mutable_cf_options.enable_blob_files && blob_file_additions)
|
||||
? new BlobFileBuilder(versions, env, fs, &ioptions,
|
||||
&mutable_cf_options, &file_options, job_id,
|
||||
column_family_id, column_family_name,
|
||||
io_priority, write_hint, &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_file_paths, blob_file_additions)
|
||||
: nullptr);
|
||||
|
||||
CompactionIterator c_iter(
|
||||
iter, internal_comparator.user_comparator(), &merge, kMaxSequenceNumber,
|
||||
&snapshots, earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
ShouldReportDetailedTime(env, ioptions.statistics),
|
||||
iter, tboptions.internal_comparator.user_comparator(), &merge,
|
||||
kMaxSequenceNumber, &snapshots, earliest_write_conflict_snapshot,
|
||||
snapshot_checker, env, ShouldReportDetailedTime(env, ioptions.stats),
|
||||
true /* internal key corruption is not ok */, range_del_agg.get(),
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors);
|
||||
blob_file_builder.get(), ioptions.allow_data_in_errors,
|
||||
/*compaction=*/nullptr, compaction_filter.get(),
|
||||
/*shutting_down=*/nullptr,
|
||||
/*preserve_deletes_seqnum=*/0, /*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, db_options.info_log,
|
||||
full_history_ts_low);
|
||||
|
||||
c_iter.SeekToFirst();
|
||||
for (; c_iter.Valid(); c_iter.Next()) {
|
||||
@@ -212,6 +223,7 @@ Status BuildTable(
|
||||
} else if (!c_iter.status().ok()) {
|
||||
s = c_iter.status();
|
||||
}
|
||||
|
||||
if (s.ok()) {
|
||||
auto range_del_it = range_del_agg->NewIterator();
|
||||
for (range_del_it->SeekToFirst(); range_del_it->Valid();
|
||||
@@ -220,16 +232,17 @@ Status BuildTable(
|
||||
auto kv = tombstone.Serialize();
|
||||
builder->Add(kv.first.Encode(), kv.second);
|
||||
meta->UpdateBoundariesForRange(kv.first, tombstone.SerializeEndKey(),
|
||||
tombstone.seq_, internal_comparator);
|
||||
}
|
||||
|
||||
if (blob_file_builder) {
|
||||
s = blob_file_builder->Finish();
|
||||
tombstone.seq_,
|
||||
tboptions.internal_comparator);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_SYNC_POINT("BuildTable:BeforeFinishBuildTable");
|
||||
const bool empty = builder->IsEmpty();
|
||||
if (num_input_entries != nullptr) {
|
||||
*num_input_entries =
|
||||
c_iter.num_input_entry_scanned() + num_unfragmented_tombstones;
|
||||
}
|
||||
if (!s.ok() || empty) {
|
||||
builder->Abandon();
|
||||
} else {
|
||||
@@ -245,6 +258,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;
|
||||
}
|
||||
@@ -254,7 +286,7 @@ Status BuildTable(
|
||||
// Finish and check for file errors
|
||||
TEST_SYNC_POINT("BuildTable:BeforeSyncTable");
|
||||
if (s.ok() && !empty) {
|
||||
StopWatch sw(env, ioptions.statistics, TABLE_SYNC_MICROS);
|
||||
StopWatch sw(ioptions.clock, ioptions.stats, TABLE_SYNC_MICROS);
|
||||
*io_status = file_writer->Sync(ioptions.use_fsync);
|
||||
}
|
||||
TEST_SYNC_POINT("BuildTable:BeforeCloseTableFile");
|
||||
@@ -273,6 +305,15 @@ Status BuildTable(
|
||||
s = *io_status;
|
||||
}
|
||||
|
||||
if (blob_file_builder) {
|
||||
if (s.ok()) {
|
||||
s = blob_file_builder->Finish();
|
||||
} else {
|
||||
blob_file_builder->Abandon();
|
||||
}
|
||||
blob_file_builder.reset();
|
||||
}
|
||||
|
||||
// TODO Also check the IO status when create the Iterator.
|
||||
|
||||
if (s.ok() && !empty) {
|
||||
@@ -284,20 +325,20 @@ Status BuildTable(
|
||||
// to cache it here for further user reads
|
||||
ReadOptions read_options;
|
||||
std::unique_ptr<InternalIterator> it(table_cache->NewIterator(
|
||||
read_options, file_options, internal_comparator, *meta,
|
||||
read_options, file_options, tboptions.internal_comparator, *meta,
|
||||
nullptr /* range_del_agg */,
|
||||
mutable_cf_options.prefix_extractor.get(), nullptr,
|
||||
(internal_stats == nullptr) ? nullptr
|
||||
: internal_stats->GetFileReadHist(0),
|
||||
TableReaderCaller::kFlush, /*arena=*/nullptr,
|
||||
/*skip_filter=*/false, level,
|
||||
/*skip_filter=*/false, tboptions.level_at_creation,
|
||||
MaxFileSizeForL0MetaPin(mutable_cf_options),
|
||||
/*smallest_compaction_key=*/nullptr,
|
||||
/*largest_compaction_key*/ nullptr,
|
||||
/*allow_unprepared_value*/ false));
|
||||
s = it->status();
|
||||
if (s.ok() && paranoid_file_checks) {
|
||||
OutputValidator file_validator(internal_comparator,
|
||||
OutputValidator file_validator(tboptions.internal_comparator,
|
||||
/*enable_order_check=*/true,
|
||||
/*enable_hash=*/true);
|
||||
for (it->SeekToFirst(); it->Valid(); it->Next()) {
|
||||
@@ -318,6 +359,8 @@ Status BuildTable(
|
||||
}
|
||||
|
||||
if (!s.ok() || meta->fd.GetFileSize() == 0) {
|
||||
TEST_SYNC_POINT("BuildTable:BeforeDeleteFile");
|
||||
|
||||
constexpr IODebugContext* dbg = nullptr;
|
||||
|
||||
Status ignored = fs->DeleteFile(fname, IOOptions(), dbg);
|
||||
@@ -327,11 +370,11 @@ Status BuildTable(
|
||||
|
||||
if (blob_file_additions) {
|
||||
for (const std::string& blob_file_path : blob_file_paths) {
|
||||
ignored = fs->DeleteFile(blob_file_path, IOOptions(), dbg);
|
||||
ignored = DeleteDBFile(&db_options, blob_file_path, dbname,
|
||||
/*force_bg=*/false, /*force_fg=*/false);
|
||||
ignored.PermitUncheckedError();
|
||||
TEST_SYNC_POINT("BuildTable::AfterDeleteFile");
|
||||
}
|
||||
|
||||
blob_file_additions->clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,9 +383,9 @@ Status BuildTable(
|
||||
}
|
||||
// Output to event logger and fire events.
|
||||
EventHelpers::LogAndNotifyTableFileCreationFinished(
|
||||
event_logger, ioptions.listeners, dbname, column_family_name, fname,
|
||||
job_id, meta->fd, meta->oldest_blob_file_number, tp, reason, s,
|
||||
file_checksum, file_checksum_func_name);
|
||||
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);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
+17
-38
@@ -24,37 +24,20 @@
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
struct Options;
|
||||
struct FileMetaData;
|
||||
|
||||
class VersionSet;
|
||||
class Env;
|
||||
struct EnvOptions;
|
||||
class BlobFileAddition;
|
||||
class Iterator;
|
||||
class SnapshotChecker;
|
||||
class TableCache;
|
||||
class VersionEdit;
|
||||
class TableBuilder;
|
||||
class WritableFileWriter;
|
||||
class InternalStats;
|
||||
class BlobFileCompletionCallback;
|
||||
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown. It must outlive the
|
||||
// TableBuilder returned by this function.
|
||||
TableBuilder* NewTableBuilder(
|
||||
const ImmutableCFOptions& options, const MutableCFOptions& moptions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
WritableFileWriter* file, const CompressionType compression_type,
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, int level,
|
||||
const bool skip_filters = false, const uint64_t creation_time = 0,
|
||||
const uint64_t oldest_key_time = 0, const uint64_t target_file_size = 0,
|
||||
const uint64_t file_creation_time = 0, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
// Convenience function for NewTableBuilder on the embedded table_factory.
|
||||
TableBuilder* NewTableBuilder(const TableBuilderOptions& tboptions,
|
||||
WritableFileWriter* file);
|
||||
|
||||
// Build a Table file from the contents of *iter. The generated file
|
||||
// will be named according to number specified in meta. On success, the rest of
|
||||
@@ -65,30 +48,26 @@ TableBuilder* NewTableBuilder(
|
||||
// @param column_family_name Name of the column family that is also identified
|
||||
// by column_family_id, or empty string if unknown.
|
||||
extern Status BuildTable(
|
||||
const std::string& dbname, VersionSet* versions, Env* env, FileSystem* fs,
|
||||
const ImmutableCFOptions& options,
|
||||
const MutableCFOptions& mutable_cf_options, const FileOptions& file_options,
|
||||
TableCache* table_cache, InternalIterator* iter,
|
||||
const std::string& dbname, VersionSet* versions,
|
||||
const ImmutableDBOptions& db_options, const TableBuilderOptions& tboptions,
|
||||
const FileOptions& file_options, TableCache* table_cache,
|
||||
InternalIterator* iter,
|
||||
std::vector<std::unique_ptr<FragmentedRangeTombstoneIterator>>
|
||||
range_del_iters,
|
||||
FileMetaData* meta, std::vector<BlobFileAddition>* blob_file_additions,
|
||||
const InternalKeyComparator& internal_comparator,
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories,
|
||||
uint32_t column_family_id, const std::string& column_family_name,
|
||||
std::vector<SequenceNumber> snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
SnapshotChecker* snapshot_checker, const CompressionType compression,
|
||||
const uint64_t sample_for_compression,
|
||||
const CompressionOptions& compression_opts, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, TableFileCreationReason reason,
|
||||
IOStatus* io_status, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
SnapshotChecker* snapshot_checker, bool paranoid_file_checks,
|
||||
InternalStats* internal_stats, IOStatus* io_status,
|
||||
const std::shared_ptr<IOTracer>& io_tracer,
|
||||
EventLogger* event_logger = nullptr, int job_id = 0,
|
||||
const Env::IOPriority io_priority = Env::IO_HIGH,
|
||||
TableProperties* table_properties = nullptr, int level = -1,
|
||||
const uint64_t creation_time = 0, const uint64_t oldest_key_time = 0,
|
||||
TableProperties* table_properties = nullptr,
|
||||
Env::WriteLifeTimeHint write_hint = Env::WLTH_NOT_SET,
|
||||
const uint64_t file_creation_time = 0, const std::string& db_id = "",
|
||||
const std::string& db_session_id = "");
|
||||
const std::string* full_history_ts_low = nullptr,
|
||||
BlobFileCompletionCallback* blob_callback = nullptr,
|
||||
uint64_t* num_input_entries = nullptr,
|
||||
uint64_t* memtable_payload_bytes = nullptr,
|
||||
uint64_t* memtable_garbage_bytes = nullptr);
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -11,7 +11,11 @@
|
||||
|
||||
#include "rocksdb/c.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <cstdlib>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/compaction_filter.h"
|
||||
@@ -24,6 +28,7 @@
|
||||
#include "rocksdb/memtablerep.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/rate_limiter.h"
|
||||
#include "rocksdb/slice_transform.h"
|
||||
#include "rocksdb/statistics.h"
|
||||
@@ -35,17 +40,13 @@
|
||||
#include "rocksdb/utilities/db_ttl.h"
|
||||
#include "rocksdb/utilities/memory_util.h"
|
||||
#include "rocksdb/utilities/optimistic_transaction_db.h"
|
||||
#include "rocksdb/utilities/table_properties_collectors.h"
|
||||
#include "rocksdb/utilities/transaction.h"
|
||||
#include "rocksdb/utilities/transaction_db.h"
|
||||
#include "rocksdb/utilities/write_batch_with_index.h"
|
||||
#include "rocksdb/write_batch.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
|
||||
using ROCKSDB_NAMESPACE::BackupableDBOptions;
|
||||
using ROCKSDB_NAMESPACE::BackupEngine;
|
||||
using ROCKSDB_NAMESPACE::BackupID;
|
||||
@@ -60,7 +61,6 @@ using ROCKSDB_NAMESPACE::ColumnFamilyDescriptor;
|
||||
using ROCKSDB_NAMESPACE::ColumnFamilyHandle;
|
||||
using ROCKSDB_NAMESPACE::ColumnFamilyOptions;
|
||||
using ROCKSDB_NAMESPACE::CompactionFilter;
|
||||
using ROCKSDB_NAMESPACE::CompactionFilterContext;
|
||||
using ROCKSDB_NAMESPACE::CompactionFilterFactory;
|
||||
using ROCKSDB_NAMESPACE::CompactionOptionsFIFO;
|
||||
using ROCKSDB_NAMESPACE::CompactRangeOptions;
|
||||
@@ -80,10 +80,12 @@ using ROCKSDB_NAMESPACE::IngestExternalFileOptions;
|
||||
using ROCKSDB_NAMESPACE::Iterator;
|
||||
using ROCKSDB_NAMESPACE::LiveFileMetaData;
|
||||
using ROCKSDB_NAMESPACE::Logger;
|
||||
using ROCKSDB_NAMESPACE::LRUCacheOptions;
|
||||
using ROCKSDB_NAMESPACE::MemoryAllocator;
|
||||
using ROCKSDB_NAMESPACE::MemoryUtil;
|
||||
using ROCKSDB_NAMESPACE::MergeOperator;
|
||||
using ROCKSDB_NAMESPACE::MergeOperators;
|
||||
using ROCKSDB_NAMESPACE::NewBloomFilterPolicy;
|
||||
using ROCKSDB_NAMESPACE::NewCompactOnDeletionCollectorFactory;
|
||||
using ROCKSDB_NAMESPACE::NewGenericRateLimiter;
|
||||
using ROCKSDB_NAMESPACE::NewLRUCache;
|
||||
using ROCKSDB_NAMESPACE::OptimisticTransactionDB;
|
||||
@@ -104,6 +106,7 @@ using ROCKSDB_NAMESPACE::SliceTransform;
|
||||
using ROCKSDB_NAMESPACE::Snapshot;
|
||||
using ROCKSDB_NAMESPACE::SstFileWriter;
|
||||
using ROCKSDB_NAMESPACE::Status;
|
||||
using ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory;
|
||||
using ROCKSDB_NAMESPACE::Transaction;
|
||||
using ROCKSDB_NAMESPACE::TransactionDB;
|
||||
using ROCKSDB_NAMESPACE::TransactionDBOptions;
|
||||
@@ -115,10 +118,8 @@ using ROCKSDB_NAMESPACE::WriteBatch;
|
||||
using ROCKSDB_NAMESPACE::WriteBatchWithIndex;
|
||||
using ROCKSDB_NAMESPACE::WriteOptions;
|
||||
|
||||
using std::shared_ptr;
|
||||
using std::vector;
|
||||
using std::unordered_set;
|
||||
using std::map;
|
||||
|
||||
extern "C" {
|
||||
|
||||
@@ -154,6 +155,12 @@ struct rocksdb_filelock_t { FileLock* rep; };
|
||||
struct rocksdb_logger_t {
|
||||
std::shared_ptr<Logger> rep;
|
||||
};
|
||||
struct rocksdb_lru_cache_options_t {
|
||||
LRUCacheOptions rep;
|
||||
};
|
||||
struct rocksdb_memory_allocator_t {
|
||||
std::shared_ptr<MemoryAllocator> rep;
|
||||
};
|
||||
struct rocksdb_cache_t {
|
||||
std::shared_ptr<Cache> rep;
|
||||
};
|
||||
@@ -181,6 +188,9 @@ struct rocksdb_transaction_options_t {
|
||||
struct rocksdb_transaction_t {
|
||||
Transaction* rep;
|
||||
};
|
||||
struct rocksdb_backupable_db_options_t {
|
||||
BackupableDBOptions rep;
|
||||
};
|
||||
struct rocksdb_checkpoint_t {
|
||||
Checkpoint* rep;
|
||||
};
|
||||
@@ -549,6 +559,18 @@ rocksdb_backup_engine_t* rocksdb_backup_engine_open(
|
||||
return result;
|
||||
}
|
||||
|
||||
rocksdb_backup_engine_t* rocksdb_backup_engine_open_opts(
|
||||
const rocksdb_backupable_db_options_t* options, rocksdb_env_t* env,
|
||||
char** errptr) {
|
||||
BackupEngine* be;
|
||||
if (SaveError(errptr, BackupEngine::Open(options->rep, env->rep, &be))) {
|
||||
return nullptr;
|
||||
}
|
||||
rocksdb_backup_engine_t* result = new rocksdb_backup_engine_t;
|
||||
result->rep = be;
|
||||
return result;
|
||||
}
|
||||
|
||||
void rocksdb_backup_engine_create_new_backup(rocksdb_backup_engine_t* be,
|
||||
rocksdb_t* db,
|
||||
char** errptr) {
|
||||
@@ -645,6 +667,128 @@ void rocksdb_backup_engine_close(rocksdb_backup_engine_t* be) {
|
||||
delete be;
|
||||
}
|
||||
|
||||
rocksdb_backupable_db_options_t* rocksdb_backupable_db_options_create(
|
||||
const char* backup_dir) {
|
||||
return new rocksdb_backupable_db_options_t{
|
||||
BackupableDBOptions(std::string(backup_dir))};
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_backup_dir(
|
||||
rocksdb_backupable_db_options_t* options, const char* backup_dir) {
|
||||
options->rep.backup_dir = std::string(backup_dir);
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_env(
|
||||
rocksdb_backupable_db_options_t* options, rocksdb_env_t* env) {
|
||||
options->rep.backup_env = (env ? env->rep : nullptr);
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_share_table_files(
|
||||
rocksdb_backupable_db_options_t* options, unsigned char val) {
|
||||
options->rep.share_table_files = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_backupable_db_options_get_share_table_files(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.share_table_files;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_sync(
|
||||
rocksdb_backupable_db_options_t* options, unsigned char val) {
|
||||
options->rep.sync = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_backupable_db_options_get_sync(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.sync;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_destroy_old_data(
|
||||
rocksdb_backupable_db_options_t* options, unsigned char val) {
|
||||
options->rep.destroy_old_data = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_backupable_db_options_get_destroy_old_data(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.destroy_old_data;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_backup_log_files(
|
||||
rocksdb_backupable_db_options_t* options, unsigned char val) {
|
||||
options->rep.backup_log_files = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_backupable_db_options_get_backup_log_files(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.backup_log_files;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_backup_rate_limit(
|
||||
rocksdb_backupable_db_options_t* options, uint64_t limit) {
|
||||
options->rep.backup_rate_limit = limit;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_backupable_db_options_get_backup_rate_limit(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.backup_rate_limit;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_restore_rate_limit(
|
||||
rocksdb_backupable_db_options_t* options, uint64_t limit) {
|
||||
options->rep.restore_rate_limit = limit;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_backupable_db_options_get_restore_rate_limit(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.restore_rate_limit;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_max_background_operations(
|
||||
rocksdb_backupable_db_options_t* options, int val) {
|
||||
options->rep.max_background_operations = val;
|
||||
}
|
||||
|
||||
int rocksdb_backupable_db_options_get_max_background_operations(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.max_background_operations;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_callback_trigger_interval_size(
|
||||
rocksdb_backupable_db_options_t* options, uint64_t size) {
|
||||
options->rep.callback_trigger_interval_size = size;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_backupable_db_options_get_callback_trigger_interval_size(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.callback_trigger_interval_size;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_max_valid_backups_to_open(
|
||||
rocksdb_backupable_db_options_t* options, int val) {
|
||||
options->rep.max_valid_backups_to_open = val;
|
||||
}
|
||||
|
||||
int rocksdb_backupable_db_options_get_max_valid_backups_to_open(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return options->rep.max_valid_backups_to_open;
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_set_share_files_with_checksum_naming(
|
||||
rocksdb_backupable_db_options_t* options, int val) {
|
||||
options->rep.share_files_with_checksum_naming =
|
||||
static_cast<BackupableDBOptions::ShareFilesNaming>(val);
|
||||
}
|
||||
|
||||
int rocksdb_backupable_db_options_get_share_files_with_checksum_naming(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
return static_cast<int>(options->rep.share_files_with_checksum_naming);
|
||||
}
|
||||
|
||||
void rocksdb_backupable_db_options_destroy(
|
||||
rocksdb_backupable_db_options_t* options) {
|
||||
delete options;
|
||||
}
|
||||
|
||||
rocksdb_checkpoint_t* rocksdb_checkpoint_object_create(rocksdb_t* db,
|
||||
char** errptr) {
|
||||
Checkpoint* checkpoint;
|
||||
@@ -1255,34 +1399,39 @@ char* rocksdb_property_value_cf(
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_approximate_sizes(
|
||||
rocksdb_t* db,
|
||||
int num_ranges,
|
||||
const char* const* range_start_key, const size_t* range_start_key_len,
|
||||
const char* const* range_limit_key, const size_t* range_limit_key_len,
|
||||
uint64_t* sizes) {
|
||||
void rocksdb_approximate_sizes(rocksdb_t* db, int num_ranges,
|
||||
const char* const* range_start_key,
|
||||
const size_t* range_start_key_len,
|
||||
const char* const* range_limit_key,
|
||||
const size_t* range_limit_key_len,
|
||||
uint64_t* sizes, char** errptr) {
|
||||
Range* ranges = new Range[num_ranges];
|
||||
for (int i = 0; i < num_ranges; i++) {
|
||||
ranges[i].start = Slice(range_start_key[i], range_start_key_len[i]);
|
||||
ranges[i].limit = Slice(range_limit_key[i], range_limit_key_len[i]);
|
||||
}
|
||||
db->rep->GetApproximateSizes(ranges, num_ranges, sizes);
|
||||
Status s = db->rep->GetApproximateSizes(ranges, num_ranges, sizes);
|
||||
if (!s.ok()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
delete[] ranges;
|
||||
}
|
||||
|
||||
void rocksdb_approximate_sizes_cf(
|
||||
rocksdb_t* db,
|
||||
rocksdb_column_family_handle_t* column_family,
|
||||
int num_ranges,
|
||||
const char* const* range_start_key, const size_t* range_start_key_len,
|
||||
const char* const* range_limit_key, const size_t* range_limit_key_len,
|
||||
uint64_t* sizes) {
|
||||
rocksdb_t* db, rocksdb_column_family_handle_t* column_family,
|
||||
int num_ranges, const char* const* range_start_key,
|
||||
const size_t* range_start_key_len, const char* const* range_limit_key,
|
||||
const size_t* range_limit_key_len, uint64_t* sizes, char** errptr) {
|
||||
Range* ranges = new Range[num_ranges];
|
||||
for (int i = 0; i < num_ranges; i++) {
|
||||
ranges[i].start = Slice(range_start_key[i], range_start_key_len[i]);
|
||||
ranges[i].limit = Slice(range_limit_key[i], range_limit_key_len[i]);
|
||||
}
|
||||
db->rep->GetApproximateSizes(column_family->rep, ranges, num_ranges, sizes);
|
||||
Status s = db->rep->GetApproximateSizes(column_family->rep, ranges,
|
||||
num_ranges, sizes);
|
||||
if (!s.ok()) {
|
||||
SaveError(errptr, s);
|
||||
}
|
||||
delete[] ranges;
|
||||
}
|
||||
|
||||
@@ -1363,6 +1512,10 @@ void rocksdb_flush_cf(
|
||||
SaveError(errptr, db->rep->Flush(options->rep, column_family->rep));
|
||||
}
|
||||
|
||||
void rocksdb_flush_wal(rocksdb_t* db, unsigned char sync, char** errptr) {
|
||||
SaveError(errptr, db->rep->FlushWAL(sync));
|
||||
}
|
||||
|
||||
void rocksdb_disable_file_deletions(
|
||||
rocksdb_t* db,
|
||||
char** errptr) {
|
||||
@@ -2544,6 +2697,59 @@ unsigned char rocksdb_options_get_skip_checking_sst_file_sizes_on_db_open(
|
||||
return opt->rep.skip_checking_sst_file_sizes_on_db_open;
|
||||
}
|
||||
|
||||
/* Blob Options Settings */
|
||||
void rocksdb_options_set_enable_blob_files(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.enable_blob_files = val;
|
||||
}
|
||||
extern ROCKSDB_LIBRARY_API unsigned char rocksdb_options_get_enable_blob_files(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.enable_blob_files;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_min_blob_size(rocksdb_options_t* opt, uint64_t val) {
|
||||
opt->rep.min_blob_size = val;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_get_min_blob_size(rocksdb_options_t* opt) {
|
||||
return opt->rep.min_blob_size;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_file_size(rocksdb_options_t* opt, uint64_t val) {
|
||||
opt->rep.blob_file_size = val;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_get_blob_file_size(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_file_size;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_compression_type(rocksdb_options_t* opt,
|
||||
int val) {
|
||||
opt->rep.blob_compression_type = static_cast<CompressionType>(val);
|
||||
}
|
||||
|
||||
int rocksdb_options_get_blob_compression_type(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_compression_type;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_enable_blob_gc(rocksdb_options_t* opt,
|
||||
unsigned char val) {
|
||||
opt->rep.enable_blob_garbage_collection = val;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_options_get_enable_blob_gc(rocksdb_options_t* opt) {
|
||||
return opt->rep.enable_blob_garbage_collection;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_blob_gc_age_cutoff(rocksdb_options_t* opt,
|
||||
double val) {
|
||||
opt->rep.blob_garbage_collection_age_cutoff = val;
|
||||
}
|
||||
|
||||
double rocksdb_options_get_blob_gc_age_cutoff(rocksdb_options_t* opt) {
|
||||
return opt->rep.blob_garbage_collection_age_cutoff;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_num_levels(rocksdb_options_t* opt, int n) {
|
||||
opt->rep.num_levels = n;
|
||||
}
|
||||
@@ -2636,6 +2842,14 @@ void rocksdb_options_set_bottommost_compression_options_zstd_max_train_bytes(
|
||||
opt->rep.bottommost_compression_opts.enabled = enabled;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_bottommost_compression_options_max_dict_buffer_bytes(
|
||||
rocksdb_options_t* opt, uint64_t max_dict_buffer_bytes,
|
||||
unsigned char enabled) {
|
||||
opt->rep.bottommost_compression_opts.max_dict_buffer_bytes =
|
||||
max_dict_buffer_bytes;
|
||||
opt->rep.bottommost_compression_opts.enabled = enabled;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compression_options(rocksdb_options_t* opt, int w_bits,
|
||||
int level, int strategy,
|
||||
int max_dict_bytes) {
|
||||
@@ -2650,6 +2864,31 @@ void rocksdb_options_set_compression_options_zstd_max_train_bytes(
|
||||
opt->rep.compression_opts.zstd_max_train_bytes = zstd_max_train_bytes;
|
||||
}
|
||||
|
||||
int rocksdb_options_get_compression_options_zstd_max_train_bytes(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.compression_opts.zstd_max_train_bytes;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compression_options_parallel_threads(
|
||||
rocksdb_options_t* opt, int value) {
|
||||
opt->rep.compression_opts.parallel_threads = value;
|
||||
}
|
||||
|
||||
int rocksdb_options_get_compression_options_parallel_threads(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.compression_opts.parallel_threads;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_compression_options_max_dict_buffer_bytes(
|
||||
rocksdb_options_t* opt, uint64_t max_dict_buffer_bytes) {
|
||||
opt->rep.compression_opts.max_dict_buffer_bytes = max_dict_buffer_bytes;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_options_get_compression_options_max_dict_buffer_bytes(
|
||||
rocksdb_options_t* opt) {
|
||||
return opt->rep.compression_opts.max_dict_buffer_bytes;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_prefix_extractor(
|
||||
rocksdb_options_t* opt, rocksdb_slicetransform_t* prefix_extractor) {
|
||||
opt->rep.prefix_extractor.reset(prefix_extractor);
|
||||
@@ -2808,6 +3047,8 @@ void rocksdb_options_set_access_hint_on_compaction_start(
|
||||
opt->rep.access_hint_on_compaction_start =
|
||||
ROCKSDB_NAMESPACE::Options::WILLNEED;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3262,6 +3503,15 @@ unsigned char rocksdb_options_get_atomic_flush(rocksdb_options_t* opt) {
|
||||
return opt->rep.atomic_flush;
|
||||
}
|
||||
|
||||
void rocksdb_options_set_manual_wal_flush(rocksdb_options_t* opt,
|
||||
unsigned char manual_wal_flush) {
|
||||
opt->rep.manual_wal_flush = manual_wal_flush;
|
||||
}
|
||||
|
||||
unsigned char rocksdb_options_get_manual_wal_flush(rocksdb_options_t* opt) {
|
||||
return opt->rep.manual_wal_flush;
|
||||
}
|
||||
|
||||
rocksdb_ratelimiter_t* rocksdb_ratelimiter_create(
|
||||
int64_t rate_bytes_per_sec,
|
||||
int64_t refill_period_us,
|
||||
@@ -3283,6 +3533,14 @@ void rocksdb_options_set_row_cache(rocksdb_options_t* opt, rocksdb_cache_t* cach
|
||||
}
|
||||
}
|
||||
|
||||
void rocksdb_options_add_compact_on_deletion_collector_factory(
|
||||
rocksdb_options_t* opt, size_t window_size, size_t num_dels_trigger) {
|
||||
std::shared_ptr<ROCKSDB_NAMESPACE::TablePropertiesCollectorFactory>
|
||||
compact_on_del =
|
||||
NewCompactOnDeletionCollectorFactory(window_size, num_dels_trigger);
|
||||
opt->rep.table_properties_collector_factories.emplace_back(compact_on_del);
|
||||
}
|
||||
|
||||
void rocksdb_set_perf_level(int v) {
|
||||
PerfLevel level = static_cast<PerfLevel>(v);
|
||||
SetPerfLevel(level);
|
||||
@@ -3798,6 +4056,25 @@ unsigned char rocksdb_readoptions_get_ignore_range_deletions(
|
||||
return opt->rep.ignore_range_deletions;
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_deadline(rocksdb_readoptions_t* opt,
|
||||
uint64_t microseconds) {
|
||||
opt->rep.deadline = std::chrono::microseconds(microseconds);
|
||||
}
|
||||
|
||||
uint64_t rocksdb_readoptions_get_deadline(rocksdb_readoptions_t* opt) {
|
||||
return opt->rep.deadline.count();
|
||||
}
|
||||
|
||||
void rocksdb_readoptions_set_io_timeout(rocksdb_readoptions_t* opt,
|
||||
uint64_t microseconds) {
|
||||
opt->rep.io_timeout = std::chrono::microseconds(microseconds);
|
||||
}
|
||||
|
||||
extern ROCKSDB_LIBRARY_API uint64_t
|
||||
rocksdb_readoptions_get_io_timeout(rocksdb_readoptions_t* opt) {
|
||||
return opt->rep.io_timeout.count();
|
||||
}
|
||||
|
||||
rocksdb_writeoptions_t* rocksdb_writeoptions_create() {
|
||||
return new rocksdb_writeoptions_t;
|
||||
}
|
||||
@@ -3930,16 +4207,58 @@ unsigned char rocksdb_flushoptions_get_wait(rocksdb_flushoptions_t* opt) {
|
||||
return opt->rep.wait;
|
||||
}
|
||||
|
||||
rocksdb_memory_allocator_t* rocksdb_jemalloc_nodump_allocator_create(
|
||||
char** errptr) {
|
||||
rocksdb_memory_allocator_t* allocator = new rocksdb_memory_allocator_t;
|
||||
ROCKSDB_NAMESPACE::JemallocAllocatorOptions options;
|
||||
SaveError(errptr, ROCKSDB_NAMESPACE::NewJemallocNodumpAllocator(
|
||||
options, &allocator->rep));
|
||||
return allocator;
|
||||
}
|
||||
|
||||
void rocksdb_memory_allocator_destroy(rocksdb_memory_allocator_t* allocator) {
|
||||
delete allocator;
|
||||
}
|
||||
|
||||
rocksdb_lru_cache_options_t* rocksdb_lru_cache_options_create() {
|
||||
return new rocksdb_lru_cache_options_t;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_destroy(rocksdb_lru_cache_options_t* opt) {
|
||||
delete opt;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_set_capacity(rocksdb_lru_cache_options_t* opt,
|
||||
size_t capacity) {
|
||||
opt->rep.capacity = capacity;
|
||||
}
|
||||
|
||||
void rocksdb_lru_cache_options_set_memory_allocator(
|
||||
rocksdb_lru_cache_options_t* opt, rocksdb_memory_allocator_t* allocator) {
|
||||
opt->rep.memory_allocator = allocator->rep;
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_lru(size_t capacity) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = NewLRUCache(capacity);
|
||||
return c;
|
||||
}
|
||||
|
||||
rocksdb_cache_t* rocksdb_cache_create_lru_opts(
|
||||
rocksdb_lru_cache_options_t* opt) {
|
||||
rocksdb_cache_t* c = new rocksdb_cache_t;
|
||||
c->rep = NewLRUCache(opt->rep);
|
||||
return c;
|
||||
}
|
||||
|
||||
void rocksdb_cache_destroy(rocksdb_cache_t* cache) {
|
||||
delete cache;
|
||||
}
|
||||
|
||||
void rocksdb_cache_disown_data(rocksdb_cache_t* cache) {
|
||||
cache->rep->DisownData();
|
||||
}
|
||||
|
||||
void rocksdb_cache_set_capacity(rocksdb_cache_t* cache, size_t capacity) {
|
||||
cache->rep->SetCapacity(capacity);
|
||||
}
|
||||
@@ -3985,20 +4304,36 @@ void rocksdb_env_set_background_threads(rocksdb_env_t* env, int n) {
|
||||
env->rep->SetBackgroundThreads(n);
|
||||
}
|
||||
|
||||
int rocksdb_env_get_background_threads(rocksdb_env_t* env) {
|
||||
return env->rep->GetBackgroundThreads();
|
||||
}
|
||||
|
||||
void rocksdb_env_set_bottom_priority_background_threads(rocksdb_env_t* env,
|
||||
int n) {
|
||||
env->rep->SetBackgroundThreads(n, Env::BOTTOM);
|
||||
}
|
||||
|
||||
int rocksdb_env_get_bottom_priority_background_threads(rocksdb_env_t* env) {
|
||||
return env->rep->GetBackgroundThreads(Env::BOTTOM);
|
||||
}
|
||||
|
||||
void rocksdb_env_set_high_priority_background_threads(rocksdb_env_t* env, int n) {
|
||||
env->rep->SetBackgroundThreads(n, Env::HIGH);
|
||||
}
|
||||
|
||||
int rocksdb_env_get_high_priority_background_threads(rocksdb_env_t* env) {
|
||||
return env->rep->GetBackgroundThreads(Env::HIGH);
|
||||
}
|
||||
|
||||
void rocksdb_env_set_low_priority_background_threads(rocksdb_env_t* env,
|
||||
int n) {
|
||||
env->rep->SetBackgroundThreads(n, Env::LOW);
|
||||
}
|
||||
|
||||
int rocksdb_env_get_low_priority_background_threads(rocksdb_env_t* env) {
|
||||
return env->rep->GetBackgroundThreads(Env::LOW);
|
||||
}
|
||||
|
||||
void rocksdb_env_join_all_threads(rocksdb_env_t* env) {
|
||||
env->rep->WaitForJoin();
|
||||
}
|
||||
@@ -4226,32 +4561,62 @@ void rocksdb_universal_compaction_options_set_size_ratio(
|
||||
uco->rep->size_ratio = ratio;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_size_ratio(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->size_ratio;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_min_merge_width(
|
||||
rocksdb_universal_compaction_options_t* uco, int w) {
|
||||
uco->rep->min_merge_width = w;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_min_merge_width(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->min_merge_width;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_max_merge_width(
|
||||
rocksdb_universal_compaction_options_t* uco, int w) {
|
||||
uco->rep->max_merge_width = w;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_max_merge_width(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->max_merge_width;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_max_size_amplification_percent(
|
||||
rocksdb_universal_compaction_options_t* uco, int p) {
|
||||
uco->rep->max_size_amplification_percent = p;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_max_size_amplification_percent(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->max_size_amplification_percent;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_compression_size_percent(
|
||||
rocksdb_universal_compaction_options_t* uco, int p) {
|
||||
uco->rep->compression_size_percent = p;
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_compression_size_percent(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return uco->rep->compression_size_percent;
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_set_stop_style(
|
||||
rocksdb_universal_compaction_options_t* uco, int style) {
|
||||
uco->rep->stop_style =
|
||||
static_cast<ROCKSDB_NAMESPACE::CompactionStopStyle>(style);
|
||||
}
|
||||
|
||||
int rocksdb_universal_compaction_options_get_stop_style(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
return static_cast<int>(uco->rep->stop_style);
|
||||
}
|
||||
|
||||
void rocksdb_universal_compaction_options_destroy(
|
||||
rocksdb_universal_compaction_options_t* uco) {
|
||||
delete uco->rep;
|
||||
@@ -4269,6 +4634,11 @@ void rocksdb_fifo_compaction_options_set_max_table_files_size(
|
||||
fifo_opts->rep.max_table_files_size = size;
|
||||
}
|
||||
|
||||
uint64_t rocksdb_fifo_compaction_options_get_max_table_files_size(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts) {
|
||||
return fifo_opts->rep.max_table_files_size;
|
||||
}
|
||||
|
||||
void rocksdb_fifo_compaction_options_destroy(
|
||||
rocksdb_fifo_compaction_options_t* fifo_opts) {
|
||||
delete fifo_opts;
|
||||
@@ -4567,7 +4937,10 @@ void rocksdb_transaction_destroy(rocksdb_transaction_t* txn) {
|
||||
|
||||
const rocksdb_snapshot_t* rocksdb_transaction_get_snapshot(
|
||||
rocksdb_transaction_t* txn) {
|
||||
rocksdb_snapshot_t* result = new rocksdb_snapshot_t;
|
||||
// This will be freed later on using free, so use malloc here to avoid a
|
||||
// mismatch
|
||||
rocksdb_snapshot_t* result =
|
||||
(rocksdb_snapshot_t*)malloc(sizeof(rocksdb_snapshot_t));
|
||||
result->rep = txn->rep->GetSnapshot();
|
||||
return result;
|
||||
}
|
||||
|
||||
+211
-5
@@ -517,6 +517,9 @@ int main(int argc, char** argv) {
|
||||
coptions = rocksdb_compactoptions_create();
|
||||
rocksdb_compactoptions_set_exclusive_manual_compaction(coptions, 1);
|
||||
|
||||
rocksdb_options_add_compact_on_deletion_collector_factory(options, 10000,
|
||||
10001);
|
||||
|
||||
StartPhase("destroy");
|
||||
rocksdb_destroy_db(options, dbname, &err);
|
||||
Free(&err);
|
||||
@@ -988,7 +991,9 @@ int main(int argc, char** argv) {
|
||||
&err);
|
||||
CheckNoError(err);
|
||||
}
|
||||
rocksdb_approximate_sizes(db, 2, start, start_len, limit, limit_len, sizes);
|
||||
rocksdb_approximate_sizes(db, 2, start, start_len, limit, limit_len, sizes,
|
||||
&err);
|
||||
CheckNoError(err);
|
||||
CheckCondition(sizes[0] > 0);
|
||||
CheckCondition(sizes[1] > 0);
|
||||
}
|
||||
@@ -1117,9 +1122,8 @@ int main(int argc, char** argv) {
|
||||
// Essentially a fingerprint of the block-based Bloom schema
|
||||
CheckCondition(hits == 241);
|
||||
} else {
|
||||
// Essentially a fingerprint of the full Bloom schema(s),
|
||||
// format_version < 5, which vary for three different CACHE_LINE_SIZEs
|
||||
CheckCondition(hits == 224 || hits == 180 || hits == 125);
|
||||
// Essentially a fingerprint of full Bloom schema, format_version=5
|
||||
CheckCondition(hits == 188);
|
||||
}
|
||||
CheckCondition(
|
||||
(keys_to_query - hits) ==
|
||||
@@ -1275,6 +1279,9 @@ int main(int argc, char** argv) {
|
||||
CheckPinGetCF(db, roptions, handles[1], "box", "c");
|
||||
rocksdb_writebatch_destroy(wb);
|
||||
|
||||
rocksdb_flush_wal(db, 1, &err);
|
||||
CheckNoError(err);
|
||||
|
||||
const char* keys[3] = { "box", "box", "barfooxx" };
|
||||
const rocksdb_column_family_handle_t* get_handles[3] = { handles[0], handles[1], handles[1] };
|
||||
const size_t keys_sizes[3] = { 3, 3, 8 };
|
||||
@@ -1758,6 +1765,28 @@ int main(int argc, char** argv) {
|
||||
rocksdb_options_set_atomic_flush(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_atomic_flush(o));
|
||||
|
||||
rocksdb_options_set_manual_wal_flush(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_manual_wal_flush(o));
|
||||
|
||||
/* Blob Options */
|
||||
rocksdb_options_set_enable_blob_files(o, 1);
|
||||
CheckCondition(1 == rocksdb_options_get_enable_blob_files(o));
|
||||
|
||||
rocksdb_options_set_min_blob_size(o, 29);
|
||||
CheckCondition(29 == rocksdb_options_get_min_blob_size(o));
|
||||
|
||||
rocksdb_options_set_blob_file_size(o, 30);
|
||||
CheckCondition(30 == rocksdb_options_get_blob_file_size(o));
|
||||
|
||||
rocksdb_options_set_blob_compression_type(o, 4);
|
||||
CheckCondition(4 == rocksdb_options_get_blob_compression_type(o));
|
||||
|
||||
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));
|
||||
|
||||
// Create a copy that should be equal to the original.
|
||||
rocksdb_options_t* copy;
|
||||
copy = rocksdb_options_create_copy(o);
|
||||
@@ -2279,6 +2308,12 @@ int main(int argc, char** argv) {
|
||||
rocksdb_readoptions_set_ignore_range_deletions(ro, 1);
|
||||
CheckCondition(1 == rocksdb_readoptions_get_ignore_range_deletions(ro));
|
||||
|
||||
rocksdb_readoptions_set_deadline(ro, 300);
|
||||
CheckCondition(300 == rocksdb_readoptions_get_deadline(ro));
|
||||
|
||||
rocksdb_readoptions_set_io_timeout(ro, 400);
|
||||
CheckCondition(400 == rocksdb_readoptions_get_io_timeout(ro));
|
||||
|
||||
rocksdb_readoptions_destroy(ro);
|
||||
}
|
||||
|
||||
@@ -2355,6 +2390,177 @@ int main(int argc, char** argv) {
|
||||
rocksdb_cache_destroy(co);
|
||||
}
|
||||
|
||||
StartPhase("jemalloc_nodump_allocator");
|
||||
{
|
||||
rocksdb_memory_allocator_t* allocator;
|
||||
allocator = rocksdb_jemalloc_nodump_allocator_create(&err);
|
||||
if (err != NULL) {
|
||||
// not supported on all platforms, allow unsupported error
|
||||
const char* ni = "Not implemented: ";
|
||||
size_t ni_len = strlen(ni);
|
||||
size_t err_len = strlen(err);
|
||||
|
||||
CheckCondition(err_len >= ni_len);
|
||||
CheckCondition(memcmp(ni, err, ni_len) == 0);
|
||||
Free(&err);
|
||||
} else {
|
||||
rocksdb_cache_t* co;
|
||||
rocksdb_lru_cache_options_t* copts;
|
||||
|
||||
copts = rocksdb_lru_cache_options_create();
|
||||
|
||||
rocksdb_lru_cache_options_set_capacity(copts, 100);
|
||||
rocksdb_lru_cache_options_set_memory_allocator(copts, allocator);
|
||||
|
||||
co = rocksdb_cache_create_lru_opts(copts);
|
||||
CheckCondition(100 == rocksdb_cache_get_capacity(co));
|
||||
|
||||
rocksdb_cache_destroy(co);
|
||||
rocksdb_lru_cache_options_destroy(copts);
|
||||
}
|
||||
rocksdb_memory_allocator_destroy(allocator);
|
||||
}
|
||||
|
||||
StartPhase("env");
|
||||
{
|
||||
rocksdb_env_t* e;
|
||||
e = rocksdb_create_default_env();
|
||||
|
||||
rocksdb_env_set_background_threads(e, 10);
|
||||
CheckCondition(10 == rocksdb_env_get_background_threads(e));
|
||||
|
||||
rocksdb_env_set_high_priority_background_threads(e, 20);
|
||||
CheckCondition(20 == rocksdb_env_get_high_priority_background_threads(e));
|
||||
|
||||
rocksdb_env_set_low_priority_background_threads(e, 30);
|
||||
CheckCondition(30 == rocksdb_env_get_low_priority_background_threads(e));
|
||||
|
||||
rocksdb_env_set_bottom_priority_background_threads(e, 40);
|
||||
CheckCondition(40 == rocksdb_env_get_bottom_priority_background_threads(e));
|
||||
|
||||
rocksdb_env_destroy(e);
|
||||
}
|
||||
|
||||
StartPhase("universal_compaction_options");
|
||||
{
|
||||
rocksdb_universal_compaction_options_t* uco;
|
||||
uco = rocksdb_universal_compaction_options_create();
|
||||
|
||||
rocksdb_universal_compaction_options_set_size_ratio(uco, 5);
|
||||
CheckCondition(5 ==
|
||||
rocksdb_universal_compaction_options_get_size_ratio(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_min_merge_width(uco, 15);
|
||||
CheckCondition(
|
||||
15 == rocksdb_universal_compaction_options_get_min_merge_width(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_max_merge_width(uco, 25);
|
||||
CheckCondition(
|
||||
25 == rocksdb_universal_compaction_options_get_max_merge_width(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_max_size_amplification_percent(uco,
|
||||
35);
|
||||
CheckCondition(
|
||||
35 ==
|
||||
rocksdb_universal_compaction_options_get_max_size_amplification_percent(
|
||||
uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_compression_size_percent(uco, 45);
|
||||
CheckCondition(
|
||||
45 ==
|
||||
rocksdb_universal_compaction_options_get_compression_size_percent(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_set_stop_style(uco, 1);
|
||||
CheckCondition(1 ==
|
||||
rocksdb_universal_compaction_options_get_stop_style(uco));
|
||||
|
||||
rocksdb_universal_compaction_options_destroy(uco);
|
||||
}
|
||||
|
||||
StartPhase("fifo_compaction_options");
|
||||
{
|
||||
rocksdb_fifo_compaction_options_t* fco;
|
||||
fco = rocksdb_fifo_compaction_options_create();
|
||||
|
||||
rocksdb_fifo_compaction_options_set_max_table_files_size(fco, 100000);
|
||||
CheckCondition(
|
||||
100000 ==
|
||||
rocksdb_fifo_compaction_options_get_max_table_files_size(fco));
|
||||
|
||||
rocksdb_fifo_compaction_options_destroy(fco);
|
||||
}
|
||||
|
||||
StartPhase("backupable_db_option");
|
||||
{
|
||||
rocksdb_backupable_db_options_t* bdo;
|
||||
bdo = rocksdb_backupable_db_options_create("path");
|
||||
|
||||
rocksdb_backupable_db_options_set_share_table_files(bdo, 1);
|
||||
CheckCondition(1 ==
|
||||
rocksdb_backupable_db_options_get_share_table_files(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_sync(bdo, 1);
|
||||
CheckCondition(1 == rocksdb_backupable_db_options_get_sync(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_destroy_old_data(bdo, 1);
|
||||
CheckCondition(1 ==
|
||||
rocksdb_backupable_db_options_get_destroy_old_data(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_backup_log_files(bdo, 1);
|
||||
CheckCondition(1 ==
|
||||
rocksdb_backupable_db_options_get_backup_log_files(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_backup_rate_limit(bdo, 123);
|
||||
CheckCondition(123 ==
|
||||
rocksdb_backupable_db_options_get_backup_rate_limit(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_restore_rate_limit(bdo, 37);
|
||||
CheckCondition(37 ==
|
||||
rocksdb_backupable_db_options_get_restore_rate_limit(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_max_background_operations(bdo, 20);
|
||||
CheckCondition(
|
||||
20 == rocksdb_backupable_db_options_get_max_background_operations(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_callback_trigger_interval_size(bdo, 9000);
|
||||
CheckCondition(
|
||||
9000 ==
|
||||
rocksdb_backupable_db_options_get_callback_trigger_interval_size(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_max_valid_backups_to_open(bdo, 40);
|
||||
CheckCondition(
|
||||
40 == rocksdb_backupable_db_options_get_max_valid_backups_to_open(bdo));
|
||||
|
||||
rocksdb_backupable_db_options_set_share_files_with_checksum_naming(bdo, 2);
|
||||
CheckCondition(
|
||||
2 == rocksdb_backupable_db_options_get_share_files_with_checksum_naming(
|
||||
bdo));
|
||||
|
||||
rocksdb_backupable_db_options_destroy(bdo);
|
||||
}
|
||||
|
||||
StartPhase("compression_options");
|
||||
{
|
||||
rocksdb_options_t* co;
|
||||
co = rocksdb_options_create();
|
||||
|
||||
rocksdb_options_set_compression_options_zstd_max_train_bytes(co, 100);
|
||||
CheckCondition(
|
||||
100 ==
|
||||
rocksdb_options_get_compression_options_zstd_max_train_bytes(co));
|
||||
|
||||
rocksdb_options_set_compression_options_parallel_threads(co, 2);
|
||||
CheckCondition(
|
||||
2 == rocksdb_options_get_compression_options_parallel_threads(co));
|
||||
|
||||
rocksdb_options_set_compression_options_max_dict_buffer_bytes(co, 200);
|
||||
CheckCondition(
|
||||
200 ==
|
||||
rocksdb_options_get_compression_options_max_dict_buffer_bytes(co));
|
||||
|
||||
rocksdb_options_destroy(co);
|
||||
}
|
||||
|
||||
StartPhase("iterate_upper_bound");
|
||||
{
|
||||
// Create new empty database
|
||||
@@ -2755,7 +2961,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
#else
|
||||
|
||||
int main() {
|
||||
int main(void) {
|
||||
fprintf(stderr, "SKIPPED\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
+94
-60
@@ -16,6 +16,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_cache.h"
|
||||
#include "db/compaction/compaction_picker.h"
|
||||
#include "db/compaction/compaction_picker_fifo.h"
|
||||
#include "db/compaction/compaction_picker_level.h"
|
||||
@@ -32,6 +33,7 @@
|
||||
#include "monitoring/thread_status_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/port.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/table.h"
|
||||
#include "table/merging_iterator.h"
|
||||
#include "util/autovector.h"
|
||||
@@ -107,8 +109,9 @@ const Comparator* ColumnFamilyHandleImpl::GetComparator() const {
|
||||
|
||||
void GetIntTblPropCollectorFactory(
|
||||
const ImmutableCFOptions& ioptions,
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories) {
|
||||
IntTblPropCollectorFactories* int_tbl_prop_collector_factories) {
|
||||
assert(int_tbl_prop_collector_factories);
|
||||
|
||||
auto& collector_factories = ioptions.table_properties_collector_factories;
|
||||
for (size_t i = 0; i < ioptions.table_properties_collector_factories.size();
|
||||
++i) {
|
||||
@@ -212,7 +215,8 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
// if user sets arena_block_size, we trust user to use this value. Otherwise,
|
||||
// calculate a proper value from writer_buffer_size;
|
||||
if (result.arena_block_size <= 0) {
|
||||
result.arena_block_size = result.write_buffer_size / 8;
|
||||
result.arena_block_size =
|
||||
std::min(size_t{1024 * 1024}, result.write_buffer_size / 8);
|
||||
|
||||
// Align up to 4k
|
||||
const size_t align = 4 * 1024;
|
||||
@@ -281,7 +285,7 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
}
|
||||
|
||||
if (result.level0_file_num_compaction_trigger == 0) {
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
ROCKS_LOG_WARN(db_options.logger,
|
||||
"level0_file_num_compaction_trigger cannot be 0");
|
||||
result.level0_file_num_compaction_trigger = 1;
|
||||
}
|
||||
@@ -290,7 +294,7 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
result.level0_slowdown_writes_trigger ||
|
||||
result.level0_slowdown_writes_trigger <
|
||||
result.level0_file_num_compaction_trigger) {
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
ROCKS_LOG_WARN(db_options.logger,
|
||||
"This condition must be satisfied: "
|
||||
"level0_stop_writes_trigger(%d) >= "
|
||||
"level0_slowdown_writes_trigger(%d) >= "
|
||||
@@ -307,7 +311,7 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
result.level0_slowdown_writes_trigger) {
|
||||
result.level0_stop_writes_trigger = result.level0_slowdown_writes_trigger;
|
||||
}
|
||||
ROCKS_LOG_WARN(db_options.info_log.get(),
|
||||
ROCKS_LOG_WARN(db_options.logger,
|
||||
"Adjust the value to "
|
||||
"level0_stop_writes_trigger(%d)"
|
||||
"level0_slowdown_writes_trigger(%d)"
|
||||
@@ -334,7 +338,9 @@ ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
// was not used)
|
||||
auto sfm = static_cast<SstFileManagerImpl*>(db_options.sst_file_manager.get());
|
||||
for (size_t i = 0; i < result.cf_paths.size(); i++) {
|
||||
DeleteScheduler::CleanupDirectory(db_options.env, sfm, result.cf_paths[i].path);
|
||||
DeleteScheduler::CleanupDirectory(db_options.env, sfm,
|
||||
result.cf_paths[i].path)
|
||||
.PermitUncheckedError();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -448,9 +454,7 @@ void SuperVersion::Cleanup() {
|
||||
to_delete.push_back(m);
|
||||
}
|
||||
current->Unref();
|
||||
if (cfd->Unref()) {
|
||||
delete cfd;
|
||||
}
|
||||
cfd->UnrefAndTryDelete(this);
|
||||
}
|
||||
|
||||
void SuperVersion::Init(ColumnFamilyData* new_cfd, MemTable* new_mem,
|
||||
@@ -500,7 +504,8 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
const ColumnFamilyOptions& cf_options, const ImmutableDBOptions& db_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)
|
||||
: id_(id),
|
||||
name_(name),
|
||||
dummy_versions_(_dummy_versions),
|
||||
@@ -543,7 +548,7 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
db_paths_registered_ = true;
|
||||
} else {
|
||||
ROCKS_LOG_ERROR(
|
||||
ioptions_.info_log,
|
||||
ioptions_.logger,
|
||||
"Failed to register data paths of column family (id: %d, name: %s)",
|
||||
id_, name_.c_str());
|
||||
}
|
||||
@@ -556,9 +561,14 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
// if _dummy_versions is nullptr, then this is a dummy column family.
|
||||
if (_dummy_versions != nullptr) {
|
||||
internal_stats_.reset(
|
||||
new InternalStats(ioptions_.num_levels, db_options.env, this));
|
||||
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));
|
||||
|
||||
if (ioptions_.compaction_style == kCompactionStyleLevel) {
|
||||
compaction_picker_.reset(
|
||||
new LevelCompactionPicker(ioptions_, &internal_comparator_));
|
||||
@@ -572,13 +582,13 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
} else if (ioptions_.compaction_style == kCompactionStyleNone) {
|
||||
compaction_picker_.reset(new NullCompactionPicker(
|
||||
ioptions_, &internal_comparator_));
|
||||
ROCKS_LOG_WARN(ioptions_.info_log,
|
||||
ROCKS_LOG_WARN(ioptions_.logger,
|
||||
"Column family %s does not use any background compaction. "
|
||||
"Compactions can only be done via CompactFiles\n",
|
||||
GetName().c_str());
|
||||
#endif // !ROCKSDB_LITE
|
||||
} else {
|
||||
ROCKS_LOG_ERROR(ioptions_.info_log,
|
||||
ROCKS_LOG_ERROR(ioptions_.logger,
|
||||
"Unable to recognize the specified compaction style %d. "
|
||||
"Column family %s will use kCompactionStyleLevel.\n",
|
||||
ioptions_.compaction_style, GetName().c_str());
|
||||
@@ -587,12 +597,12 @@ ColumnFamilyData::ColumnFamilyData(
|
||||
}
|
||||
|
||||
if (column_family_set_->NumberOfColumnFamilies() < 10) {
|
||||
ROCKS_LOG_INFO(ioptions_.info_log,
|
||||
ROCKS_LOG_INFO(ioptions_.logger,
|
||||
"--------------- Options for column family [%s]:\n",
|
||||
name.c_str());
|
||||
initial_cf_options_.Dump(ioptions_.info_log);
|
||||
initial_cf_options_.Dump(ioptions_.logger);
|
||||
} else {
|
||||
ROCKS_LOG_INFO(ioptions_.info_log, "\t(skipping printing options)\n");
|
||||
ROCKS_LOG_INFO(ioptions_.logger, "\t(skipping printing options)\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +637,7 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
|
||||
if (dummy_versions_ != nullptr) {
|
||||
// List must be empty
|
||||
assert(dummy_versions_->TEST_Next() == dummy_versions_);
|
||||
assert(dummy_versions_->Next() == dummy_versions_);
|
||||
bool deleted __attribute__((__unused__));
|
||||
deleted = dummy_versions_->Unref();
|
||||
assert(deleted);
|
||||
@@ -648,14 +658,14 @@ ColumnFamilyData::~ColumnFamilyData() {
|
||||
Status s = ioptions_.env->UnregisterDbPaths(GetDbPaths());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_ERROR(
|
||||
ioptions_.info_log,
|
||||
ioptions_.logger,
|
||||
"Failed to unregister data paths of column family (id: %d, name: %s)",
|
||||
id_, name_.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ColumnFamilyData::UnrefAndTryDelete() {
|
||||
bool ColumnFamilyData::UnrefAndTryDelete(SuperVersion* sv_under_cleanup) {
|
||||
int old_refs = refs_.fetch_sub(1);
|
||||
assert(old_refs > 0);
|
||||
|
||||
@@ -665,7 +675,11 @@ bool ColumnFamilyData::UnrefAndTryDelete() {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (old_refs == 2 && super_version_ != nullptr) {
|
||||
// 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) {
|
||||
// Only the super_version_ holds me
|
||||
SuperVersion* sv = super_version_;
|
||||
super_version_ = nullptr;
|
||||
@@ -703,9 +717,7 @@ uint64_t ColumnFamilyData::OldestLogToKeep() {
|
||||
auto current_log = GetLogNumber();
|
||||
|
||||
if (allow_2pc_) {
|
||||
autovector<MemTable*> empty_list;
|
||||
auto imm_prep_log =
|
||||
imm()->PrecomputeMinLogContainingPrepSection(empty_list);
|
||||
auto imm_prep_log = imm()->PrecomputeMinLogContainingPrepSection();
|
||||
auto mem_prep_log = mem()->GetMinLogContainingPrepSection();
|
||||
|
||||
if (imm_prep_log > 0 && imm_prep_log < current_log) {
|
||||
@@ -827,7 +839,8 @@ std::pair<WriteStallCondition, ColumnFamilyData::WriteStallCause>
|
||||
ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
const MutableCFOptions& mutable_cf_options) {
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableCFOptions& immutable_cf_options) {
|
||||
if (num_unflushed_memtables >= mutable_cf_options.max_write_buffer_number) {
|
||||
return {WriteStallCondition::kStopped, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
@@ -841,7 +854,9 @@ ColumnFamilyData::GetWriteStallConditionAndCause(
|
||||
WriteStallCause::kPendingCompactionBytes};
|
||||
} else if (mutable_cf_options.max_write_buffer_number > 3 &&
|
||||
num_unflushed_memtables >=
|
||||
mutable_cf_options.max_write_buffer_number - 1) {
|
||||
mutable_cf_options.max_write_buffer_number - 1 &&
|
||||
num_unflushed_memtables - 1 >=
|
||||
immutable_cf_options.min_write_buffer_number_to_merge) {
|
||||
return {WriteStallCondition::kDelayed, WriteStallCause::kMemtableLimit};
|
||||
} else if (!mutable_cf_options.disable_auto_compactions &&
|
||||
mutable_cf_options.level0_slowdown_writes_trigger >= 0 &&
|
||||
@@ -869,7 +884,8 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
|
||||
auto write_stall_condition_and_cause = GetWriteStallConditionAndCause(
|
||||
imm()->NumNotFlushed(), vstorage->l0_delay_trigger_count(),
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options);
|
||||
vstorage->estimated_compaction_needed_bytes(), mutable_cf_options,
|
||||
*ioptions());
|
||||
write_stall_condition = write_stall_condition_and_cause.first;
|
||||
auto write_stall_cause = write_stall_condition_and_cause.second;
|
||||
|
||||
@@ -881,7 +897,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
write_controller_token_ = write_controller->GetStopToken();
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_STOPS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
ioptions_.logger,
|
||||
"[%s] Stopping writes because we have %d immutable memtables "
|
||||
"(waiting for flush), max_write_buffer_number is set to %d",
|
||||
name_.c_str(), imm()->NumNotFlushed(),
|
||||
@@ -894,7 +910,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_STOPS, 1);
|
||||
}
|
||||
ROCKS_LOG_WARN(ioptions_.info_log,
|
||||
ROCKS_LOG_WARN(ioptions_.logger,
|
||||
"[%s] Stopping writes because we have %d level-0 files",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
} else if (write_stall_condition == WriteStallCondition::kStopped &&
|
||||
@@ -903,7 +919,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_STOPS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
ioptions_.logger,
|
||||
"[%s] Stopping writes because of estimated pending compaction "
|
||||
"bytes %" PRIu64,
|
||||
name_.c_str(), compaction_needed_bytes);
|
||||
@@ -915,7 +931,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
mutable_cf_options.disable_auto_compactions);
|
||||
internal_stats_->AddCFStats(InternalStats::MEMTABLE_LIMIT_SLOWDOWNS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
ioptions_.logger,
|
||||
"[%s] Stalling writes because we have %d immutable memtables "
|
||||
"(waiting for flush), max_write_buffer_number is set to %d "
|
||||
"rate %" PRIu64,
|
||||
@@ -937,7 +953,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::LOCKED_L0_FILE_COUNT_LIMIT_SLOWDOWNS, 1);
|
||||
}
|
||||
ROCKS_LOG_WARN(ioptions_.info_log,
|
||||
ROCKS_LOG_WARN(ioptions_.logger,
|
||||
"[%s] Stalling writes because we have %d level-0 files "
|
||||
"rate %" PRIu64,
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count(),
|
||||
@@ -962,7 +978,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
internal_stats_->AddCFStats(
|
||||
InternalStats::PENDING_COMPACTION_BYTES_LIMIT_SLOWDOWNS, 1);
|
||||
ROCKS_LOG_WARN(
|
||||
ioptions_.info_log,
|
||||
ioptions_.logger,
|
||||
"[%s] Stalling writes because of estimated pending compaction "
|
||||
"bytes %" PRIu64 " rate %" PRIu64,
|
||||
name_.c_str(), vstorage->estimated_compaction_needed_bytes(),
|
||||
@@ -976,7 +992,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
write_controller_token_ =
|
||||
write_controller->GetCompactionPressureToken();
|
||||
ROCKS_LOG_INFO(
|
||||
ioptions_.info_log,
|
||||
ioptions_.logger,
|
||||
"[%s] Increasing compaction threads because we have %d level-0 "
|
||||
"files ",
|
||||
name_.c_str(), vstorage->l0_delay_trigger_count());
|
||||
@@ -990,7 +1006,7 @@ WriteStallCondition ColumnFamilyData::RecalculateWriteStallConditions(
|
||||
write_controller->GetCompactionPressureToken();
|
||||
if (mutable_cf_options.soft_pending_compaction_bytes_limit > 0) {
|
||||
ROCKS_LOG_INFO(
|
||||
ioptions_.info_log,
|
||||
ioptions_.logger,
|
||||
"[%s] Increasing compaction threads because of estimated pending "
|
||||
"compaction "
|
||||
"bytes %" PRIu64,
|
||||
@@ -1083,7 +1099,7 @@ bool ColumnFamilyData::RangeOverlapWithCompaction(
|
||||
|
||||
Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
const autovector<Range>& ranges, SuperVersion* super_version,
|
||||
bool* overlap) {
|
||||
bool allow_data_in_errors, bool* overlap) {
|
||||
assert(overlap != nullptr);
|
||||
*overlap = false;
|
||||
// Create an InternalIterator over all unflushed memtables
|
||||
@@ -1116,13 +1132,12 @@ Status ColumnFamilyData::RangesOverlapWithMemtables(
|
||||
memtable_iter->Seek(range_start.Encode());
|
||||
status = memtable_iter->status();
|
||||
ParsedInternalKey seek_result;
|
||||
if (status.ok()) {
|
||||
if (memtable_iter->Valid() &&
|
||||
ParseInternalKey(memtable_iter->key(), &seek_result) !=
|
||||
Status::OK()) {
|
||||
status = Status::Corruption("DB have corrupted keys");
|
||||
}
|
||||
|
||||
if (status.ok() && memtable_iter->Valid()) {
|
||||
status = ParseInternalKey(memtable_iter->key(), &seek_result,
|
||||
allow_data_in_errors);
|
||||
}
|
||||
|
||||
if (status.ok()) {
|
||||
if (memtable_iter->Valid() &&
|
||||
ucmp->Compare(seek_result.user_key, ranges[i].limit) <= 0) {
|
||||
@@ -1192,11 +1207,11 @@ SuperVersion* ColumnFamilyData::GetThreadLocalSuperVersion(DBImpl* db) {
|
||||
SuperVersion* sv = static_cast<SuperVersion*>(ptr);
|
||||
if (sv == SuperVersion::kSVObsolete ||
|
||||
sv->version_number != super_version_number_.load()) {
|
||||
RecordTick(ioptions_.statistics, NUMBER_SUPERVERSION_ACQUIRES);
|
||||
RecordTick(ioptions_.stats, NUMBER_SUPERVERSION_ACQUIRES);
|
||||
SuperVersion* sv_to_delete = nullptr;
|
||||
|
||||
if (sv && sv->Unref()) {
|
||||
RecordTick(ioptions_.statistics, NUMBER_SUPERVERSION_CLEANUPS);
|
||||
RecordTick(ioptions_.stats, NUMBER_SUPERVERSION_CLEANUPS);
|
||||
db->mutex()->Lock();
|
||||
// NOTE: underlying resources held by superversion (sst files) might
|
||||
// not be released until the next background job.
|
||||
@@ -1335,24 +1350,39 @@ Status ColumnFamilyData::ValidateOptions(
|
||||
"Block-Based Table format. ");
|
||||
}
|
||||
}
|
||||
|
||||
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.compaction_style == kCompactionStyleFIFO &&
|
||||
db_options.max_open_files != -1 && cf_options.ttl > 0) {
|
||||
return Status::NotSupported(
|
||||
"FIFO compaction only supported with max_open_files = -1.");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
Status ColumnFamilyData::SetOptions(
|
||||
const DBOptions& db_options,
|
||||
const DBOptions& db_opts,
|
||||
const std::unordered_map<std::string, std::string>& options_map) {
|
||||
MutableCFOptions new_mutable_cf_options;
|
||||
Status s =
|
||||
GetMutableOptionsFromStrings(mutable_cf_options_, options_map,
|
||||
ioptions_.info_log, &new_mutable_cf_options);
|
||||
ColumnFamilyOptions cf_opts =
|
||||
BuildColumnFamilyOptions(initial_cf_options_, mutable_cf_options_);
|
||||
ConfigOptions config_opts;
|
||||
config_opts.mutable_options_only = true;
|
||||
Status s = GetColumnFamilyOptionsFromMap(config_opts, cf_opts, options_map,
|
||||
&cf_opts);
|
||||
if (s.ok()) {
|
||||
ColumnFamilyOptions cf_options =
|
||||
BuildColumnFamilyOptions(initial_cf_options_, new_mutable_cf_options);
|
||||
s = ValidateOptions(db_options, cf_options);
|
||||
s = ValidateOptions(db_opts, cf_opts);
|
||||
}
|
||||
if (s.ok()) {
|
||||
mutable_cf_options_ = new_mutable_cf_options;
|
||||
mutable_cf_options_ = MutableCFOptions(cf_opts);
|
||||
mutable_cf_options_.RefreshDerivedOptions(ioptions_);
|
||||
}
|
||||
return s;
|
||||
@@ -1391,7 +1421,8 @@ Status ColumnFamilyData::AddDirectories(
|
||||
|
||||
if (existing_dir == created_dirs->end()) {
|
||||
std::unique_ptr<FSDirectory> path_directory;
|
||||
s = DBImpl::CreateAndNewDirectory(ioptions_.fs, p.path, &path_directory);
|
||||
s = DBImpl::CreateAndNewDirectory(ioptions_.fs.get(), p.path,
|
||||
&path_directory);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
@@ -1422,12 +1453,13 @@ 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),
|
||||
dummy_cfd_(new ColumnFamilyData(
|
||||
ColumnFamilyData::kDummyColumnFamilyDataId, "", nullptr, nullptr,
|
||||
nullptr, ColumnFamilyOptions(), *db_options, file_options, nullptr,
|
||||
block_cache_tracer, io_tracer)),
|
||||
block_cache_tracer, io_tracer, db_session_id)),
|
||||
default_cfd_cache_(nullptr),
|
||||
db_name_(dbname),
|
||||
db_options_(db_options),
|
||||
@@ -1436,7 +1468,8 @@ ColumnFamilySet::ColumnFamilySet(const std::string& dbname,
|
||||
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_;
|
||||
@@ -1502,7 +1535,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);
|
||||
|
||||
+46
-28
@@ -44,6 +44,7 @@ class LogBuffer;
|
||||
class InstrumentedMutex;
|
||||
class InstrumentedMutexLock;
|
||||
struct SuperVersionContext;
|
||||
class BlobFileCache;
|
||||
|
||||
extern const double kIncSlowdownRatio;
|
||||
// This file contains a list of data structures for managing column family
|
||||
@@ -252,13 +253,12 @@ extern Status CheckCFPathsSupported(const DBOptions& db_options,
|
||||
|
||||
extern ColumnFamilyOptions SanitizeOptions(const ImmutableDBOptions& db_options,
|
||||
const ColumnFamilyOptions& src);
|
||||
// Wrap user defined table proproties collector factories `from cf_options`
|
||||
// Wrap user defined table properties collector factories `from cf_options`
|
||||
// into internal ones in int_tbl_prop_collector_factories. Add a system internal
|
||||
// one too.
|
||||
extern void GetIntTblPropCollectorFactory(
|
||||
const ImmutableCFOptions& ioptions,
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories);
|
||||
IntTblPropCollectorFactories* int_tbl_prop_collector_factories);
|
||||
|
||||
class ColumnFamilySet;
|
||||
|
||||
@@ -278,21 +278,11 @@ class ColumnFamilyData {
|
||||
// holding a DB mutex, or as the leader in a write batch group).
|
||||
void Ref() { refs_.fetch_add(1); }
|
||||
|
||||
// Unref decreases the reference count, but does not handle deletion
|
||||
// when the count goes to 0. If this method returns true then the
|
||||
// caller should delete the instance immediately, or later, by calling
|
||||
// FreeDeadColumnFamilies(). Unref() can only be called while holding
|
||||
// a DB mutex, or during single-threaded recovery.
|
||||
bool Unref() {
|
||||
int old_refs = refs_.fetch_sub(1);
|
||||
assert(old_refs > 0);
|
||||
return old_refs == 1;
|
||||
}
|
||||
|
||||
// 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.
|
||||
bool UnrefAndTryDelete();
|
||||
// sv_under_cleanup is only provided when called from SuperVersion::Cleanup.
|
||||
bool UnrefAndTryDelete(SuperVersion* sv_under_cleanup = nullptr);
|
||||
|
||||
// SetDropped() can only be called under following conditions:
|
||||
// 1) Holding a DB mutex,
|
||||
@@ -325,7 +315,7 @@ class ColumnFamilyData {
|
||||
FlushReason GetFlushReason() const { return flush_reason_; }
|
||||
// thread-safe
|
||||
const FileOptions* soptions() const;
|
||||
const ImmutableCFOptions* ioptions() const { return &ioptions_; }
|
||||
const ImmutableOptions* ioptions() const { return &ioptions_; }
|
||||
// REQUIRES: DB mutex held
|
||||
// This returns the MutableCFOptions used by current SuperVersion
|
||||
// You should use this API to reference MutableCFOptions most of the time.
|
||||
@@ -359,6 +349,11 @@ class ColumnFamilyData {
|
||||
|
||||
MemTableList* imm() { return &imm_; }
|
||||
MemTable* mem() { return mem_; }
|
||||
|
||||
bool IsEmpty() {
|
||||
return mem()->GetFirstSequenceNumber() == 0 && imm()->NumNotFlushed() == 0;
|
||||
}
|
||||
|
||||
Version* current() { return current_; }
|
||||
Version* dummy_versions() { return dummy_versions_; }
|
||||
void SetCurrent(Version* _current);
|
||||
@@ -381,6 +376,7 @@ class ColumnFamilyData {
|
||||
SequenceNumber earliest_seq);
|
||||
|
||||
TableCache* table_cache() const { return table_cache_.get(); }
|
||||
BlobFileCache* blob_file_cache() const { return blob_file_cache_.get(); }
|
||||
|
||||
// See documentation in compaction_picker.h
|
||||
// REQUIRES: DB mutex held
|
||||
@@ -404,7 +400,8 @@ class ColumnFamilyData {
|
||||
//
|
||||
// Thread-safe
|
||||
Status RangesOverlapWithMemtables(const autovector<Range>& ranges,
|
||||
SuperVersion* super_version, bool* overlap);
|
||||
SuperVersion* super_version,
|
||||
bool allow_data_in_errors, bool* overlap);
|
||||
|
||||
// A flag to tell a manual compaction is to compact all levels together
|
||||
// instead of a specific level.
|
||||
@@ -430,8 +427,7 @@ class ColumnFamilyData {
|
||||
return internal_comparator_;
|
||||
}
|
||||
|
||||
const std::vector<std::unique_ptr<IntTblPropCollectorFactory>>*
|
||||
int_tbl_prop_collector_factories() const {
|
||||
const IntTblPropCollectorFactories* int_tbl_prop_collector_factories() const {
|
||||
return &int_tbl_prop_collector_factories_;
|
||||
}
|
||||
|
||||
@@ -443,7 +439,7 @@ class ColumnFamilyData {
|
||||
// Get SuperVersion stored in thread local storage. If it does not exist,
|
||||
// get a reference from a current SuperVersion.
|
||||
SuperVersion* GetThreadLocalSuperVersion(DBImpl* db);
|
||||
// Try to return SuperVersion back to thread local storage. Retrun true on
|
||||
// Try to return SuperVersion back to thread local storage. Return true on
|
||||
// success and false on failure. It fails when the thread local storage
|
||||
// contains anything other than SuperVersion::kSVInUse flag.
|
||||
bool ReturnThreadLocalSuperVersion(SuperVersion* sv);
|
||||
@@ -477,9 +473,11 @@ class ColumnFamilyData {
|
||||
kPendingCompactionBytes,
|
||||
};
|
||||
static std::pair<WriteStallCondition, WriteStallCause>
|
||||
GetWriteStallConditionAndCause(int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
const MutableCFOptions& mutable_cf_options);
|
||||
GetWriteStallConditionAndCause(
|
||||
int num_unflushed_memtables, int num_l0_files,
|
||||
uint64_t num_compaction_needed_bytes,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableCFOptions& immutable_cf_options);
|
||||
|
||||
// Recalculate some small conditions, which are changed only during
|
||||
// compaction, adding new memtable and/or
|
||||
@@ -506,6 +504,21 @@ class ColumnFamilyData {
|
||||
|
||||
FSDirectory* GetDataDir(size_t path_id) const;
|
||||
|
||||
// full_history_ts_low_ can only increase.
|
||||
void SetFullHistoryTsLow(std::string ts_low) {
|
||||
assert(!ts_low.empty());
|
||||
const Comparator* ucmp = user_comparator();
|
||||
assert(ucmp);
|
||||
if (full_history_ts_low_.empty() ||
|
||||
ucmp->CompareTimestamp(ts_low, full_history_ts_low_) > 0) {
|
||||
full_history_ts_low_ = std::move(ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& GetFullHistoryTsLow() const {
|
||||
return full_history_ts_low_;
|
||||
}
|
||||
|
||||
ThreadLocalPtr* TEST_GetLocalSV() { return local_sv_.get(); }
|
||||
|
||||
private:
|
||||
@@ -519,7 +532,8 @@ class ColumnFamilyData {
|
||||
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;
|
||||
|
||||
@@ -533,16 +547,16 @@ class ColumnFamilyData {
|
||||
std::atomic<bool> dropped_; // true if client dropped it
|
||||
|
||||
const InternalKeyComparator internal_comparator_;
|
||||
std::vector<std::unique_ptr<IntTblPropCollectorFactory>>
|
||||
int_tbl_prop_collector_factories_;
|
||||
IntTblPropCollectorFactories int_tbl_prop_collector_factories_;
|
||||
|
||||
const ColumnFamilyOptions initial_cf_options_;
|
||||
const ImmutableCFOptions ioptions_;
|
||||
const ImmutableOptions ioptions_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
|
||||
const bool is_delete_range_supported_;
|
||||
|
||||
std::unique_ptr<TableCache> table_cache_;
|
||||
std::unique_ptr<BlobFileCache> blob_file_cache_;
|
||||
|
||||
std::unique_ptr<InternalStats> internal_stats_;
|
||||
|
||||
@@ -601,6 +615,8 @@ class ColumnFamilyData {
|
||||
std::vector<std::shared_ptr<FSDirectory>> data_dirs_;
|
||||
|
||||
bool db_paths_registered_;
|
||||
|
||||
std::string full_history_ts_low_;
|
||||
};
|
||||
|
||||
// ColumnFamilySet has interesting thread-safety requirements
|
||||
@@ -653,7 +669,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 +735,7 @@ class ColumnFamilySet {
|
||||
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
|
||||
|
||||
+61
-49
@@ -35,10 +35,10 @@ namespace ROCKSDB_NAMESPACE {
|
||||
static const int kValueSize = 1000;
|
||||
|
||||
// counts how many operations were performed
|
||||
class EnvCounter : public EnvWrapper {
|
||||
class EnvCounter : public SpecialEnv {
|
||||
public:
|
||||
explicit EnvCounter(Env* base)
|
||||
: EnvWrapper(base), num_new_writable_file_(0) {}
|
||||
: SpecialEnv(base), num_new_writable_file_(0) {}
|
||||
int GetNumberOfNewWritableFileCalls() {
|
||||
return num_new_writable_file_;
|
||||
}
|
||||
@@ -56,23 +56,16 @@ 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;
|
||||
dbname_ = test::PerThreadDBPath("column_family_test");
|
||||
db_options_.create_if_missing = true;
|
||||
db_options_.fail_if_options_file_error = true;
|
||||
db_options_.env = env_;
|
||||
DestroyDB(dbname_, Options(db_options_, column_family_options_));
|
||||
EXPECT_OK(DestroyDB(dbname_, Options(db_options_, column_family_options_)));
|
||||
}
|
||||
|
||||
~ColumnFamilyTestBase() override {
|
||||
@@ -87,7 +80,6 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
#endif // ROCKSDB_LITE
|
||||
column_families.push_back(cfdescriptor);
|
||||
}
|
||||
Close();
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
Destroy(column_families);
|
||||
delete env_;
|
||||
@@ -187,8 +179,8 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
names_.clear();
|
||||
for (size_t i = 0; i < cf.size(); ++i) {
|
||||
column_families.push_back(ColumnFamilyDescriptor(
|
||||
cf[i], options.size() == 0 ? column_family_options_ : options[i]));
|
||||
column_families.emplace_back(
|
||||
cf[i], options.size() == 0 ? column_family_options_ : options[i]);
|
||||
names_.push_back(cf[i]);
|
||||
}
|
||||
return DB::Open(db_options_, dbname_, column_families, &handles_, &db_);
|
||||
@@ -199,8 +191,8 @@ class ColumnFamilyTestBase : public testing::Test {
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
names_.clear();
|
||||
for (size_t i = 0; i < cf.size(); ++i) {
|
||||
column_families.push_back(ColumnFamilyDescriptor(
|
||||
cf[i], options.size() == 0 ? column_family_options_ : options[i]));
|
||||
column_families.emplace_back(
|
||||
cf[i], options.size() == 0 ? column_family_options_ : options[i]);
|
||||
names_.push_back(cf[i]);
|
||||
}
|
||||
return DB::OpenForReadOnly(db_options_, dbname_, column_families, &handles_,
|
||||
@@ -653,8 +645,8 @@ TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest) {
|
||||
// after flushing file B is deleted. At the same time, the min log number of
|
||||
// default CF is not written to manifest. Log file A still remains.
|
||||
// Flushed to SST file Y.
|
||||
Flush(1);
|
||||
Flush(0);
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_OK(Put(1, "bar", "v3")); // seqID 4
|
||||
ASSERT_OK(Put(1, "foo", "v4")); // seqID 5
|
||||
ASSERT_OK(db_->FlushWAL(/*sync=*/false));
|
||||
@@ -708,15 +700,15 @@ TEST_P(FlushEmptyCFTestWithParam, FlushEmptyCFTest2) {
|
||||
// and is set to current. Both CFs' min log number is set to file C so after
|
||||
// flushing file B is deleted. Log file A still remains.
|
||||
// Flushed to SST file Y.
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(0, "bar", "v2")); // seqID 4
|
||||
ASSERT_OK(Put(2, "bar", "v2")); // seqID 5
|
||||
ASSERT_OK(Put(1, "bar", "v3")); // seqID 6
|
||||
// Flushing all column families. This forces all CFs' min log to current. This
|
||||
// is written to the manifest file. Log file C is cleared.
|
||||
Flush(0);
|
||||
Flush(1);
|
||||
Flush(2);
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Flush(2));
|
||||
// Write to log file D
|
||||
ASSERT_OK(Put(1, "bar", "v4")); // seqID 7
|
||||
ASSERT_OK(Put(1, "bar", "v5")); // seqID 8
|
||||
@@ -898,9 +890,7 @@ TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) {
|
||||
std::vector<std::string> old_files;
|
||||
ASSERT_OK(env_->GetChildren(backup_logs, &old_files));
|
||||
for (auto& file : old_files) {
|
||||
if (file != "." && file != "..") {
|
||||
ASSERT_OK(env_->DeleteFile(backup_logs + "/" + file));
|
||||
}
|
||||
ASSERT_OK(env_->DeleteFile(backup_logs + "/" + file));
|
||||
}
|
||||
|
||||
column_family_options_.merge_operator =
|
||||
@@ -929,9 +919,7 @@ TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) {
|
||||
std::vector<std::string> logs;
|
||||
ASSERT_OK(env_->GetChildren(db_options_.wal_dir, &logs));
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(db_options_.wal_dir + "/" + log, backup_logs + "/" + log);
|
||||
}
|
||||
CopyFile(db_options_.wal_dir + "/" + log, backup_logs + "/" + log);
|
||||
}
|
||||
|
||||
// recover the DB
|
||||
@@ -956,9 +944,7 @@ TEST_P(ColumnFamilyTest, IgnoreRecoveredLog) {
|
||||
if (iter == 0) {
|
||||
// copy the logs from backup back to wal dir
|
||||
for (auto& log : logs) {
|
||||
if (log != ".." && log != ".") {
|
||||
CopyFile(backup_logs + "/" + log, db_options_.wal_dir + "/" + log);
|
||||
}
|
||||
CopyFile(backup_logs + "/" + log, db_options_.wal_dir + "/" + log);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -985,7 +971,7 @@ TEST_P(ColumnFamilyTest, FlushTest) {
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
uint64_t max_total_in_memory_state =
|
||||
MaxTotalInMemoryState();
|
||||
Flush(i);
|
||||
ASSERT_OK(Flush(i));
|
||||
AssertMaxTotalInMemoryState(max_total_in_memory_state);
|
||||
}
|
||||
ASSERT_OK(Put(1, "foofoo", "bar"));
|
||||
@@ -1093,7 +1079,7 @@ TEST_P(ColumnFamilyTest, CrashAfterFlush) {
|
||||
ASSERT_OK(batch.Put(handles_[0], Slice("foo"), Slice("bar")));
|
||||
ASSERT_OK(batch.Put(handles_[1], Slice("foo"), Slice("bar")));
|
||||
ASSERT_OK(db_->Write(WriteOptions(), &batch));
|
||||
Flush(0);
|
||||
ASSERT_OK(Flush(0));
|
||||
fault_env->SetFilesystemActive(false);
|
||||
|
||||
std::vector<std::string> names;
|
||||
@@ -1103,7 +1089,7 @@ TEST_P(ColumnFamilyTest, CrashAfterFlush) {
|
||||
}
|
||||
}
|
||||
Close();
|
||||
fault_env->DropUnsyncedFileData();
|
||||
ASSERT_OK(fault_env->DropUnsyncedFileData());
|
||||
fault_env->ResetState();
|
||||
Open(names, {});
|
||||
|
||||
@@ -2236,7 +2222,7 @@ TEST_P(ColumnFamilyTest, FlushStaleColumnFamilies) {
|
||||
// files for column family [one], because it's empty
|
||||
AssertCountLiveFiles(4);
|
||||
|
||||
Flush(0);
|
||||
ASSERT_OK(Flush(0));
|
||||
ASSERT_EQ(0, dbfull()->TEST_total_log_size());
|
||||
Close();
|
||||
}
|
||||
@@ -2292,6 +2278,8 @@ TEST_P(ColumnFamilyTest, SanitizeOptions) {
|
||||
// not a multiple of 4k, round up 4k
|
||||
expected_arena_block_size += 4 * 1024;
|
||||
}
|
||||
expected_arena_block_size =
|
||||
std::min(size_t{1024 * 1024}, expected_arena_block_size);
|
||||
ASSERT_EQ(expected_arena_block_size, result.arena_block_size);
|
||||
}
|
||||
}
|
||||
@@ -3040,7 +3028,7 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile1) {
|
||||
Iterator* it = db_->NewIterator(ReadOptions(), handles_[1]);
|
||||
ASSERT_OK(it->status());
|
||||
// A flush will make `it` hold the last reference of its super version.
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(0, "fodor", "mirko"));
|
||||
@@ -3093,7 +3081,7 @@ TEST_P(ColumnFamilyTest, IteratorCloseWALFile2) {
|
||||
Iterator* it = db_->NewIterator(ro, handles_[1]);
|
||||
ASSERT_OK(it->status());
|
||||
// A flush will make `it` hold the last reference of its super version.
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(0, "fodor", "mirko"));
|
||||
@@ -3147,7 +3135,7 @@ TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
CreateColumnFamilies({"one"});
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodar2", "mirko"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// Create an iterator holding the current super version, as well as
|
||||
// the SST file just flushed.
|
||||
@@ -3159,7 +3147,7 @@ TEST_P(ColumnFamilyTest, ForwardIteratorCloseWALFile) {
|
||||
|
||||
ASSERT_OK(Put(1, "fodor", "mirko"));
|
||||
ASSERT_OK(Put(1, "fodar2", "mirko"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
WaitForCompaction();
|
||||
|
||||
@@ -3232,9 +3220,9 @@ TEST_P(ColumnFamilyTest, LogSyncConflictFlush) {
|
||||
ROCKSDB_NAMESPACE::port::Thread thread([&] { ASSERT_OK(db_->SyncWAL()); });
|
||||
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::LogSyncConflictFlush:1");
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "foo", "bar"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
TEST_SYNC_POINT("ColumnFamilyTest::LogSyncConflictFlush:2");
|
||||
|
||||
@@ -3256,7 +3244,7 @@ TEST_P(ColumnFamilyTest, DISABLED_LogTruncationTest) {
|
||||
Build(0, 100);
|
||||
|
||||
// Flush the 0th column family to force a roll of the wal log
|
||||
Flush(0);
|
||||
ASSERT_OK(Flush(0));
|
||||
|
||||
// Add some more entries
|
||||
Build(100, 100);
|
||||
@@ -3271,7 +3259,7 @@ TEST_P(ColumnFamilyTest, DISABLED_LogTruncationTest) {
|
||||
FileType type;
|
||||
if (!(ParseFileName(filenames[i], &number, &type))) continue;
|
||||
|
||||
if (type != kLogFile) continue;
|
||||
if (type != kWalFile) continue;
|
||||
|
||||
logfs.push_back(filenames[i]);
|
||||
}
|
||||
@@ -3332,14 +3320,14 @@ TEST_P(ColumnFamilyTest, DefaultCfPathsTest) {
|
||||
|
||||
// Fill Column family 1.
|
||||
PutRandomData(1, 100, 100);
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_EQ(1, GetSstFileCount(cf_opt1.cf_paths[0].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
// Fill column family 2
|
||||
PutRandomData(2, 100, 100);
|
||||
Flush(2);
|
||||
ASSERT_OK(Flush(2));
|
||||
|
||||
// SST from Column family 2 should be generated in
|
||||
// db_paths which is dbname_ in this case.
|
||||
@@ -3358,14 +3346,14 @@ TEST_P(ColumnFamilyTest, MultipleCFPathsTest) {
|
||||
Reopen({ColumnFamilyOptions(), cf_opt1, cf_opt2});
|
||||
|
||||
PutRandomData(1, 100, 100, true /* save */);
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// Check that files are generated in appropriate paths.
|
||||
ASSERT_EQ(1, GetSstFileCount(cf_opt1.cf_paths[0].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
|
||||
PutRandomData(2, 100, 100, true /* save */);
|
||||
Flush(2);
|
||||
ASSERT_OK(Flush(2));
|
||||
|
||||
ASSERT_EQ(1, GetSstFileCount(cf_opt2.cf_paths[0].path));
|
||||
ASSERT_EQ(0, GetSstFileCount(dbname_));
|
||||
@@ -3391,6 +3379,30 @@ TEST_P(ColumnFamilyTest, MultipleCFPathsTest) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ColumnFamilyTest, ValidateBlobGCCutoff) {
|
||||
DBOptions db_options;
|
||||
|
||||
ColumnFamilyOptions cf_options;
|
||||
cf_options.enable_blob_garbage_collection = true;
|
||||
|
||||
cf_options.blob_garbage_collection_age_cutoff = -0.5;
|
||||
ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options)
|
||||
.IsInvalidArgument());
|
||||
|
||||
cf_options.blob_garbage_collection_age_cutoff = 0.0;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_age_cutoff = 0.5;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_age_cutoff = 1.0;
|
||||
ASSERT_OK(ColumnFamilyData::ValidateOptions(db_options, cf_options));
|
||||
|
||||
cf_options.blob_garbage_collection_age_cutoff = 1.5;
|
||||
ASSERT_TRUE(ColumnFamilyData::ValidateOptions(db_options, cf_options)
|
||||
.IsInvalidArgument());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
#ifdef ROCKSDB_UNITTESTS_WITH_CUSTOM_OBJECTS_FROM_STATIC_LIBS
|
||||
|
||||
+100
-25
@@ -91,9 +91,9 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
// create couple files
|
||||
// Background compaction starts and waits in BackgroundCallCompaction:0
|
||||
for (int i = 0; i < kLevel0Trigger * 4; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i), "");
|
||||
db->Put(WriteOptions(), ToString(100 - i), "");
|
||||
db->Flush(FlushOptions());
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(i), ""));
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(100 - i), ""));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
}
|
||||
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyMetaData meta;
|
||||
@@ -118,6 +118,78 @@ TEST_F(CompactFilesTest, L0ConflictsFiles) {
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, MultipleLevel) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.num_levels = 6;
|
||||
// Add listener
|
||||
FlushedFileCollector* collector = new FlushedFileCollector();
|
||||
options.listeners.emplace_back(collector);
|
||||
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(db, nullptr);
|
||||
|
||||
// create couple files in L0, L3, L4 and L5
|
||||
for (int i = 5; i > 2; --i) {
|
||||
collector->ClearFlushedFiles();
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(i), ""));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, i));
|
||||
|
||||
std::string prop;
|
||||
ASSERT_TRUE(
|
||||
db->GetProperty("rocksdb.num-files-at-level" + ToString(i), &prop));
|
||||
ASSERT_EQ("1", prop);
|
||||
}
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(0), ""));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db->GetColumnFamilyMetaData(&meta);
|
||||
// Compact files except the file in L3
|
||||
std::vector<std::string> files;
|
||||
for (int i = 0; i < 6; ++i) {
|
||||
if (i == 3) continue;
|
||||
for (auto& file : meta.levels[i].files) {
|
||||
files.push_back(file.db_path + "/" + file.name);
|
||||
}
|
||||
}
|
||||
|
||||
SyncPoint::GetInstance()->LoadDependency({
|
||||
{"CompactionJob::Run():Start", "CompactFilesTest.MultipleLevel:0"},
|
||||
{"CompactFilesTest.MultipleLevel:1", "CompactFilesImpl:3"},
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::thread thread([&] {
|
||||
TEST_SYNC_POINT("CompactFilesTest.MultipleLevel:0");
|
||||
ASSERT_OK(db->Put(WriteOptions(), "bar", "v2"));
|
||||
ASSERT_OK(db->Put(WriteOptions(), "foo", "v2"));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
TEST_SYNC_POINT("CompactFilesTest.MultipleLevel:1");
|
||||
});
|
||||
|
||||
// Compaction cannot move up the data to higher level
|
||||
// here we have input file from level 5, so the output level has to be >= 5
|
||||
for (int invalid_output_level = 0; invalid_output_level < 5;
|
||||
invalid_output_level++) {
|
||||
s = db->CompactFiles(CompactionOptions(), files, invalid_output_level);
|
||||
std::cout << s.ToString() << std::endl;
|
||||
ASSERT_TRUE(s.IsInvalidArgument());
|
||||
}
|
||||
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), files, 5));
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
thread.join();
|
||||
|
||||
delete db;
|
||||
}
|
||||
|
||||
TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
Options options;
|
||||
// to trigger compaction more easily
|
||||
@@ -138,18 +210,18 @@ TEST_F(CompactFilesTest, ObsoleteFiles) {
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
assert(s.ok());
|
||||
assert(db);
|
||||
ASSERT_OK(s);
|
||||
ASSERT_NE(db, nullptr);
|
||||
|
||||
// create couple files
|
||||
for (int i = 1000; i < 2000; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i),
|
||||
std::string(kWriteBufferSize / 10, 'a' + (i % 26)));
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(i),
|
||||
std::string(kWriteBufferSize / 10, 'a' + (i % 26))));
|
||||
}
|
||||
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files, 1));
|
||||
static_cast_with_check<DBImpl>(db)->TEST_WaitForCompact();
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForCompact());
|
||||
|
||||
// verify all compaction input files are deleted
|
||||
for (auto fname : l0_files) {
|
||||
@@ -182,15 +254,17 @@ TEST_F(CompactFilesTest, NotCutOutputOnLevel0) {
|
||||
|
||||
// create couple files
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(i),
|
||||
std::string(1000, 'a' + (i % 26))));
|
||||
}
|
||||
static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable();
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable());
|
||||
auto l0_files_1 = collector->GetFlushedFiles();
|
||||
collector->ClearFlushedFiles();
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(i),
|
||||
std::string(1000, 'a' + (i % 26))));
|
||||
}
|
||||
static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable();
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable());
|
||||
auto l0_files_2 = collector->GetFlushedFiles();
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files_1, 0));
|
||||
ASSERT_OK(db->CompactFiles(CompactionOptions(), l0_files_2, 0));
|
||||
@@ -213,13 +287,13 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
assert(s.ok());
|
||||
ASSERT_OK(s);
|
||||
assert(db);
|
||||
|
||||
// Create 5 files.
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
db->Put(WriteOptions(), "key" + ToString(i), "value");
|
||||
db->Flush(FlushOptions());
|
||||
ASSERT_OK(db->Put(WriteOptions(), "key" + ToString(i), "value"));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
}
|
||||
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
@@ -237,8 +311,8 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
|
||||
|
||||
// In the meantime flush another file.
|
||||
TEST_SYNC_POINT("CompactFilesTest.CapturingPendingFiles:0");
|
||||
db->Put(WriteOptions(), "key5", "value");
|
||||
db->Flush(FlushOptions());
|
||||
ASSERT_OK(db->Put(WriteOptions(), "key5", "value"));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
TEST_SYNC_POINT("CompactFilesTest.CapturingPendingFiles:1");
|
||||
|
||||
compaction_thread.join();
|
||||
@@ -249,7 +323,7 @@ TEST_F(CompactFilesTest, CapturingPendingFiles) {
|
||||
|
||||
// Make sure we can reopen the DB.
|
||||
s = DB::Open(options, db_name_, &db);
|
||||
ASSERT_TRUE(s.ok());
|
||||
ASSERT_OK(s);
|
||||
assert(db);
|
||||
delete db;
|
||||
}
|
||||
@@ -293,8 +367,8 @@ TEST_F(CompactFilesTest, CompactionFilterWithGetSv) {
|
||||
cf->SetDB(db);
|
||||
|
||||
// Write one L0 file
|
||||
db->Put(WriteOptions(), "K1", "V1");
|
||||
db->Flush(FlushOptions());
|
||||
ASSERT_OK(db->Put(WriteOptions(), "K1", "V1"));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
// Compact all L0 files using CompactFiles
|
||||
ROCKSDB_NAMESPACE::ColumnFamilyMetaData meta;
|
||||
@@ -337,8 +411,8 @@ TEST_F(CompactFilesTest, SentinelCompressionType) {
|
||||
DB* db = nullptr;
|
||||
ASSERT_OK(DB::Open(options, db_name_, &db));
|
||||
|
||||
db->Put(WriteOptions(), "key", "val");
|
||||
db->Flush(FlushOptions());
|
||||
ASSERT_OK(db->Put(WriteOptions(), "key", "val"));
|
||||
ASSERT_OK(db->Flush(FlushOptions()));
|
||||
|
||||
auto l0_files = collector->GetFlushedFiles();
|
||||
ASSERT_EQ(1, l0_files.size());
|
||||
@@ -377,14 +451,15 @@ TEST_F(CompactFilesTest, GetCompactionJobInfo) {
|
||||
DB* db = nullptr;
|
||||
DestroyDB(db_name_, options);
|
||||
Status s = DB::Open(options, db_name_, &db);
|
||||
assert(s.ok());
|
||||
ASSERT_OK(s);
|
||||
assert(db);
|
||||
|
||||
// create couple files
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
db->Put(WriteOptions(), ToString(i), std::string(1000, 'a' + (i % 26)));
|
||||
ASSERT_OK(db->Put(WriteOptions(), ToString(i),
|
||||
std::string(1000, 'a' + (i % 26))));
|
||||
}
|
||||
static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable();
|
||||
ASSERT_OK(static_cast_with_check<DBImpl>(db)->TEST_WaitForFlushMemTable());
|
||||
auto l0_files_1 = collector->GetFlushedFiles();
|
||||
CompactionOptions co;
|
||||
co.compression = CompressionType::kLZ4Compression;
|
||||
|
||||
@@ -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,256 @@
|
||||
// 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 "rocksdb/comparator.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.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 test::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), 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 test::VectorIterator(input_keys, input_values));
|
||||
|
||||
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();
|
||||
}
|
||||
+41
-30
@@ -204,27 +204,24 @@ bool Compaction::IsFullCompaction(
|
||||
return num_files_in_compaction == total_num_files;
|
||||
}
|
||||
|
||||
Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
const ImmutableCFOptions& _immutable_cf_options,
|
||||
const MutableCFOptions& _mutable_cf_options,
|
||||
const MutableDBOptions& _mutable_db_options,
|
||||
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,
|
||||
CompactionReason _compaction_reason)
|
||||
Compaction::Compaction(
|
||||
VersionStorageInfo* vstorage, const ImmutableOptions& _immutable_options,
|
||||
const MutableCFOptions& _mutable_cf_options,
|
||||
const MutableDBOptions& _mutable_db_options,
|
||||
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,
|
||||
CompactionReason _compaction_reason)
|
||||
: input_vstorage_(vstorage),
|
||||
start_level_(_inputs[0].level),
|
||||
output_level_(_output_level),
|
||||
max_output_file_size_(_target_file_size),
|
||||
max_compaction_bytes_(_max_compaction_bytes),
|
||||
max_subcompactions_(_max_subcompactions),
|
||||
immutable_cf_options_(_immutable_cf_options),
|
||||
immutable_options_(_immutable_options),
|
||||
mutable_cf_options_(_mutable_cf_options),
|
||||
input_version_(nullptr),
|
||||
number_levels_(vstorage->num_levels()),
|
||||
@@ -248,12 +245,6 @@ Compaction::Compaction(VersionStorageInfo* vstorage,
|
||||
if (max_subcompactions_ == 0) {
|
||||
max_subcompactions_ = _mutable_db_options.max_subcompactions;
|
||||
}
|
||||
if (!bottommost_level_) {
|
||||
// Currently we only enable dictionary compression during compaction to the
|
||||
// bottommost level.
|
||||
output_compression_opts_.max_dict_bytes = 0;
|
||||
output_compression_opts_.zstd_max_train_bytes = 0;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
for (size_t i = 1; i < inputs_.size(); ++i) {
|
||||
@@ -284,7 +275,7 @@ Compaction::~Compaction() {
|
||||
|
||||
bool Compaction::InputCompressionMatchesOutput() const {
|
||||
int base_level = input_vstorage_->base_level();
|
||||
bool matches = (GetCompressionType(immutable_cf_options_, input_vstorage_,
|
||||
bool matches = (GetCompressionType(immutable_options_, input_vstorage_,
|
||||
mutable_cf_options_, start_level_,
|
||||
base_level) == output_compression_);
|
||||
if (matches) {
|
||||
@@ -309,8 +300,8 @@ bool Compaction::IsTrivialMove() const {
|
||||
}
|
||||
|
||||
if (is_manual_compaction_ &&
|
||||
(immutable_cf_options_.compaction_filter != nullptr ||
|
||||
immutable_cf_options_.compaction_filter_factory != nullptr)) {
|
||||
(immutable_options_.compaction_filter != nullptr ||
|
||||
immutable_options_.compaction_filter_factory != nullptr)) {
|
||||
// This is a manual compaction and we have a compaction filter that should
|
||||
// be executed, we cannot do a trivial move
|
||||
return false;
|
||||
@@ -383,7 +374,13 @@ bool Compaction::KeyNotExistsBeyondOutputLevel(
|
||||
auto* f = files[level_ptrs->at(lvl)];
|
||||
if (user_cmp->Compare(user_key, f->largest.user_key()) <= 0) {
|
||||
// We've advanced far enough
|
||||
if (user_cmp->Compare(user_key, f->smallest.user_key()) >= 0) {
|
||||
// In the presence of user-defined timestamp, we may need to handle
|
||||
// the case in which f->smallest.user_key() (including ts) has the
|
||||
// same user key, but the ts part is smaller. If so,
|
||||
// Compare(user_key, f->smallest.user_key()) returns -1.
|
||||
// That's why we need CompareWithoutTimestamp().
|
||||
if (user_cmp->CompareWithoutTimestamp(user_key,
|
||||
f->smallest.user_key()) >= 0) {
|
||||
// Key falls in this file's range, so it may
|
||||
// exist beyond output level
|
||||
return false;
|
||||
@@ -512,14 +509,14 @@ uint64_t Compaction::OutputFilePreallocationSize() const {
|
||||
}
|
||||
|
||||
if (max_output_file_size_ != port::kMaxUint64 &&
|
||||
(immutable_cf_options_.compaction_style == kCompactionStyleLevel ||
|
||||
(immutable_options_.compaction_style == kCompactionStyleLevel ||
|
||||
output_level() > 0)) {
|
||||
preallocation_size = std::min(max_output_file_size_, preallocation_size);
|
||||
}
|
||||
|
||||
// Over-estimate slightly so we don't end up just barely crossing
|
||||
// the threshold
|
||||
// No point to prellocate more than 1GB.
|
||||
// No point to preallocate more than 1GB.
|
||||
return std::min(uint64_t{1073741824},
|
||||
preallocation_size + (preallocation_size / 10));
|
||||
}
|
||||
@@ -529,16 +526,23 @@ std::unique_ptr<CompactionFilter> Compaction::CreateCompactionFilter() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!cfd_->ioptions()
|
||||
->compaction_filter_factory->ShouldFilterTableFileCreation(
|
||||
TableFileCreationReason::kCompaction)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CompactionFilter::Context context;
|
||||
context.is_full_compaction = is_full_compaction_;
|
||||
context.is_manual_compaction = is_manual_compaction_;
|
||||
context.column_family_id = cfd_->GetID();
|
||||
context.reason = TableFileCreationReason::kCompaction;
|
||||
return cfd_->ioptions()->compaction_filter_factory->CreateCompactionFilter(
|
||||
context);
|
||||
}
|
||||
|
||||
std::unique_ptr<SstPartitioner> Compaction::CreateSstPartitioner() const {
|
||||
if (!immutable_cf_options_.sst_partitioner_factory) {
|
||||
if (!immutable_options_.sst_partitioner_factory) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -548,8 +552,7 @@ std::unique_ptr<SstPartitioner> Compaction::CreateSstPartitioner() const {
|
||||
context.output_level = output_level_;
|
||||
context.smallest_user_key = smallest_user_key_;
|
||||
context.largest_user_key = largest_user_key_;
|
||||
return immutable_cf_options_.sst_partitioner_factory->CreatePartitioner(
|
||||
context);
|
||||
return immutable_options_.sst_partitioner_factory->CreatePartitioner(context);
|
||||
}
|
||||
|
||||
bool Compaction::IsOutputLevelEmpty() const {
|
||||
@@ -560,6 +563,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();
|
||||
|
||||
@@ -70,7 +70,7 @@ class CompactionFilter;
|
||||
class Compaction {
|
||||
public:
|
||||
Compaction(VersionStorageInfo* input_version,
|
||||
const ImmutableCFOptions& immutable_cf_options,
|
||||
const ImmutableOptions& immutable_options,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
std::vector<CompactionInputFiles> inputs, int output_level,
|
||||
@@ -162,7 +162,7 @@ class Compaction {
|
||||
CompressionType output_compression() const { return output_compression_; }
|
||||
|
||||
// What compression options for output
|
||||
CompressionOptions output_compression_opts() const {
|
||||
const CompressionOptions& output_compression_opts() const {
|
||||
return output_compression_opts_;
|
||||
}
|
||||
|
||||
@@ -223,10 +223,10 @@ 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 ImmutableCFOptions* immutable_cf_options() const {
|
||||
return &immutable_cf_options_;
|
||||
const ImmutableOptions* immutable_options() const {
|
||||
return &immutable_options_;
|
||||
}
|
||||
|
||||
// Return the MutableCFOptions that should be used throughout the compaction
|
||||
@@ -330,7 +330,7 @@ class Compaction {
|
||||
uint64_t max_output_file_size_;
|
||||
uint64_t max_compaction_bytes_;
|
||||
uint32_t max_subcompactions_;
|
||||
const ImmutableCFOptions immutable_cf_options_;
|
||||
const ImmutableOptions immutable_options_;
|
||||
const MutableCFOptions mutable_cf_options_;
|
||||
Version* input_version_;
|
||||
VersionEdit edit_;
|
||||
@@ -341,7 +341,7 @@ class Compaction {
|
||||
const uint32_t output_path_id_;
|
||||
CompressionType output_compression_;
|
||||
CompressionOptions output_compression_opts_;
|
||||
// If true, then the comaction can be done by simply deleting input files.
|
||||
// If true, then the compaction can be done by simply deleting input files.
|
||||
const bool deletion_compaction_;
|
||||
|
||||
// Compaction input files organized by level. Constant after construction
|
||||
|
||||
@@ -34,4 +34,8 @@ struct CompactionIterationStats {
|
||||
// Single-Delete diagnostics for exceptional situations
|
||||
uint64_t num_single_del_fallthru = 0;
|
||||
uint64_t num_single_del_mismatch = 0;
|
||||
|
||||
// Blob related statistics
|
||||
uint64_t num_blobs_read = 0;
|
||||
uint64_t total_blob_bytes_read = 0;
|
||||
};
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
#include "db/blob/blob_file_builder.h"
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/snapshot_checker.h"
|
||||
#include "port/likely.h"
|
||||
#include "rocksdb/listener.h"
|
||||
@@ -31,7 +33,6 @@
|
||||
(snapshot_checker_ == nullptr || LIKELY(IsInEarliestSnapshot(seq))))
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
SequenceNumber last_sequence, std::vector<SequenceNumber>* snapshots,
|
||||
@@ -44,16 +45,19 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::shared_ptr<Logger> info_log)
|
||||
const std::atomic<bool>* manual_compaction_canceled,
|
||||
const std::shared_ptr<Logger> info_log,
|
||||
const std::string* full_history_ts_low)
|
||||
: CompactionIterator(
|
||||
input, cmp, merge_helper, last_sequence, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, env,
|
||||
report_detailed_time, expect_valid_internal_key, range_del_agg,
|
||||
blob_file_builder, allow_data_in_errors,
|
||||
std::unique_ptr<CompactionProxy>(
|
||||
compaction ? new CompactionProxy(compaction) : nullptr),
|
||||
compaction ? new RealCompaction(compaction) : nullptr),
|
||||
compaction_filter, shutting_down, preserve_deletes_seqnum,
|
||||
manual_compaction_paused, info_log) {}
|
||||
manual_compaction_paused, manual_compaction_canceled, info_log,
|
||||
full_history_ts_low) {}
|
||||
|
||||
CompactionIterator::CompactionIterator(
|
||||
InternalIterator* input, const Comparator* cmp, MergeHelper* merge_helper,
|
||||
@@ -68,14 +72,20 @@ CompactionIterator::CompactionIterator(
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum,
|
||||
const std::atomic<int>* manual_compaction_paused,
|
||||
const std::shared_ptr<Logger> info_log)
|
||||
: input_(input),
|
||||
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.
|
||||
cmp_(cmp),
|
||||
merge_helper_(merge_helper),
|
||||
snapshots_(snapshots),
|
||||
earliest_write_conflict_snapshot_(earliest_write_conflict_snapshot),
|
||||
snapshot_checker_(snapshot_checker),
|
||||
env_(env),
|
||||
clock_(env_->GetSystemClock().get()),
|
||||
report_detailed_time_(report_detailed_time),
|
||||
expect_valid_internal_key_(expect_valid_internal_key),
|
||||
range_del_agg_(range_del_agg),
|
||||
@@ -84,14 +94,20 @@ 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),
|
||||
timestamp_size_(cmp_ ? cmp_->timestamp_size() : 0),
|
||||
full_history_ts_low_(full_history_ts_low),
|
||||
current_user_key_sequence_(0),
|
||||
current_user_key_snapshot_(0),
|
||||
merge_out_iter_(merge_helper_),
|
||||
blob_garbage_collection_cutoff_file_number_(
|
||||
ComputeBlobGarbageCollectionCutoffFileNumber(compaction_.get())),
|
||||
current_key_committed_(false),
|
||||
info_log_(info_log),
|
||||
allow_data_in_errors_(allow_data_in_errors) {
|
||||
assert(compaction_filter_ == nullptr || compaction_ != nullptr);
|
||||
cmp_with_history_ts_low_(0),
|
||||
level_(compaction_ == nullptr ? 0 : compaction_->level()) {
|
||||
assert(snapshots_ != nullptr);
|
||||
bottommost_level_ = compaction_ == nullptr
|
||||
? false
|
||||
@@ -117,14 +133,16 @@ CompactionIterator::CompactionIterator(
|
||||
for (size_t i = 1; i < snapshots_->size(); ++i) {
|
||||
assert(snapshots_->at(i - 1) < snapshots_->at(i));
|
||||
}
|
||||
assert(timestamp_size_ == 0 || !full_history_ts_low_ ||
|
||||
timestamp_size_ == full_history_ts_low_->size());
|
||||
#endif
|
||||
input_->SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
input_.SetPinnedItersMgr(&pinned_iters_mgr_);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:AfterInit", compaction_.get());
|
||||
}
|
||||
|
||||
CompactionIterator::~CompactionIterator() {
|
||||
// input_ Iteartor lifetime is longer than pinned_iters_mgr_ lifetime
|
||||
input_->SetPinnedItersMgr(nullptr);
|
||||
// input_ Iterator lifetime is longer than pinned_iters_mgr_ lifetime
|
||||
input_.SetPinnedItersMgr(nullptr);
|
||||
}
|
||||
|
||||
void CompactionIterator::ResetRecordCounts() {
|
||||
@@ -151,13 +169,13 @@ void CompactionIterator::Next() {
|
||||
if (merge_out_iter_.Valid()) {
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
Status s = ParseInternalKey(key_, &ikey_);
|
||||
Status s = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to be valid.
|
||||
assert(s.ok());
|
||||
if (!s.ok()) {
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key (%s) in compaction",
|
||||
key_.ToString(true).c_str());
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key in compaction. %s",
|
||||
s.getState());
|
||||
}
|
||||
|
||||
// Keep current_key_ in sync.
|
||||
@@ -177,7 +195,7 @@ void CompactionIterator::Next() {
|
||||
// Only advance the input iterator if there is no merge output and the
|
||||
// iterator is not already at the next record.
|
||||
if (!at_next_) {
|
||||
input_->Next();
|
||||
AdvanceInputIter();
|
||||
}
|
||||
NextFromInput();
|
||||
}
|
||||
@@ -192,100 +210,172 @@ void CompactionIterator::Next() {
|
||||
|
||||
bool CompactionIterator::InvokeFilterIfNeeded(bool* need_skip,
|
||||
Slice* skip_until) {
|
||||
if (compaction_filter_ != nullptr &&
|
||||
(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex)) {
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
// replace the entry with a deletion marker.
|
||||
CompactionFilter::Decision filter;
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
CompactionFilter::ValueType value_type =
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
: CompactionFilter::ValueType::kBlobIndex;
|
||||
// Hack: pass internal key to BlobIndexCompactionFilter since it needs
|
||||
// to get sequence number.
|
||||
Slice& filter_key = ikey_.type == kTypeValue ? ikey_.user_key : key_;
|
||||
{
|
||||
StopWatchNano timer(env_, report_detailed_time_);
|
||||
if (!compaction_filter_ ||
|
||||
(ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex)) {
|
||||
return true;
|
||||
}
|
||||
bool error = false;
|
||||
// If the user has specified a compaction filter and the sequence
|
||||
// number is greater than any external snapshot, then invoke the
|
||||
// filter. If the return value of the compaction filter is true,
|
||||
// replace the entry with a deletion marker.
|
||||
CompactionFilter::Decision filter = CompactionFilter::Decision::kUndetermined;
|
||||
compaction_filter_value_.clear();
|
||||
compaction_filter_skip_until_.Clear();
|
||||
CompactionFilter::ValueType value_type =
|
||||
ikey_.type == kTypeValue ? CompactionFilter::ValueType::kValue
|
||||
: CompactionFilter::ValueType::kBlobIndex;
|
||||
// Hack: pass internal key to BlobIndexCompactionFilter since it needs
|
||||
// to get sequence number.
|
||||
assert(compaction_filter_);
|
||||
Slice& filter_key =
|
||||
(ikey_.type == kTypeValue ||
|
||||
!compaction_filter_->IsStackedBlobDbInternalCompactionFilter())
|
||||
? ikey_.user_key
|
||||
: key_;
|
||||
{
|
||||
StopWatchNano timer(clock_, report_detailed_time_);
|
||||
if (kTypeBlobIndex == ikey_.type) {
|
||||
blob_value_.Reset();
|
||||
filter = compaction_filter_->FilterBlobByKey(
|
||||
level_, filter_key, &compaction_filter_value_,
|
||||
compaction_filter_skip_until_.rep());
|
||||
if (CompactionFilter::Decision::kUndetermined == filter &&
|
||||
!compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
// For integrated BlobDB impl, CompactionIterator reads blob value.
|
||||
// For Stacked BlobDB impl, the corresponding CompactionFilter's
|
||||
// FilterV2 method should read the blob value.
|
||||
BlobIndex blob_index;
|
||||
Status s = blob_index.DecodeFrom(value_);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (blob_index.HasTTL() || blob_index.IsInlined()) {
|
||||
status_ = Status::Corruption("Unexpected TTL/inlined blob index");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (compaction_ == nullptr) {
|
||||
status_ =
|
||||
Status::Corruption("Unexpected blob index outside of compaction");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
const Version* const version = compaction_->input_version();
|
||||
assert(version);
|
||||
|
||||
uint64_t bytes_read = 0;
|
||||
s = version->GetBlob(ReadOptions(), ikey_.user_key, blob_index,
|
||||
&blob_value_, &bytes_read);
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
++iter_stats_.num_blobs_read;
|
||||
iter_stats_.total_blob_bytes_read += bytes_read;
|
||||
|
||||
value_type = CompactionFilter::ValueType::kValue;
|
||||
}
|
||||
}
|
||||
if (CompactionFilter::Decision::kUndetermined == filter) {
|
||||
filter = compaction_filter_->FilterV2(
|
||||
compaction_->level(), filter_key, value_type, value_,
|
||||
&compaction_filter_value_, compaction_filter_skip_until_.rep());
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr && report_detailed_time_ ? timer.ElapsedNanos() : 0;
|
||||
level_, filter_key, value_type,
|
||||
blob_value_.empty() ? value_ : blob_value_, &compaction_filter_value_,
|
||||
compaction_filter_skip_until_.rep());
|
||||
}
|
||||
iter_stats_.total_filter_time +=
|
||||
env_ != nullptr && report_detailed_time_ ? timer.ElapsedNanos() : 0;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
|
||||
0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
filter = CompactionFilter::Decision::kKeep;
|
||||
if (CompactionFilter::Decision::kUndetermined == filter) {
|
||||
// Should not reach here, since FilterV2 should never return kUndetermined.
|
||||
status_ =
|
||||
Status::NotSupported("FilterV2() should never return kUndetermined");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil &&
|
||||
cmp_->Compare(*compaction_filter_skip_until_.rep(), ikey_.user_key) <=
|
||||
0) {
|
||||
// Can't skip to a key smaller than the current one.
|
||||
// Keep the key as per FilterV2 documentation.
|
||||
filter = CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
|
||||
// no value associated with delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (filter == CompactionFilter::Decision::kChangeValue) {
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
// value transfer from blob file to inlined data
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
|
||||
if (filter == CompactionFilter::Decision::kRemove) {
|
||||
// convert the current key to a delete; key_ is pointing into
|
||||
// current_key_ at this point, so updating current_key_ updates key()
|
||||
ikey_.type = kTypeDeletion;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, kTypeDeletion);
|
||||
// no value associated with delete
|
||||
value_.clear();
|
||||
iter_stats_.num_record_drop_user++;
|
||||
} else if (filter == CompactionFilter::Decision::kChangeValue) {
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
// value transfer from blob file to inlined data
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
*need_skip = true;
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
*skip_until = compaction_filter_skip_until_.Encode();
|
||||
} else if (filter == CompactionFilter::Decision::kChangeBlobIndex) {
|
||||
if (ikey_.type == kTypeValue) {
|
||||
// value transfer from inlined data to blob file
|
||||
ikey_.type = kTypeBlobIndex;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kIOError) {
|
||||
status_ =
|
||||
Status::IOError("Failed to access blob during compaction filter");
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kRemoveAndSkipUntil) {
|
||||
*need_skip = true;
|
||||
compaction_filter_skip_until_.ConvertFromUserKey(kMaxSequenceNumber,
|
||||
kValueTypeForSeek);
|
||||
*skip_until = compaction_filter_skip_until_.Encode();
|
||||
} else if (filter == CompactionFilter::Decision::kChangeBlobIndex) {
|
||||
// Only the StackableDB-based BlobDB impl's compaction filter should return
|
||||
// kChangeBlobIndex. Decision about rewriting blob and changing blob index
|
||||
// in the integrated BlobDB impl is made in subsequent call to
|
||||
// PrepareOutput() and its callees.
|
||||
if (!compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
status_ = Status::NotSupported(
|
||||
"Only stacked BlobDB's internal compaction filter can return "
|
||||
"kChangeBlobIndex.");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
if (ikey_.type == kTypeValue) {
|
||||
// value transfer from inlined data to blob file
|
||||
ikey_.type = kTypeBlobIndex;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
value_ = compaction_filter_value_;
|
||||
} else if (filter == CompactionFilter::Decision::kIOError) {
|
||||
if (!compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
status_ = Status::NotSupported(
|
||||
"CompactionFilter for integrated BlobDB should not return kIOError");
|
||||
valid_ = false;
|
||||
return false;
|
||||
}
|
||||
status_ = Status::IOError("Failed to access blob during compaction filter");
|
||||
error = true;
|
||||
}
|
||||
return true;
|
||||
return !error;
|
||||
}
|
||||
|
||||
void CompactionIterator::NextFromInput() {
|
||||
at_next_ = false;
|
||||
valid_ = false;
|
||||
|
||||
while (!valid_ && input_->Valid() && !IsPausingManualCompaction() &&
|
||||
while (!valid_ && input_.Valid() && !IsPausingManualCompaction() &&
|
||||
!IsShuttingDown()) {
|
||||
key_ = input_->key();
|
||||
value_ = input_->value();
|
||||
key_ = input_.key();
|
||||
value_ = input_.value();
|
||||
iter_stats_.num_input_records++;
|
||||
|
||||
Status pikStatus = ParseInternalKey(key_, &ikey_);
|
||||
if (!pikStatus.ok()) {
|
||||
Status pik_status = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
if (!pik_status.ok()) {
|
||||
iter_stats_.num_input_corrupt_records++;
|
||||
|
||||
// If `expect_valid_internal_key_` is false, return the corrupted key
|
||||
// and let the caller decide what to do with it.
|
||||
// TODO(noetzli): We should have a more elegant solution for this.
|
||||
if (expect_valid_internal_key_) {
|
||||
std::string msg("Corrupted internal key not expected.");
|
||||
if (allow_data_in_errors_) {
|
||||
msg.append(" Corrupt key: " + ikey_.user_key.ToString(/*hex=*/true) +
|
||||
". ");
|
||||
msg.append("key type: " + std::to_string(ikey_.type) + ".");
|
||||
msg.append("seq: " + std::to_string(ikey_.sequence) + ".");
|
||||
}
|
||||
status_ = Status::Corruption(msg.c_str());
|
||||
status_ = pik_status;
|
||||
return;
|
||||
}
|
||||
key_ = current_key_.SetInternalKey(key_);
|
||||
@@ -298,7 +388,8 @@ void CompactionIterator::NextFromInput() {
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionIterator:ProcessKV", &ikey_);
|
||||
|
||||
// Update input statistics
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion) {
|
||||
if (ikey_.type == kTypeDeletion || ikey_.type == kTypeSingleDeletion ||
|
||||
ikey_.type == kTypeDeletionWithTimestamp) {
|
||||
iter_stats_.num_input_deletion_records++;
|
||||
}
|
||||
iter_stats_.total_input_raw_key_bytes += key_.size();
|
||||
@@ -311,19 +402,54 @@ void CompactionIterator::NextFromInput() {
|
||||
// merge_helper_->compaction_filter_skip_until_.
|
||||
Slice skip_until;
|
||||
|
||||
bool user_key_equal_without_ts = false;
|
||||
int cmp_ts = 0;
|
||||
if (has_current_user_key_) {
|
||||
user_key_equal_without_ts =
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, current_user_key_);
|
||||
// if timestamp_size_ > 0, then curr_ts_ has been initialized by a
|
||||
// previous key.
|
||||
cmp_ts = timestamp_size_ ? cmp_->CompareTimestamp(
|
||||
ExtractTimestampFromUserKey(
|
||||
ikey_.user_key, timestamp_size_),
|
||||
curr_ts_)
|
||||
: 0;
|
||||
}
|
||||
|
||||
// Check whether the user key changed. After this if statement current_key_
|
||||
// is a copy of the current input key (maybe converted to a delete by the
|
||||
// compaction filter). ikey_.user_key is pointing to the copy.
|
||||
if (!has_current_user_key_ ||
|
||||
!cmp_->Equal(ikey_.user_key, current_user_key_)) {
|
||||
if (!has_current_user_key_ || !user_key_equal_without_ts || cmp_ts != 0) {
|
||||
// First occurrence of this user key
|
||||
// Copy key for output
|
||||
key_ = current_key_.SetInternalKey(key_, &ikey_);
|
||||
|
||||
// If timestamp_size_ > 0, then copy from ikey_ to curr_ts_ for the use
|
||||
// in next iteration to compare with the timestamp of next key.
|
||||
UpdateTimestampAndCompareWithFullHistoryLow();
|
||||
|
||||
// If
|
||||
// (1) !has_current_user_key_, OR
|
||||
// (2) timestamp is disabled, OR
|
||||
// (3) all history will be preserved, OR
|
||||
// (4) user key (excluding timestamp) is different from previous key, OR
|
||||
// (5) timestamp is NO older than *full_history_ts_low_
|
||||
// then current_user_key_ must be treated as a different user key.
|
||||
// This means, if a user key (excluding ts) is the same as the previous
|
||||
// user key, and its ts is older than *full_history_ts_low_, then we
|
||||
// consider this key for GC, e.g. it may be dropped if certain conditions
|
||||
// match.
|
||||
if (!has_current_user_key_ || !timestamp_size_ || !full_history_ts_low_ ||
|
||||
!user_key_equal_without_ts || cmp_with_history_ts_low_ >= 0) {
|
||||
// Initialize for future comparison for rule (A) and etc.
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
has_current_user_key_ = true;
|
||||
}
|
||||
current_user_key_ = ikey_.user_key;
|
||||
has_current_user_key_ = true;
|
||||
|
||||
has_outputted_key_ = false;
|
||||
current_user_key_sequence_ = kMaxSequenceNumber;
|
||||
current_user_key_snapshot_ = 0;
|
||||
|
||||
current_key_committed_ = KeyCommitted(ikey_.sequence);
|
||||
|
||||
// Apply the compaction filter to the first committed version of the user
|
||||
@@ -381,8 +507,8 @@ void CompactionIterator::NextFromInput() {
|
||||
// In the previous iteration we encountered a single delete that we could
|
||||
// not compact out. We will keep this Put, but can drop it's data.
|
||||
// (See Optimization 3, below.)
|
||||
assert(ikey_.type == kTypeValue);
|
||||
if (ikey_.type != kTypeValue) {
|
||||
assert(ikey_.type == kTypeValue || ikey_.type == kTypeBlobIndex);
|
||||
if (ikey_.type != kTypeValue && ikey_.type != kTypeBlobIndex) {
|
||||
ROCKS_LOG_FATAL(info_log_,
|
||||
"Unexpected key type %d for compaction output",
|
||||
ikey_.type);
|
||||
@@ -395,6 +521,11 @@ void CompactionIterator::NextFromInput() {
|
||||
current_user_key_snapshot_, last_snapshot);
|
||||
}
|
||||
|
||||
if (ikey_.type == kTypeBlobIndex) {
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
|
||||
value_.clear();
|
||||
valid_ = true;
|
||||
clear_and_output_next_key_ = false;
|
||||
@@ -434,12 +565,13 @@ void CompactionIterator::NextFromInput() {
|
||||
// The easiest way to process a SingleDelete during iteration is to peek
|
||||
// ahead at the next key.
|
||||
ParsedInternalKey next_ikey;
|
||||
input_->Next();
|
||||
AdvanceInputIter();
|
||||
|
||||
// Check whether the next key exists, is not corrupt, and is the same key
|
||||
// as the single delete.
|
||||
if (input_->Valid() &&
|
||||
ParseInternalKey(input_->key(), &next_ikey) == Status::OK() &&
|
||||
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.
|
||||
@@ -452,7 +584,7 @@ void CompactionIterator::NextFromInput() {
|
||||
// to handle the second SingleDelete
|
||||
|
||||
// First SingleDelete has been skipped since we already called
|
||||
// input_->Next().
|
||||
// input_.Next().
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
++iter_stats_.num_single_del_mismatch;
|
||||
} else if (has_outputted_key_ ||
|
||||
@@ -474,9 +606,9 @@ void CompactionIterator::NextFromInput() {
|
||||
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
// Already called input_->Next() once. Call it a second time to
|
||||
// Already called input_.Next() once. Call it a second time to
|
||||
// skip past the second key.
|
||||
input_->Next();
|
||||
AdvanceInputIter();
|
||||
} else {
|
||||
// Found a matching value, but we cannot drop both keys since
|
||||
// there is an earlier snapshot and we need to leave behind a record
|
||||
@@ -543,9 +675,12 @@ void CompactionIterator::NextFromInput() {
|
||||
last_sequence, current_user_key_sequence_);
|
||||
}
|
||||
|
||||
++iter_stats_.num_record_drop_hidden; // (A)
|
||||
input_->Next();
|
||||
} else if (compaction_ != nullptr && ikey_.type == kTypeDeletion &&
|
||||
++iter_stats_.num_record_drop_hidden; // rule (A)
|
||||
AdvanceInputIter();
|
||||
} else if (compaction_ != nullptr &&
|
||||
(ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
IN_EARLIEST_SNAPSHOT(ikey_.sequence) &&
|
||||
ikeyNotNeededForIncrementalSnapshot() &&
|
||||
compaction_->KeyNotExistsBeyondOutputLevel(ikey_.user_key,
|
||||
@@ -569,35 +704,47 @@ void CompactionIterator::NextFromInput() {
|
||||
// given that:
|
||||
// (1) The deletion is earlier than earliest_write_conflict_snapshot, and
|
||||
// (2) No value exist earlier than the deletion.
|
||||
//
|
||||
// Note also that a deletion marker of type kTypeDeletionWithTimestamp
|
||||
// will be treated as a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
++iter_stats_.num_record_drop_obsolete;
|
||||
if (!bottommost_level_) {
|
||||
++iter_stats_.num_optimized_del_drop_obsolete;
|
||||
}
|
||||
input_->Next();
|
||||
} else if ((ikey_.type == kTypeDeletion) && bottommost_level_ &&
|
||||
ikeyNotNeededForIncrementalSnapshot()) {
|
||||
AdvanceInputIter();
|
||||
} else if ((ikey_.type == kTypeDeletion ||
|
||||
(ikey_.type == kTypeDeletionWithTimestamp &&
|
||||
cmp_with_history_ts_low_ < 0)) &&
|
||||
bottommost_level_ && ikeyNotNeededForIncrementalSnapshot()) {
|
||||
// Handle the case where we have a delete key at the bottom most level
|
||||
// We can skip outputting the key iff there are no subsequent puts for this
|
||||
// key
|
||||
assert(!compaction_ || compaction_->KeyNotExistsBeyondOutputLevel(
|
||||
ikey_.user_key, &level_ptrs_));
|
||||
ParsedInternalKey next_ikey;
|
||||
input_->Next();
|
||||
// Skip over all versions of this key that happen to occur in the same snapshot
|
||||
// range as the delete
|
||||
AdvanceInputIter();
|
||||
// Skip over all versions of this key that happen to occur in the same
|
||||
// snapshot range as the delete.
|
||||
//
|
||||
// Note that a deletion marker of type kTypeDeletionWithTimestamp will be
|
||||
// considered to have a different user key unless the timestamp is older
|
||||
// than *full_history_ts_low_.
|
||||
while (!IsPausingManualCompaction() && !IsShuttingDown() &&
|
||||
input_->Valid() &&
|
||||
(ParseInternalKey(input_->key(), &next_ikey) == Status::OK()) &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key) &&
|
||||
input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key) &&
|
||||
(prev_snapshot == 0 ||
|
||||
DEFINITELY_NOT_IN_SNAPSHOT(next_ikey.sequence, prev_snapshot))) {
|
||||
input_->Next();
|
||||
AdvanceInputIter();
|
||||
}
|
||||
// If you find you still need to output a row with this key, we need to output the
|
||||
// delete too
|
||||
if (input_->Valid() &&
|
||||
(ParseInternalKey(input_->key(), &next_ikey) == Status::OK()) &&
|
||||
cmp_->Equal(ikey_.user_key, next_ikey.user_key)) {
|
||||
if (input_.Valid() &&
|
||||
(ParseInternalKey(input_.key(), &next_ikey, allow_data_in_errors_)
|
||||
.ok()) &&
|
||||
cmp_->EqualWithoutTimestamp(ikey_.user_key, next_ikey.user_key)) {
|
||||
valid_ = true;
|
||||
at_next_ = true;
|
||||
}
|
||||
@@ -613,8 +760,9 @@ void CompactionIterator::NextFromInput() {
|
||||
// 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_);
|
||||
Status s =
|
||||
merge_helper_->MergeUntil(&input_, range_del_agg_, prev_snapshot,
|
||||
bottommost_level_, allow_data_in_errors_);
|
||||
merge_out_iter_.SeekToFirst();
|
||||
|
||||
if (!s.ok() && !s.IsMergeInProgress()) {
|
||||
@@ -625,13 +773,13 @@ void CompactionIterator::NextFromInput() {
|
||||
// These will be correctly set below.
|
||||
key_ = merge_out_iter_.key();
|
||||
value_ = merge_out_iter_.value();
|
||||
pikStatus = ParseInternalKey(key_, &ikey_);
|
||||
pik_status = ParseInternalKey(key_, &ikey_, allow_data_in_errors_);
|
||||
// MergeUntil stops when it encounters a corrupt key and does not
|
||||
// include them in the result, so we expect the keys here to valid.
|
||||
assert(pikStatus.ok());
|
||||
if (!pikStatus.ok()) {
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key (%s) in compaction",
|
||||
key_.ToString(true).c_str());
|
||||
assert(pik_status.ok());
|
||||
if (!pik_status.ok()) {
|
||||
ROCKS_LOG_FATAL(info_log_, "Invalid key in compaction. %s",
|
||||
pik_status.getState());
|
||||
}
|
||||
// Keep current_key_ in sync.
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
@@ -657,14 +805,14 @@ void CompactionIterator::NextFromInput() {
|
||||
if (should_delete) {
|
||||
++iter_stats_.num_record_drop_hidden;
|
||||
++iter_stats_.num_record_drop_range_del;
|
||||
input_->Next();
|
||||
AdvanceInputIter();
|
||||
} else {
|
||||
valid_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (need_skip) {
|
||||
input_->Seek(skip_until);
|
||||
SkipUntil(skip_until);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,40 +825,142 @@ void CompactionIterator::NextFromInput() {
|
||||
}
|
||||
}
|
||||
|
||||
bool CompactionIterator::ExtractLargeValueIfNeededImpl() {
|
||||
if (!blob_file_builder_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
blob_index_.clear();
|
||||
const Status s = blob_file_builder_->Add(user_key(), value_, &blob_index_);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (blob_index_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value_ = blob_index_;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CompactionIterator::ExtractLargeValueIfNeeded() {
|
||||
assert(ikey_.type == kTypeValue);
|
||||
|
||||
if (!ExtractLargeValueIfNeededImpl()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ikey_.type = kTypeBlobIndex;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
|
||||
void CompactionIterator::GarbageCollectBlobIfNeeded() {
|
||||
assert(ikey_.type == kTypeBlobIndex);
|
||||
|
||||
if (!compaction_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// GC for integrated BlobDB
|
||||
if (compaction_->enable_blob_garbage_collection()) {
|
||||
BlobIndex blob_index;
|
||||
|
||||
{
|
||||
const Status s = blob_index.DecodeFrom(value_);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (blob_index.IsInlined() || blob_index.HasTTL()) {
|
||||
status_ = Status::Corruption("Unexpected TTL/inlined blob index");
|
||||
valid_ = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob_index.file_number() >=
|
||||
blob_garbage_collection_cutoff_file_number_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Version* const version = compaction_->input_version();
|
||||
assert(version);
|
||||
|
||||
uint64_t bytes_read = 0;
|
||||
|
||||
{
|
||||
const Status s = version->GetBlob(ReadOptions(), user_key(), blob_index,
|
||||
&blob_value_, &bytes_read);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
++iter_stats_.num_blobs_read;
|
||||
iter_stats_.total_blob_bytes_read += bytes_read;
|
||||
|
||||
value_ = blob_value_;
|
||||
|
||||
if (ExtractLargeValueIfNeededImpl()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ikey_.type = kTypeValue;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// GC for stacked BlobDB
|
||||
if (compaction_filter_ &&
|
||||
compaction_filter_->IsStackedBlobDbInternalCompactionFilter()) {
|
||||
const auto blob_decision = compaction_filter_->PrepareBlobOutput(
|
||||
user_key(), value_, &compaction_filter_value_);
|
||||
|
||||
if (blob_decision == CompactionFilter::BlobDecision::kCorruption) {
|
||||
status_ =
|
||||
Status::Corruption("Corrupted blob reference encountered during GC");
|
||||
valid_ = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob_decision == CompactionFilter::BlobDecision::kIOError) {
|
||||
status_ = Status::IOError("Could not relocate blob during GC");
|
||||
valid_ = false;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (blob_decision == CompactionFilter::BlobDecision::kChangeValue) {
|
||||
value_ = compaction_filter_value_;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CompactionIterator::PrepareOutput() {
|
||||
if (valid_) {
|
||||
if (ikey_.type == kTypeValue) {
|
||||
if (blob_file_builder_) {
|
||||
blob_index_.clear();
|
||||
const Status s =
|
||||
blob_file_builder_->Add(user_key(), value_, &blob_index_);
|
||||
|
||||
if (!s.ok()) {
|
||||
status_ = s;
|
||||
valid_ = false;
|
||||
} else if (!blob_index_.empty()) {
|
||||
value_ = blob_index_;
|
||||
ikey_.type = kTypeBlobIndex;
|
||||
current_key_.UpdateInternalKey(ikey_.sequence, ikey_.type);
|
||||
}
|
||||
}
|
||||
ExtractLargeValueIfNeeded();
|
||||
} else if (ikey_.type == kTypeBlobIndex) {
|
||||
if (compaction_filter_) {
|
||||
const auto blob_decision = compaction_filter_->PrepareBlobOutput(
|
||||
user_key(), value_, &compaction_filter_value_);
|
||||
|
||||
if (blob_decision == CompactionFilter::BlobDecision::kCorruption) {
|
||||
status_ = Status::Corruption(
|
||||
"Corrupted blob reference encountered during GC");
|
||||
valid_ = false;
|
||||
} else if (blob_decision == CompactionFilter::BlobDecision::kIOError) {
|
||||
status_ = Status::IOError("Could not relocate blob during GC");
|
||||
valid_ = false;
|
||||
} else if (blob_decision ==
|
||||
CompactionFilter::BlobDecision::kChangeValue) {
|
||||
value_ = compaction_filter_value_;
|
||||
}
|
||||
}
|
||||
GarbageCollectBlobIfNeeded();
|
||||
}
|
||||
|
||||
// Zeroing out the sequence number leads to better compression.
|
||||
@@ -735,7 +985,18 @@ void CompactionIterator::PrepareOutput() {
|
||||
ikey_.type);
|
||||
}
|
||||
ikey_.sequence = 0;
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
if (!timestamp_size_) {
|
||||
current_key_.UpdateInternalKey(0, ikey_.type);
|
||||
} else if (full_history_ts_low_ && cmp_with_history_ts_low_ < 0) {
|
||||
// We can also zero out timestamp for better compression.
|
||||
// For the same user key (excluding timestamp), the timestamp-based
|
||||
// history can be collapsed to save some space if the timestamp is
|
||||
// older than *full_history_ts_low_.
|
||||
const std::string kTsMin(timestamp_size_, static_cast<char>(0));
|
||||
const Slice ts_slice = kTsMin;
|
||||
ikey_.SetTimestamp(ts_slice);
|
||||
current_key_.UpdateInternalKey(0, ikey_.type, &ts_slice);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -827,4 +1088,30 @@ bool CompactionIterator::IsInEarliestSnapshot(SequenceNumber sequence) {
|
||||
return in_snapshot == SnapshotCheckerResult::kInSnapshot;
|
||||
}
|
||||
|
||||
uint64_t CompactionIterator::ComputeBlobGarbageCollectionCutoffFileNumber(
|
||||
const CompactionProxy* compaction) {
|
||||
if (!compaction) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!compaction->enable_blob_garbage_collection()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Version* const version = compaction->input_version();
|
||||
assert(version);
|
||||
|
||||
const VersionStorageInfo* const storage_info = version->storage_info();
|
||||
assert(storage_info);
|
||||
|
||||
const auto& blob_files = storage_info->GetBlobFiles();
|
||||
|
||||
auto it = blob_files.begin();
|
||||
std::advance(
|
||||
it, compaction->blob_garbage_collection_age_cutoff() * blob_files.size());
|
||||
|
||||
return it != blob_files.end() ? it->first
|
||||
: std::numeric_limits<uint64_t>::max();
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cinttypes>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
@@ -23,76 +24,166 @@ namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class BlobFileBuilder;
|
||||
|
||||
// A wrapper of internal iterator whose purpose is to count how
|
||||
// many entries there are in the iterator.
|
||||
class SequenceIterWrapper : public InternalIterator {
|
||||
public:
|
||||
SequenceIterWrapper(InternalIterator* iter, const Comparator* cmp,
|
||||
bool 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 {
|
||||
num_itered_++;
|
||||
inner_iter_->Next();
|
||||
}
|
||||
void Seek(const Slice& target) override {
|
||||
if (!need_count_entries_) {
|
||||
inner_iter_->Seek(target);
|
||||
} else {
|
||||
// For flush cases, we need to count total number of entries, so we
|
||||
// do Next() rather than Seek().
|
||||
while (inner_iter_->Valid() &&
|
||||
icmp_.Compare(inner_iter_->key(), target) < 0) {
|
||||
Next();
|
||||
}
|
||||
}
|
||||
}
|
||||
Slice key() const override { return inner_iter_->key(); }
|
||||
Slice value() const override { return inner_iter_->value(); }
|
||||
|
||||
// Unused InternalIterator methods
|
||||
void SeekToFirst() override { assert(false); }
|
||||
void Prev() override { assert(false); }
|
||||
void SeekForPrev(const Slice& /* target */) override { assert(false); }
|
||||
void SeekToLast() override { assert(false); }
|
||||
|
||||
uint64_t num_itered() const { return num_itered_; }
|
||||
|
||||
private:
|
||||
InternalKeyComparator icmp_;
|
||||
InternalIterator* inner_iter_; // not owned
|
||||
uint64_t num_itered_ = 0;
|
||||
bool need_count_entries_;
|
||||
};
|
||||
|
||||
class CompactionIterator {
|
||||
public:
|
||||
// A wrapper around Compaction. Has a much smaller interface, only what
|
||||
// CompactionIterator uses. Tests can override it.
|
||||
class CompactionProxy {
|
||||
public:
|
||||
explicit CompactionProxy(const Compaction* compaction)
|
||||
: compaction_(compaction) {}
|
||||
|
||||
virtual ~CompactionProxy() = default;
|
||||
virtual int level(size_t /*compaction_input_level*/ = 0) const {
|
||||
return compaction_->level();
|
||||
}
|
||||
|
||||
virtual int level() const = 0;
|
||||
|
||||
virtual bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const {
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const = 0;
|
||||
|
||||
virtual bool bottommost_level() const = 0;
|
||||
|
||||
virtual int number_levels() const = 0;
|
||||
|
||||
virtual Slice GetLargestUserKey() const = 0;
|
||||
|
||||
virtual bool allow_ingest_behind() const = 0;
|
||||
|
||||
virtual bool preserve_deletes() const = 0;
|
||||
|
||||
virtual bool enable_blob_garbage_collection() const = 0;
|
||||
|
||||
virtual double blob_garbage_collection_age_cutoff() const = 0;
|
||||
|
||||
virtual Version* input_version() const = 0;
|
||||
};
|
||||
|
||||
class RealCompaction : public CompactionProxy {
|
||||
public:
|
||||
explicit RealCompaction(const Compaction* compaction)
|
||||
: compaction_(compaction) {
|
||||
assert(compaction_);
|
||||
assert(compaction_->immutable_options());
|
||||
assert(compaction_->mutable_cf_options());
|
||||
}
|
||||
|
||||
int level() const override { return compaction_->level(); }
|
||||
|
||||
bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& user_key, std::vector<size_t>* level_ptrs) const override {
|
||||
return compaction_->KeyNotExistsBeyondOutputLevel(user_key, level_ptrs);
|
||||
}
|
||||
virtual bool bottommost_level() const {
|
||||
|
||||
bool bottommost_level() const override {
|
||||
return compaction_->bottommost_level();
|
||||
}
|
||||
virtual int number_levels() const { return compaction_->number_levels(); }
|
||||
virtual Slice GetLargestUserKey() const {
|
||||
|
||||
int number_levels() const override { return compaction_->number_levels(); }
|
||||
|
||||
Slice GetLargestUserKey() const override {
|
||||
return compaction_->GetLargestUserKey();
|
||||
}
|
||||
virtual bool allow_ingest_behind() const {
|
||||
return compaction_->immutable_cf_options()->allow_ingest_behind;
|
||||
}
|
||||
virtual bool preserve_deletes() const {
|
||||
return compaction_->immutable_cf_options()->preserve_deletes;
|
||||
|
||||
bool allow_ingest_behind() const override {
|
||||
return compaction_->immutable_options()->allow_ingest_behind;
|
||||
}
|
||||
|
||||
protected:
|
||||
CompactionProxy() = default;
|
||||
bool preserve_deletes() const override {
|
||||
return compaction_->immutable_options()->preserve_deletes;
|
||||
}
|
||||
|
||||
bool enable_blob_garbage_collection() const override {
|
||||
return compaction_->mutable_cf_options()->enable_blob_garbage_collection;
|
||||
}
|
||||
|
||||
double blob_garbage_collection_age_cutoff() const override {
|
||||
return compaction_->mutable_cf_options()
|
||||
->blob_garbage_collection_age_cutoff;
|
||||
}
|
||||
|
||||
Version* input_version() const override {
|
||||
return compaction_->input_version();
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
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();
|
||||
|
||||
@@ -116,16 +207,36 @@ class CompactionIterator {
|
||||
bool Valid() const { return valid_; }
|
||||
const Slice& user_key() const { return current_user_key_; }
|
||||
const CompactionIterationStats& iter_stats() const { return iter_stats_; }
|
||||
uint64_t num_input_entry_scanned() const { return input_.num_itered(); }
|
||||
|
||||
private:
|
||||
// Processes the input stream to find the next output
|
||||
void NextFromInput();
|
||||
|
||||
// Do last preparations before presenting the output to the callee. At this
|
||||
// point this only zeroes out the sequence number if possible for better
|
||||
// compression.
|
||||
// Do final preparations before presenting the output to the callee.
|
||||
void PrepareOutput();
|
||||
|
||||
// Passes the output value to the blob file builder (if any), and replaces it
|
||||
// with the corresponding blob reference if it has been actually written to a
|
||||
// blob file (i.e. if it passed the value size check). Returns true if the
|
||||
// value got extracted to a blob file, false otherwise.
|
||||
bool ExtractLargeValueIfNeededImpl();
|
||||
|
||||
// Extracts large values as described above, and updates the internal key's
|
||||
// type to kTypeBlobIndex if the value got extracted. Should only be called
|
||||
// for regular values (kTypeValue).
|
||||
void ExtractLargeValueIfNeeded();
|
||||
|
||||
// Relocates valid blobs residing in the oldest blob files if garbage
|
||||
// collection is enabled. Relocated blobs are written to new blob files or
|
||||
// inlined in the LSM tree depending on the current settings (i.e.
|
||||
// enable_blob_files and min_blob_size). Should only be called for blob
|
||||
// references (kTypeBlobIndex).
|
||||
//
|
||||
// Note: the stacked BlobDB implementation's compaction filter based GC
|
||||
// algorithm is also called from here.
|
||||
void GarbageCollectBlobIfNeeded();
|
||||
|
||||
// Invoke compaction filter if needed.
|
||||
// Return true on success, false on failures (e.g.: kIOError).
|
||||
bool InvokeFilterIfNeeded(bool* need_skip, Slice* skip_until);
|
||||
@@ -152,7 +263,24 @@ class CompactionIterator {
|
||||
|
||||
bool IsInEarliestSnapshot(SequenceNumber sequence);
|
||||
|
||||
InternalIterator* input_;
|
||||
// Extract user-defined timestamp from user key if possible and compare it
|
||||
// with *full_history_ts_low_ if applicable.
|
||||
inline void UpdateTimestampAndCompareWithFullHistoryLow() {
|
||||
if (!timestamp_size_) {
|
||||
return;
|
||||
}
|
||||
Slice ts = ExtractTimestampFromUserKey(ikey_.user_key, timestamp_size_);
|
||||
curr_ts_.assign(ts.data(), ts.size());
|
||||
if (full_history_ts_low_) {
|
||||
cmp_with_history_ts_low_ =
|
||||
cmp_->CompareTimestamp(ts, *full_history_ts_low_);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t ComputeBlobGarbageCollectionCutoffFileNumber(
|
||||
const CompactionProxy* compaction);
|
||||
|
||||
SequenceIterWrapper input_;
|
||||
const Comparator* cmp_;
|
||||
MergeHelper* merge_helper_;
|
||||
const std::vector<SequenceNumber>* snapshots_;
|
||||
@@ -166,6 +294,7 @@ class CompactionIterator {
|
||||
const SequenceNumber earliest_write_conflict_snapshot_;
|
||||
const SnapshotChecker* const snapshot_checker_;
|
||||
Env* env_;
|
||||
SystemClock* clock_;
|
||||
bool report_detailed_time_;
|
||||
bool expect_valid_internal_key_;
|
||||
CompactionRangeDelAggregator* range_del_agg_;
|
||||
@@ -174,6 +303,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;
|
||||
@@ -181,6 +311,20 @@ class CompactionIterator {
|
||||
SequenceNumber earliest_snapshot_;
|
||||
SequenceNumber latest_snapshot_;
|
||||
|
||||
std::shared_ptr<Logger> info_log_;
|
||||
|
||||
bool allow_data_in_errors_;
|
||||
|
||||
// Comes from comparator.
|
||||
const size_t timestamp_size_;
|
||||
|
||||
// Lower bound timestamp to retain full history in terms of user-defined
|
||||
// timestamp. If a key's timestamp is older than full_history_ts_low_, then
|
||||
// the key *may* be eligible for garbage collection (GC). The skipping logic
|
||||
// is in `NextFromInput()` and `PrepareOutput()`.
|
||||
// If nullptr, NO GC will be performed and all history will be preserved.
|
||||
const std::string* const full_history_ts_low_;
|
||||
|
||||
// State
|
||||
//
|
||||
// Points to a copy of the current compaction iterator output (current_key_)
|
||||
@@ -199,11 +343,13 @@ class CompactionIterator {
|
||||
// Stores whether ikey_.user_key is valid. If set to false, the user key is
|
||||
// not compared against the current key in the underlying iterator.
|
||||
bool has_current_user_key_ = false;
|
||||
bool at_next_ = false; // If false, the iterator
|
||||
// Holds a copy of the current compaction iterator output (or current key in
|
||||
// the underlying iterator during NextFromInput()).
|
||||
// If false, the iterator holds a copy of the current compaction iterator
|
||||
// output (or current key in the underlying iterator during NextFromInput()).
|
||||
bool at_next_ = false;
|
||||
|
||||
IterKey current_key_;
|
||||
Slice current_user_key_;
|
||||
std::string curr_ts_;
|
||||
SequenceNumber current_user_key_sequence_;
|
||||
SequenceNumber current_user_key_snapshot_;
|
||||
|
||||
@@ -218,7 +364,11 @@ class CompactionIterator {
|
||||
// PinnedIteratorsManager used to pin input_ Iterator blocks while reading
|
||||
// merge operands and then releasing them after consuming them.
|
||||
PinnedIteratorsManager pinned_iters_mgr_;
|
||||
|
||||
uint64_t blob_garbage_collection_cutoff_file_number_;
|
||||
|
||||
std::string blob_index_;
|
||||
PinnableSlice blob_value_;
|
||||
std::string compaction_filter_value_;
|
||||
InternalKey compaction_filter_skip_until_;
|
||||
// "level_ptrs" holds indices that remember which file of an associated
|
||||
@@ -233,9 +383,15 @@ class CompactionIterator {
|
||||
// Used to avoid purging uncommitted values. The application can specify
|
||||
// uncommitted values by providing a SnapshotChecker object.
|
||||
bool current_key_committed_;
|
||||
std::shared_ptr<Logger> info_log_;
|
||||
|
||||
bool allow_data_in_errors_;
|
||||
// Saved result of ucmp->CompareTimestamp(current_ts_, *full_history_ts_low_)
|
||||
int cmp_with_history_ts_low_;
|
||||
|
||||
const int level_;
|
||||
|
||||
void AdvanceInputIter() { input_.Next(); }
|
||||
|
||||
void SkipUntil(const Slice& skip_until) { input_.Seek(skip_until); }
|
||||
|
||||
bool IsShuttingDown() {
|
||||
// This is a best-effort facility, so memory_order_relaxed is sufficient.
|
||||
@@ -244,8 +400,10 @@ 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));
|
||||
}
|
||||
};
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -38,7 +38,7 @@ class NoMergingMergeOp : public MergeOperator {
|
||||
|
||||
// Compaction filter that gets stuck when it sees a particular key,
|
||||
// then gets unstuck when told to.
|
||||
// Always returns Decition::kRemove.
|
||||
// Always returns Decision::kRemove.
|
||||
class StallingFilter : public CompactionFilter {
|
||||
public:
|
||||
Decision FilterV2(int /*level*/, const Slice& key, ValueType /*type*/,
|
||||
@@ -156,23 +156,32 @@ class LoggingForwardVectorIterator : public InternalIterator {
|
||||
|
||||
class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
public:
|
||||
FakeCompaction() = default;
|
||||
int level() const override { return 0; }
|
||||
|
||||
int level(size_t /*compaction_input_level*/) const override { return 0; }
|
||||
bool KeyNotExistsBeyondOutputLevel(
|
||||
const Slice& /*user_key*/,
|
||||
std::vector<size_t>* /*level_ptrs*/) const override {
|
||||
return is_bottommost_level || key_not_exists_beyond_output_level;
|
||||
}
|
||||
|
||||
bool bottommost_level() const override { return is_bottommost_level; }
|
||||
|
||||
int number_levels() const override { return 1; }
|
||||
|
||||
Slice GetLargestUserKey() const override {
|
||||
return "\xff\xff\xff\xff\xff\xff\xff\xff\xff";
|
||||
}
|
||||
|
||||
bool allow_ingest_behind() const override { return is_allow_ingest_behind; }
|
||||
|
||||
bool preserve_deletes() const override { return false; }
|
||||
|
||||
bool enable_blob_garbage_collection() const override { return false; }
|
||||
|
||||
double blob_garbage_collection_age_cutoff() const override { return 0.0; }
|
||||
|
||||
Version* input_version() const override { return nullptr; }
|
||||
|
||||
bool key_not_exists_beyond_output_level = false;
|
||||
|
||||
bool is_bottommost_level = false;
|
||||
@@ -180,7 +189,7 @@ class FakeCompaction : public CompactionIterator::CompactionProxy {
|
||||
bool is_allow_ingest_behind = false;
|
||||
};
|
||||
|
||||
// A simplifed snapshot checker which assumes each snapshot has a global
|
||||
// A simplified snapshot checker which assumes each snapshot has a global
|
||||
// last visible sequence.
|
||||
class TestSnapshotChecker : public SnapshotChecker {
|
||||
public:
|
||||
@@ -216,6 +225,9 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
CompactionIteratorTest()
|
||||
: cmp_(BytewiseComparator()), icmp_(cmp_), snapshots_({}) {}
|
||||
|
||||
explicit CompactionIteratorTest(const Comparator* ucmp)
|
||||
: cmp_(ucmp), icmp_(cmp_), snapshots_({}) {}
|
||||
|
||||
void InitIterators(
|
||||
const std::vector<std::string>& ks, const std::vector<std::string>& vs,
|
||||
const std::vector<std::string>& range_del_ks,
|
||||
@@ -224,7 +236,9 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
SequenceNumber last_committed_sequence = kMaxSequenceNumber,
|
||||
MergeOperator* merge_op = nullptr, CompactionFilter* filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
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));
|
||||
auto tombstone_list = std::make_shared<FragmentedRangeTombstoneList>(
|
||||
@@ -236,10 +250,12 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
range_del_agg_->AddTombstones(std::move(range_del_iter));
|
||||
|
||||
std::unique_ptr<CompactionIterator::CompactionProxy> compaction;
|
||||
if (filter || bottommost_level) {
|
||||
if (filter || bottommost_level || key_not_exists_beyond_output_level) {
|
||||
compaction_proxy_ = new FakeCompaction();
|
||||
compaction_proxy_->is_bottommost_level = bottommost_level;
|
||||
compaction_proxy_->is_allow_ingest_behind = AllowIngestBehind();
|
||||
compaction_proxy_->key_not_exists_beyond_output_level =
|
||||
key_not_exists_beyond_output_level;
|
||||
compaction.reset(compaction_proxy_);
|
||||
}
|
||||
bool use_snapshot_checker = UseSnapshotChecker() || GetParam();
|
||||
@@ -252,6 +268,11 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
0 /*latest_snapshot*/, snapshot_checker_.get(),
|
||||
0 /*level*/, nullptr /*statistics*/, &shutting_down_));
|
||||
|
||||
if (c_iter_) {
|
||||
// Since iter_ is still used in ~CompactionIterator(), we call
|
||||
// ~CompactionIterator() first.
|
||||
c_iter_.reset();
|
||||
}
|
||||
iter_.reset(new LoggingForwardVectorIterator(ks, vs));
|
||||
iter_->SeekToFirst();
|
||||
c_iter_.reset(new CompactionIterator(
|
||||
@@ -259,8 +280,11 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
earliest_write_conflict_snapshot, snapshot_checker_.get(),
|
||||
Env::Default(), false /* report_detailed_time */, false,
|
||||
range_del_agg_.get(), nullptr /* blob_file_builder */,
|
||||
false /*allow_data_in_errors*/, std::move(compaction), filter,
|
||||
&shutting_down_));
|
||||
true /*allow_data_in_errors*/, std::move(compaction), filter,
|
||||
&shutting_down_, /*preserve_deletes_seqnum=*/0,
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, /*info_log=*/nullptr,
|
||||
full_history_ts_low));
|
||||
}
|
||||
|
||||
void AddSnapshot(SequenceNumber snapshot,
|
||||
@@ -282,10 +306,13 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
MergeOperator* merge_operator = nullptr,
|
||||
CompactionFilter* compaction_filter = nullptr,
|
||||
bool bottommost_level = false,
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber) {
|
||||
SequenceNumber earliest_write_conflict_snapshot = kMaxSequenceNumber,
|
||||
bool key_not_exists_beyond_output_level = false,
|
||||
const std::string* full_history_ts_low = nullptr) {
|
||||
InitIterators(input_keys, input_values, {}, {}, kMaxSequenceNumber,
|
||||
last_committed_seq, merge_operator, compaction_filter,
|
||||
bottommost_level, earliest_write_conflict_snapshot);
|
||||
bottommost_level, earliest_write_conflict_snapshot,
|
||||
key_not_exists_beyond_output_level, full_history_ts_low);
|
||||
c_iter_->SeekToFirst();
|
||||
for (size_t i = 0; i < expected_keys.size(); i++) {
|
||||
std::string info = "i = " + ToString(i);
|
||||
@@ -299,6 +326,11 @@ class CompactionIteratorTest : public testing::TestWithParam<bool> {
|
||||
ASSERT_FALSE(c_iter_->Valid());
|
||||
}
|
||||
|
||||
void ClearSnapshots() {
|
||||
snapshots_.clear();
|
||||
snapshot_map_.clear();
|
||||
}
|
||||
|
||||
const Comparator* cmp_;
|
||||
const InternalKeyComparator icmp_;
|
||||
std::vector<SequenceNumber> snapshots_;
|
||||
@@ -680,7 +712,7 @@ TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) {
|
||||
RunTest({test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 2, kTypeValue)},
|
||||
{"v1", "v2"},
|
||||
{test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue)},
|
||||
{"v1", "v2"}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
{"v1", "v2"}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
@@ -689,15 +721,14 @@ TEST_P(CompactionIteratorTest, ZeroOutSequenceAtBottomLevel) {
|
||||
// permanently.
|
||||
TEST_P(CompactionIteratorTest, RemoveDeletionAtBottomLevel) {
|
||||
AddSnapshot(1);
|
||||
RunTest({test::KeyStr("a", 1, kTypeDeletion),
|
||||
test::KeyStr("b", 3, kTypeDeletion),
|
||||
test::KeyStr("b", 1, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 3, kTypeDeletion),
|
||||
test::KeyStr("b", 0, kTypeValue)},
|
||||
{"", ""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
RunTest(
|
||||
{test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 3, kTypeDeletion),
|
||||
test::KeyStr("b", 1, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 3, kTypeDeletion), test::KeyStr("b", 0, kTypeValue)},
|
||||
{"", ""}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
// In bottommost level, single deletions earlier than earliest snapshot can be
|
||||
@@ -707,7 +738,7 @@ TEST_P(CompactionIteratorTest, RemoveSingleDeletionAtBottomLevel) {
|
||||
RunTest({test::KeyStr("a", 1, kTypeSingleDeletion),
|
||||
test::KeyStr("b", 2, kTypeSingleDeletion)},
|
||||
{"", ""}, {test::KeyStr("b", 2, kTypeSingleDeletion)}, {""},
|
||||
kMaxSequenceNumber /*last_commited_seq*/, nullptr /*merge_operator*/,
|
||||
kMaxSequenceNumber /*last_committed_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
@@ -864,7 +895,7 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
{"v1", "v2", "v3"},
|
||||
{test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 2, kTypeValue),
|
||||
test::KeyStr("c", 3, kTypeValue)},
|
||||
{"v1", "v2", "v3"}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
{"v1", "v2", "v3"}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
@@ -875,9 +906,7 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
RunTest(
|
||||
{test::KeyStr("a", 1, kTypeDeletion), test::KeyStr("b", 2, kTypeDeletion),
|
||||
test::KeyStr("c", 3, kTypeDeletion)},
|
||||
{"", "", ""},
|
||||
{},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
{"", "", ""}, {}, {"", ""}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
@@ -885,15 +914,14 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
NotRemoveDeletionIfValuePresentToEarlierSnapshot) {
|
||||
AddSnapshot(2,1);
|
||||
RunTest(
|
||||
{test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 1, kTypeValue),
|
||||
test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("a", 4, kTypeDeletion), test::KeyStr("a", 0, kTypeValue),
|
||||
test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
RunTest({test::KeyStr("a", 4, kTypeDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue), test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""},
|
||||
{test::KeyStr("a", 4, kTypeDeletion),
|
||||
test::KeyStr("a", 0, kTypeValue), test::KeyStr("b", 3, kTypeValue)},
|
||||
{"", "", ""}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
@@ -905,7 +933,7 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
{"", "", ""},
|
||||
{test::KeyStr("b", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("c", 3, kTypeSingleDeletion)},
|
||||
{"", ""}, kMaxSequenceNumber /*last_commited_seq*/,
|
||||
{"", ""}, kMaxSequenceNumber /*last_committed_seq*/,
|
||||
nullptr /*merge_operator*/, nullptr /*compaction_filter*/,
|
||||
true /*bottommost_level*/);
|
||||
}
|
||||
@@ -939,9 +967,24 @@ TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
2 /*earliest_write_conflict_snapshot*/);
|
||||
}
|
||||
|
||||
// Same as above but with a blob index. In addition to the value getting
|
||||
// trimmed, the type of the KV is changed to kTypeValue.
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest,
|
||||
KeepSingleDeletionForWriteConflictChecking_BlobIndex) {
|
||||
AddSnapshot(2, 0);
|
||||
RunTest({test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeBlobIndex)},
|
||||
{"", "fake_blob_index"},
|
||||
{test::KeyStr("a", 2, kTypeSingleDeletion),
|
||||
test::KeyStr("a", 1, kTypeValue)},
|
||||
{"", ""}, 2 /*last_committed_seq*/, nullptr /*merge_operator*/,
|
||||
nullptr /*compaction_filter*/, false /*bottommost_level*/,
|
||||
2 /*earliest_write_conflict_snapshot*/);
|
||||
}
|
||||
|
||||
// Compaction filter should keep uncommitted key as-is, and
|
||||
// * Convert the latest velue to deletion, and/or
|
||||
// * if latest value is a merge, apply filter to all suequent merges.
|
||||
// * Convert the latest value to deletion, and/or
|
||||
// * if latest value is a merge, apply filter to all subsequent merges.
|
||||
|
||||
TEST_F(CompactionIteratorWithSnapshotCheckerTest, CompactionFilter_Value) {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter(
|
||||
@@ -1033,6 +1076,188 @@ INSTANTIATE_TEST_CASE_P(CompactionIteratorWithAllowIngestBehindTestInstance,
|
||||
CompactionIteratorWithAllowIngestBehindTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
class CompactionIteratorTsGcTest : public CompactionIteratorTest {
|
||||
public:
|
||||
CompactionIteratorTsGcTest()
|
||||
: CompactionIteratorTest(test::ComparatorWithU64Ts()) {}
|
||||
};
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, NoKeyEligibleForGC) {
|
||||
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, kTypeValue),
|
||||
test::KeyStr(/*ts=*/102, user_key[0], /*seq=*/3,
|
||||
kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/104, user_key[1], /*seq=*/5, 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, AllKeysOlderThanThreshold) {
|
||||
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,
|
||||
kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/102, user_key[0], /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key[0], /*seq=*/2, kTypeValue),
|
||||
test::KeyStr(/*ts=*/104, user_key[1], /*seq=*/5, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "a1", "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[3]};
|
||||
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, the deletion marker should be preserved because the user
|
||||
// key may appear beyond output level.
|
||||
const std::vector<std::string> expected_keys = {input_keys[0],
|
||||
input_keys[3]};
|
||||
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);
|
||||
}
|
||||
{
|
||||
// No snapshot, the deletion marker can be dropped because the user key
|
||||
// does not appear in higher levels.
|
||||
const std::vector<std::string> expected_keys = {input_keys[3]};
|
||||
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=*/true, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, NewHidesOldSameSnapshot) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key, /*seq=*/4, kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/102, user_key, /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key, /*seq=*/2, kTypeValue),
|
||||
test::KeyStr(/*ts=*/100, user_key, /*seq=*/1, kTypeValue)};
|
||||
const std::vector<std::string> input_values = {"", "a2", "a1", "a0"};
|
||||
{
|
||||
std::string full_history_ts_low;
|
||||
// Keys whose timestamps larger than or equal to 102 will be preserved.
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
const std::vector<std::string> expected_keys = {input_keys[0],
|
||||
input_keys[1]};
|
||||
const std::vector<std::string> expected_values = {"", "a2"};
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, DropTombstones) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key, /*seq=*/4, kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/102, user_key, /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key, /*seq=*/2, kTypeDeletionWithTimestamp),
|
||||
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);
|
||||
|
||||
{
|
||||
// Non-bottommost level, but key does not exist beyond output level.
|
||||
std::string full_history_ts_low;
|
||||
PutFixed64(&full_history_ts_low, 102);
|
||||
RunTest(input_keys, input_values, expected_keys, expected_values,
|
||||
/*last_committed_sequence=*/kMaxSequenceNumber,
|
||||
/*merge_op=*/nullptr, /*compaction_filter=*/nullptr,
|
||||
/*bottommost_level=*/false,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/true, &full_history_ts_low);
|
||||
}
|
||||
{
|
||||
// Bottommost level
|
||||
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=*/true,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/false, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(CompactionIteratorTsGcTest, RewriteTs) {
|
||||
constexpr char user_key[] = "a";
|
||||
const std::vector<std::string> input_keys = {
|
||||
test::KeyStr(/*ts=*/103, user_key, /*seq=*/4, kTypeDeletionWithTimestamp),
|
||||
test::KeyStr(/*ts=*/102, user_key, /*seq=*/3, kTypeValue),
|
||||
test::KeyStr(/*ts=*/101, user_key, /*seq=*/2, kTypeDeletionWithTimestamp),
|
||||
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], input_keys[2],
|
||||
test::KeyStr(/*ts=*/0, user_key, /*seq=*/0, kTypeValue)};
|
||||
const std::vector<std::string> expected_values = {"", "a2", "", "a0"};
|
||||
|
||||
AddSnapshot(1);
|
||||
AddSnapshot(2);
|
||||
|
||||
{
|
||||
// Bottommost level and need to rewrite both ts and seq.
|
||||
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=*/true,
|
||||
/*earliest_write_conflict_snapshot=*/kMaxSequenceNumber,
|
||||
/*key_not_exists_beyond_output_level=*/true, &full_history_ts_low);
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(CompactionIteratorTsGcTestInstance,
|
||||
CompactionIteratorTsGcTest,
|
||||
testing::Values(true, false));
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1093
-160
File diff suppressed because it is too large
Load Diff
+181
-25
@@ -17,6 +17,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "db/blob/blob_file_completion_callback.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_iterator.h"
|
||||
#include "db/dbformat.h"
|
||||
@@ -50,6 +51,7 @@ class Arena;
|
||||
class ErrorHandler;
|
||||
class MemTable;
|
||||
class SnapshotChecker;
|
||||
class SystemClock;
|
||||
class TableCache;
|
||||
class Version;
|
||||
class VersionEdit;
|
||||
@@ -64,12 +66,13 @@ class CompactionJob {
|
||||
public:
|
||||
CompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down,
|
||||
const SequenceNumber preserve_deletes_seqnum, LogBuffer* log_buffer,
|
||||
FSDirectory* db_directory, FSDirectory* output_directory,
|
||||
Statistics* stats, InstrumentedMutex* db_mutex,
|
||||
ErrorHandler* db_error_handler,
|
||||
FSDirectory* blob_output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
SequenceNumber earliest_write_conflict_snapshot,
|
||||
const SnapshotChecker* snapshot_checker,
|
||||
@@ -78,9 +81,12 @@ 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::string& db_id = "", const std::string& db_session_id = "");
|
||||
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);
|
||||
|
||||
~CompactionJob();
|
||||
virtual ~CompactionJob();
|
||||
|
||||
// no copy/move
|
||||
CompactionJob(CompactionJob&& job) = delete;
|
||||
@@ -103,11 +109,36 @@ class CompactionJob {
|
||||
// Return the IO status
|
||||
IOStatus io_status() const { return io_status_; }
|
||||
|
||||
private:
|
||||
protected:
|
||||
struct SubcompactionState;
|
||||
// CompactionJob state
|
||||
struct CompactionState;
|
||||
|
||||
void AggregateStatistics();
|
||||
void UpdateCompactionStats();
|
||||
void LogCompaction();
|
||||
void RecordCompactionIOStats();
|
||||
void CleanupCompaction();
|
||||
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
// kv-pairs
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
CompactionState* compact_;
|
||||
InternalStats::CompactionStats compaction_stats_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const MutableDBOptions mutable_db_options_copy_;
|
||||
LogBuffer* log_buffer_;
|
||||
FSDirectory* output_directory_;
|
||||
Statistics* stats_;
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level_;
|
||||
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
|
||||
IOStatus io_status_;
|
||||
|
||||
private:
|
||||
// Generates a histogram representing potential divisions of key ranges from
|
||||
// the input. It adds the starting and/or ending keys of certain input files
|
||||
// to the working set and then finds the approximate size of data in between
|
||||
@@ -115,12 +146,12 @@ class CompactionJob {
|
||||
// consecutive groups such that each group has a similar size.
|
||||
void GenSubcompactionBoundaries();
|
||||
|
||||
void ProcessKeyValueCompactionWithCompactionService(
|
||||
SubcompactionState* sub_compact);
|
||||
|
||||
// update the thread status for starting a compaction.
|
||||
void ReportStartedCompaction(Compaction* compaction);
|
||||
void AllocateCompactionOutputFileNumbers();
|
||||
// Call compaction filter. Then iterate through input and compact the
|
||||
// kv-pairs
|
||||
void ProcessKeyValueCompaction(SubcompactionState* sub_compact);
|
||||
|
||||
Status FinishCompactionOutputFile(
|
||||
const Status& input_status, SubcompactionState* sub_compact,
|
||||
@@ -128,33 +159,23 @@ class CompactionJob {
|
||||
CompactionIterationStats* range_del_out_stats,
|
||||
const Slice* next_table_min_key = nullptr);
|
||||
Status InstallCompactionResults(const MutableCFOptions& mutable_cf_options);
|
||||
void RecordCompactionIOStats();
|
||||
Status OpenCompactionOutputFile(SubcompactionState* sub_compact);
|
||||
void CleanupCompaction();
|
||||
void UpdateCompactionJobStats(
|
||||
const InternalStats::CompactionStats& stats) const;
|
||||
void RecordDroppedKeys(const CompactionIterationStats& c_iter_stats,
|
||||
CompactionJobStats* compaction_job_stats = nullptr);
|
||||
|
||||
void UpdateCompactionStats();
|
||||
void UpdateCompactionInputStatsHelper(
|
||||
int* num_files, uint64_t* bytes_read, int input_level);
|
||||
|
||||
void LogCompaction();
|
||||
|
||||
int job_id_;
|
||||
|
||||
// CompactionJob state
|
||||
struct CompactionState;
|
||||
CompactionState* compact_;
|
||||
CompactionJobStats* compaction_job_stats_;
|
||||
InternalStats::CompactionStats compaction_stats_;
|
||||
|
||||
// DBImpl state
|
||||
const std::string& dbname_;
|
||||
const std::string db_id_;
|
||||
const std::string db_session_id_;
|
||||
const ImmutableDBOptions& db_options_;
|
||||
const FileOptions file_options_;
|
||||
|
||||
Env* env_;
|
||||
@@ -165,11 +186,10 @@ 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_;
|
||||
LogBuffer* log_buffer_;
|
||||
FSDirectory* db_directory_;
|
||||
FSDirectory* output_directory_;
|
||||
Statistics* stats_;
|
||||
FSDirectory* blob_output_directory_;
|
||||
InstrumentedMutex* db_mutex_;
|
||||
ErrorHandler* db_error_handler_;
|
||||
// If there were two snapshots with seq numbers s1 and
|
||||
@@ -189,17 +209,153 @@ class CompactionJob {
|
||||
|
||||
EventLogger* event_logger_;
|
||||
|
||||
// Is this compaction creating a file in the bottom most level?
|
||||
bool bottommost_level_;
|
||||
bool paranoid_file_checks_;
|
||||
bool measure_io_stats_;
|
||||
// Stores the Slices that designate the boundaries for each subcompaction
|
||||
std::vector<Slice> boundaries_;
|
||||
// Stores the approx size of keys covered in the range of each subcompaction
|
||||
std::vector<uint64_t> sizes_;
|
||||
Env::WriteLifeTimeHint write_hint_;
|
||||
Env::Priority thread_pri_;
|
||||
IOStatus io_status_;
|
||||
std::string full_history_ts_low_;
|
||||
BlobFileCompletionCallback* blob_callback_;
|
||||
|
||||
// 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);
|
||||
};
|
||||
|
||||
// CompactionServiceInput is used the pass compaction information between two
|
||||
// db instances. It contains the information needed to do a compaction. It
|
||||
// doesn't contain the LSM tree information, which is passed though MANIFEST
|
||||
// file.
|
||||
struct CompactionServiceInput {
|
||||
ColumnFamilyDescriptor column_family;
|
||||
|
||||
DBOptions db_options;
|
||||
|
||||
std::vector<SequenceNumber> snapshots;
|
||||
|
||||
// SST files for compaction, it should already be expended to include all the
|
||||
// files needed for this compaction, for both input level files and output
|
||||
// level files.
|
||||
std::vector<std::string> input_files;
|
||||
int output_level;
|
||||
|
||||
// information for subcompaction
|
||||
bool has_begin = false;
|
||||
std::string begin;
|
||||
bool has_end = false;
|
||||
std::string end;
|
||||
uint64_t approx_size = 0;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
static Status Read(const std::string& data_str, CompactionServiceInput* obj);
|
||||
Status Write(std::string* output);
|
||||
|
||||
// Initialize a dummy ColumnFamilyDescriptor
|
||||
CompactionServiceInput() : column_family("", ColumnFamilyOptions()) {}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool TEST_Equals(CompactionServiceInput* other);
|
||||
bool TEST_Equals(CompactionServiceInput* other, std::string* mismatch);
|
||||
#endif // NDEBUG
|
||||
};
|
||||
|
||||
// CompactionServiceOutputFile is the metadata for the output SST file
|
||||
struct CompactionServiceOutputFile {
|
||||
std::string file_name;
|
||||
SequenceNumber smallest_seqno;
|
||||
SequenceNumber largest_seqno;
|
||||
std::string smallest_internal_key;
|
||||
std::string largest_internal_key;
|
||||
uint64_t oldest_ancester_time;
|
||||
uint64_t file_creation_time;
|
||||
uint64_t paranoid_hash;
|
||||
bool marked_for_compaction;
|
||||
|
||||
CompactionServiceOutputFile() = default;
|
||||
CompactionServiceOutputFile(
|
||||
const std::string& name, SequenceNumber smallest, SequenceNumber largest,
|
||||
std::string _smallest_internal_key, std::string _largest_internal_key,
|
||||
uint64_t _oldest_ancester_time, uint64_t _file_creation_time,
|
||||
uint64_t _paranoid_hash, bool _marked_for_compaction)
|
||||
: file_name(name),
|
||||
smallest_seqno(smallest),
|
||||
largest_seqno(largest),
|
||||
smallest_internal_key(std::move(_smallest_internal_key)),
|
||||
largest_internal_key(std::move(_largest_internal_key)),
|
||||
oldest_ancester_time(_oldest_ancester_time),
|
||||
file_creation_time(_file_creation_time),
|
||||
paranoid_hash(_paranoid_hash),
|
||||
marked_for_compaction(_marked_for_compaction) {}
|
||||
};
|
||||
|
||||
// CompactionServiceResult contains the compaction result from a different db
|
||||
// instance, with these information, the primary db instance with write
|
||||
// permission is able to install the result to the DB.
|
||||
struct CompactionServiceResult {
|
||||
Status status;
|
||||
std::vector<CompactionServiceOutputFile> output_files;
|
||||
int output_level;
|
||||
|
||||
// location of the output files
|
||||
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;
|
||||
CompactionJobStats stats;
|
||||
|
||||
// serialization interface to read and write the object
|
||||
static Status Read(const std::string& data_str, CompactionServiceResult* obj);
|
||||
Status Write(std::string* output);
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool TEST_Equals(CompactionServiceResult* other);
|
||||
bool TEST_Equals(CompactionServiceResult* other, std::string* mismatch);
|
||||
#endif // NDEBUG
|
||||
};
|
||||
|
||||
// CompactionServiceCompactionJob is an read-only compaction job, it takes
|
||||
// input information from `compaction_service_input` and put result information
|
||||
// in `compaction_service_result`, the SST files are generated to `output_path`.
|
||||
class CompactionServiceCompactionJob : private CompactionJob {
|
||||
public:
|
||||
CompactionServiceCompactionJob(
|
||||
int job_id, Compaction* compaction, const ImmutableDBOptions& db_options,
|
||||
const MutableDBOptions& mutable_db_options,
|
||||
const FileOptions& file_options, VersionSet* versions,
|
||||
const std::atomic<bool>* shutting_down, LogBuffer* log_buffer,
|
||||
FSDirectory* output_directory, Statistics* stats,
|
||||
InstrumentedMutex* db_mutex, ErrorHandler* db_error_handler,
|
||||
std::vector<SequenceNumber> existing_snapshots,
|
||||
std::shared_ptr<Cache> table_cache, EventLogger* event_logger,
|
||||
const std::string& dbname, const std::shared_ptr<IOTracer>& io_tracer,
|
||||
const std::string& db_id, const std::string& db_session_id,
|
||||
const std::string& output_path,
|
||||
const CompactionServiceInput& compaction_service_input,
|
||||
CompactionServiceResult* compaction_service_result);
|
||||
|
||||
// Run the compaction in current thread and return the result
|
||||
Status Run();
|
||||
|
||||
void CleanupCompaction();
|
||||
|
||||
IOStatus io_status() const { return CompactionJob::io_status(); }
|
||||
|
||||
private:
|
||||
// Get table file name in output_path
|
||||
std::string GetTableFileName(uint64_t file_number) override;
|
||||
// Specific the compaction output path, otherwise it uses default DB path
|
||||
const std::string output_path_;
|
||||
|
||||
// Compaction job input
|
||||
const CompactionServiceInput& compaction_input_;
|
||||
|
||||
// Compaction job result
|
||||
CompactionServiceResult* compaction_result_;
|
||||
};
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "db/write_batch_internal.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "file/filename.h"
|
||||
#include "logging/logging.h"
|
||||
#include "memtable/hash_linklist_rep.h"
|
||||
#include "monitoring/statistics.h"
|
||||
#include "monitoring/thread_status_util.h"
|
||||
@@ -298,15 +297,14 @@ class CompactionJobStatsTest : public testing::Test,
|
||||
return result;
|
||||
}
|
||||
|
||||
uint64_t Size(const Slice& start, const Slice& limit, int cf = 0) {
|
||||
Status Size(uint64_t* size, const Slice& start, const Slice& limit,
|
||||
int cf = 0) {
|
||||
Range r(start, limit);
|
||||
uint64_t size;
|
||||
if (cf == 0) {
|
||||
db_->GetApproximateSizes(&r, 1, &size);
|
||||
return db_->GetApproximateSizes(&r, 1, size);
|
||||
} else {
|
||||
db_->GetApproximateSizes(handles_[1], &r, 1, &size);
|
||||
return db_->GetApproximateSizes(handles_[1], &r, 1, size);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
void Compact(int cf, const Slice& start, const Slice& limit,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/compaction/compaction_job.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cinttypes>
|
||||
@@ -14,13 +16,13 @@
|
||||
|
||||
#include "db/blob/blob_index.h"
|
||||
#include "db/column_family.h"
|
||||
#include "db/compaction/compaction_job.h"
|
||||
#include "db/db_impl/db_impl.h"
|
||||
#include "db/error_handler.h"
|
||||
#include "db/version_set.h"
|
||||
#include "file/writable_file_writer.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/db.h"
|
||||
#include "rocksdb/file_system.h"
|
||||
#include "rocksdb/options.h"
|
||||
#include "rocksdb/write_buffer_manager.h"
|
||||
#include "table/mock_table.h"
|
||||
@@ -67,31 +69,38 @@ void VerifyInitializationOfCompactionJobStats(
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO(icanadi) Make it simpler once we mock out VersionSet
|
||||
class CompactionJobTest : public testing::Test {
|
||||
public:
|
||||
CompactionJobTest()
|
||||
class CompactionJobTestBase : public testing::Test {
|
||||
protected:
|
||||
CompactionJobTestBase(std::string dbname, const Comparator* ucmp,
|
||||
std::function<std::string(uint64_t)> encode_u64_ts)
|
||||
: env_(Env::Default()),
|
||||
fs_(std::make_shared<LegacyFileSystemWrapper>(env_)),
|
||||
dbname_(test::PerThreadDBPath("compaction_job_test")),
|
||||
fs_(env_->GetFileSystem()),
|
||||
dbname_(std::move(dbname)),
|
||||
ucmp_(ucmp),
|
||||
db_options_(),
|
||||
mutable_cf_options_(cf_options_),
|
||||
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()),
|
||||
error_handler_(nullptr, db_options_, &mutex_) {
|
||||
error_handler_(nullptr, db_options_, &mutex_),
|
||||
encode_u64_ts_(std::move(encode_u64_ts)) {}
|
||||
|
||||
void SetUp() override {
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
db_options_.env = env_;
|
||||
db_options_.fs = fs_;
|
||||
db_options_.db_paths.emplace_back(dbname_,
|
||||
std::numeric_limits<uint64_t>::max());
|
||||
cf_options_.comparator = ucmp_;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
}
|
||||
|
||||
std::string GenerateFileName(uint64_t file_number) {
|
||||
@@ -102,9 +111,10 @@ class CompactionJobTest : public testing::Test {
|
||||
return TableFileName(db_paths, meta.fd.GetNumber(), meta.fd.GetPathId());
|
||||
}
|
||||
|
||||
static std::string KeyStr(const std::string& user_key,
|
||||
const SequenceNumber seq_num, const ValueType t) {
|
||||
return InternalKey(user_key, seq_num, t).Encode().ToString();
|
||||
std::string KeyStr(const std::string& user_key, const SequenceNumber seq_num,
|
||||
const ValueType t, uint64_t ts = 0) {
|
||||
std::string user_key_with_ts = user_key + encode_u64_ts_(ts);
|
||||
return InternalKey(user_key_with_ts, seq_num, t).Encode().ToString();
|
||||
}
|
||||
|
||||
static std::string BlobStr(uint64_t blob_file_number, uint64_t offset,
|
||||
@@ -144,7 +154,8 @@ class CompactionJobTest : public testing::Test {
|
||||
std::string skey;
|
||||
std::string value;
|
||||
std::tie(skey, value) = kv;
|
||||
const Status pikStatus = ParseInternalKey(skey, &key);
|
||||
const Status pik_status =
|
||||
ParseInternalKey(skey, &key, true /* log_err_key */);
|
||||
|
||||
smallest_seqno = std::min(smallest_seqno, key.sequence);
|
||||
largest_seqno = std::max(largest_seqno, key.sequence);
|
||||
@@ -162,7 +173,7 @@ class CompactionJobTest : public testing::Test {
|
||||
|
||||
first_key = false;
|
||||
|
||||
if (pikStatus.ok() && key.type == kTypeBlobIndex) {
|
||||
if (pik_status.ok() && key.type == kTypeBlobIndex) {
|
||||
BlobIndex blob_index;
|
||||
const Status s = blob_index.DecodeFrom(value);
|
||||
if (!s.ok()) {
|
||||
@@ -207,9 +218,9 @@ class CompactionJobTest : public testing::Test {
|
||||
// returns expected result after compaction
|
||||
mock::KVVector CreateTwoFiles(bool gen_corrupted_keys) {
|
||||
stl_wrappers::KVMap expected_results;
|
||||
const int kKeysPerFile = 10000;
|
||||
const int kCorruptKeysPerFile = 200;
|
||||
const int kMatchingKeys = kKeysPerFile / 2;
|
||||
constexpr int kKeysPerFile = 10000;
|
||||
constexpr int kCorruptKeysPerFile = 200;
|
||||
constexpr int kMatchingKeys = kKeysPerFile / 2;
|
||||
SequenceNumber sequence_number = 0;
|
||||
|
||||
auto corrupt_id = [&](int id) {
|
||||
@@ -238,7 +249,7 @@ class CompactionJobTest : public testing::Test {
|
||||
{bottommost_internal_key.Encode().ToString(), value});
|
||||
}
|
||||
}
|
||||
mock::SortKVVector(&contents);
|
||||
mock::SortKVVector(&contents, ucmp_);
|
||||
|
||||
AddMockFile(contents);
|
||||
}
|
||||
@@ -254,33 +265,29 @@ class CompactionJobTest : public testing::Test {
|
||||
}
|
||||
|
||||
void NewDB() {
|
||||
DestroyDB(dbname_, Options());
|
||||
EXPECT_OK(DestroyDB(dbname_, Options()));
|
||||
EXPECT_OK(env_->CreateDirIfMissing(dbname_));
|
||||
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();
|
||||
SetIdentityFile(env_, dbname_);
|
||||
ASSERT_OK(SetIdentityFile(env_, dbname_));
|
||||
|
||||
VersionEdit new_db;
|
||||
if (db_options_.write_dbid_to_manifest) {
|
||||
DBImpl* impl = new DBImpl(DBOptions(), dbname_);
|
||||
std::string db_id;
|
||||
impl->GetDbIdentityFromIdentityFile(&db_id);
|
||||
new_db.SetDBId(db_id);
|
||||
}
|
||||
new_db.SetLogNumber(0);
|
||||
new_db.SetNextFile(2);
|
||||
new_db.SetLastSequence(0);
|
||||
|
||||
const std::string manifest = DescriptorFileName(dbname_, 1);
|
||||
std::unique_ptr<WritableFile> file;
|
||||
Status s = env_->NewWritableFile(
|
||||
manifest, &file, env_->OptimizeForManifestWrite(env_options_));
|
||||
std::unique_ptr<WritableFileWriter> file_writer;
|
||||
const auto& fs = env_->GetFileSystem();
|
||||
Status s = WritableFileWriter::Create(
|
||||
fs, manifest, fs->OptimizeForManifestWrite(env_options_), &file_writer,
|
||||
nullptr);
|
||||
|
||||
ASSERT_OK(s);
|
||||
std::unique_ptr<WritableFileWriter> file_writer(new WritableFileWriter(
|
||||
NewLegacyWritableFileWrapper(std::move(file)), manifest, env_options_));
|
||||
{
|
||||
log::Writer log(std::move(file_writer), 0, false);
|
||||
std::string record;
|
||||
@@ -293,13 +300,12 @@ class CompactionJobTest : public testing::Test {
|
||||
|
||||
ASSERT_OK(s);
|
||||
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
cf_options_.table_factory = mock_table_factory_;
|
||||
cf_options_.merge_operator = merge_op_;
|
||||
cf_options_.compaction_filter = compaction_filter_.get();
|
||||
std::vector<ColumnFamilyDescriptor> column_families;
|
||||
column_families.emplace_back(kDefaultColumnFamilyName, cf_options_);
|
||||
|
||||
EXPECT_OK(versions_->Recover(column_families, false));
|
||||
ASSERT_OK(versions_->Recover(column_families, false));
|
||||
cfd_ = versions_->GetColumnFamilySet()->GetDefault();
|
||||
}
|
||||
|
||||
@@ -337,19 +343,23 @@ class CompactionJobTest : public testing::Test {
|
||||
EventLogger event_logger(db_options_.info_log.get());
|
||||
// TODO(yiwu) add a mock snapshot checker and add test for it.
|
||||
SnapshotChecker* snapshot_checker = nullptr;
|
||||
ASSERT_TRUE(full_history_ts_low_.empty() ||
|
||||
ucmp_->timestamp_size() == full_history_ts_low_.size());
|
||||
CompactionJob compaction_job(
|
||||
0, &compaction, db_options_, env_options_, versions_.get(),
|
||||
&shutting_down_, preserve_deletes_seqnum_, &log_buffer, nullptr,
|
||||
nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
0, &compaction, db_options_, mutable_db_options_, env_options_,
|
||||
versions_.get(), &shutting_down_, preserve_deletes_seqnum_, &log_buffer,
|
||||
nullptr, nullptr, nullptr, nullptr, &mutex_, &error_handler_, snapshots,
|
||||
earliest_write_conflict_snapshot, snapshot_checker, table_cache_,
|
||||
&event_logger, false, false, dbname_, &compaction_job_stats_,
|
||||
Env::Priority::USER, nullptr /* IOTracer */);
|
||||
Env::Priority::USER, nullptr /* IOTracer */,
|
||||
/*manual_compaction_paused=*/nullptr,
|
||||
/*manual_compaction_canceled=*/nullptr, /*db_id=*/"",
|
||||
/*db_session_id=*/"", full_history_ts_low_);
|
||||
VerifyInitializationOfCompactionJobStats(compaction_job_stats_);
|
||||
|
||||
compaction_job.Prepare();
|
||||
mutex_.Unlock();
|
||||
Status s;
|
||||
s = compaction_job.Run();
|
||||
Status s = compaction_job.Run();
|
||||
ASSERT_OK(s);
|
||||
ASSERT_OK(compaction_job.io_status());
|
||||
mutex_.Lock();
|
||||
@@ -379,6 +389,7 @@ class CompactionJobTest : public testing::Test {
|
||||
Env* env_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
std::string dbname_;
|
||||
const Comparator* const ucmp_;
|
||||
EnvOptions env_options_;
|
||||
ImmutableDBOptions db_options_;
|
||||
ColumnFamilyOptions cf_options_;
|
||||
@@ -397,6 +408,17 @@ class CompactionJobTest : public testing::Test {
|
||||
std::unique_ptr<CompactionFilter> compaction_filter_;
|
||||
std::shared_ptr<MergeOperator> merge_op_;
|
||||
ErrorHandler error_handler_;
|
||||
std::string full_history_ts_low_;
|
||||
const std::function<std::string(uint64_t)> encode_u64_ts_;
|
||||
};
|
||||
|
||||
// TODO(icanadi) Make it simpler once we mock out VersionSet
|
||||
class CompactionJobTest : public CompactionJobTestBase {
|
||||
public:
|
||||
CompactionJobTest()
|
||||
: CompactionJobTestBase(test::PerThreadDBPath("compaction_job_test"),
|
||||
BytewiseComparator(),
|
||||
[](uint64_t /*ts*/) { return ""; }) {}
|
||||
};
|
||||
|
||||
TEST_F(CompactionJobTest, Simple) {
|
||||
@@ -1077,6 +1099,297 @@ TEST_F(CompactionJobTest, OldestBlobFileNumber) {
|
||||
/* expected_oldest_blob_file_number */ 19);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, InputSerialization) {
|
||||
// Setup a random CompactionServiceInput
|
||||
CompactionServiceInput input;
|
||||
const int kStrMaxLen = 1000;
|
||||
Random rnd(static_cast<uint32_t>(time(nullptr)));
|
||||
Random64 rnd64(time(nullptr));
|
||||
input.column_family.name = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
input.column_family.options.comparator = ReverseBytewiseComparator();
|
||||
input.column_family.options.max_bytes_for_level_base =
|
||||
rnd64.Uniform(UINT64_MAX);
|
||||
input.column_family.options.disable_auto_compactions = rnd.OneIn(2);
|
||||
input.column_family.options.compression = kZSTD;
|
||||
input.column_family.options.compression_opts.level = 4;
|
||||
input.db_options.max_background_flushes = 10;
|
||||
input.db_options.paranoid_checks = rnd.OneIn(2);
|
||||
input.db_options.statistics = CreateDBStatistics();
|
||||
input.db_options.env = env_;
|
||||
while (!rnd.OneIn(10)) {
|
||||
input.snapshots.emplace_back(rnd64.Uniform(UINT64_MAX));
|
||||
}
|
||||
while (!rnd.OneIn(10)) {
|
||||
input.input_files.emplace_back(rnd.RandomString(rnd.Uniform(kStrMaxLen)));
|
||||
}
|
||||
input.output_level = 4;
|
||||
input.has_begin = rnd.OneIn(2);
|
||||
if (input.has_begin) {
|
||||
input.begin = rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen));
|
||||
}
|
||||
input.has_end = rnd.OneIn(2);
|
||||
if (input.has_end) {
|
||||
input.end = rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen));
|
||||
}
|
||||
input.approx_size = rnd64.Uniform(UINT64_MAX);
|
||||
|
||||
std::string output;
|
||||
ASSERT_OK(input.Write(&output));
|
||||
|
||||
// Test deserialization
|
||||
CompactionServiceInput deserialized1;
|
||||
ASSERT_OK(CompactionServiceInput::Read(output, &deserialized1));
|
||||
ASSERT_TRUE(deserialized1.TEST_Equals(&input));
|
||||
|
||||
// Test mismatch
|
||||
deserialized1.db_options.max_background_flushes += 10;
|
||||
std::string mismatch;
|
||||
ASSERT_FALSE(deserialized1.TEST_Equals(&input, &mismatch));
|
||||
ASSERT_EQ(mismatch, "db_options.max_background_flushes");
|
||||
|
||||
// Test unknown field
|
||||
CompactionServiceInput deserialized2;
|
||||
output.clear();
|
||||
ASSERT_OK(input.Write(&output));
|
||||
output.append("new_field=123;");
|
||||
|
||||
ASSERT_OK(CompactionServiceInput::Read(output, &deserialized2));
|
||||
ASSERT_TRUE(deserialized2.TEST_Equals(&input));
|
||||
|
||||
// Test missing field
|
||||
CompactionServiceInput deserialized3;
|
||||
deserialized3.output_level = 0;
|
||||
std::string to_remove = "output_level=4;";
|
||||
size_t pos = output.find(to_remove);
|
||||
ASSERT_TRUE(pos != std::string::npos);
|
||||
output.erase(pos, to_remove.length());
|
||||
ASSERT_OK(CompactionServiceInput::Read(output, &deserialized3));
|
||||
mismatch.clear();
|
||||
ASSERT_FALSE(deserialized3.TEST_Equals(&input, &mismatch));
|
||||
ASSERT_EQ(mismatch, "output_level");
|
||||
|
||||
// manually set the value back, should match the original structure
|
||||
deserialized3.output_level = 4;
|
||||
ASSERT_TRUE(deserialized3.TEST_Equals(&input));
|
||||
|
||||
// Test invalid version
|
||||
output.clear();
|
||||
ASSERT_OK(input.Write(&output));
|
||||
|
||||
uint32_t data_version = DecodeFixed32(output.data());
|
||||
const size_t kDataVersionSize = sizeof(data_version);
|
||||
ASSERT_EQ(data_version,
|
||||
1U); // Update once the default data version is changed
|
||||
char buf[kDataVersionSize];
|
||||
EncodeFixed32(buf, data_version + 10); // make sure it's not valid
|
||||
output.replace(0, kDataVersionSize, buf, kDataVersionSize);
|
||||
Status s = CompactionServiceInput::Read(output, &deserialized3);
|
||||
ASSERT_TRUE(s.IsNotSupported());
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTest, ResultSerialization) {
|
||||
// Setup a random CompactionServiceResult
|
||||
CompactionServiceResult result;
|
||||
const int kStrMaxLen = 1000;
|
||||
Random rnd(static_cast<uint32_t>(time(nullptr)));
|
||||
Random64 rnd64(time(nullptr));
|
||||
std::vector<Status> status_list = {
|
||||
Status::OK(),
|
||||
Status::InvalidArgument("invalid option"),
|
||||
Status::Aborted("failed to run"),
|
||||
Status::NotSupported("not supported option"),
|
||||
};
|
||||
result.status =
|
||||
status_list.at(rnd.Uniform(static_cast<int>(status_list.size())));
|
||||
while (!rnd.OneIn(10)) {
|
||||
result.output_files.emplace_back(
|
||||
rnd.RandomString(rnd.Uniform(kStrMaxLen)), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd.RandomBinaryString(rnd.Uniform(kStrMaxLen)),
|
||||
rnd64.Uniform(UINT64_MAX), rnd64.Uniform(UINT64_MAX),
|
||||
rnd64.Uniform(UINT64_MAX), rnd.OneIn(2));
|
||||
}
|
||||
result.output_level = rnd.Uniform(10);
|
||||
result.output_path = rnd.RandomString(rnd.Uniform(kStrMaxLen));
|
||||
result.num_output_records = rnd64.Uniform(UINT64_MAX);
|
||||
result.total_bytes = rnd64.Uniform(UINT64_MAX);
|
||||
result.bytes_read = 123;
|
||||
result.bytes_written = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.elapsed_micros = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.num_output_files = rnd.Uniform(1000);
|
||||
result.stats.is_full_compaction = rnd.OneIn(2);
|
||||
result.stats.num_single_del_mismatch = rnd64.Uniform(UINT64_MAX);
|
||||
result.stats.num_input_files = 9;
|
||||
|
||||
std::string output;
|
||||
ASSERT_OK(result.Write(&output));
|
||||
|
||||
// Test deserialization
|
||||
CompactionServiceResult deserialized1;
|
||||
ASSERT_OK(CompactionServiceResult::Read(output, &deserialized1));
|
||||
ASSERT_TRUE(deserialized1.TEST_Equals(&result));
|
||||
|
||||
// Test mismatch
|
||||
deserialized1.stats.num_input_files += 10;
|
||||
std::string mismatch;
|
||||
ASSERT_FALSE(deserialized1.TEST_Equals(&result, &mismatch));
|
||||
ASSERT_EQ(mismatch, "stats.num_input_files");
|
||||
|
||||
// Test unknown field
|
||||
CompactionServiceResult deserialized2;
|
||||
output.clear();
|
||||
ASSERT_OK(result.Write(&output));
|
||||
output.append("new_field=123;");
|
||||
|
||||
ASSERT_OK(CompactionServiceResult::Read(output, &deserialized2));
|
||||
ASSERT_TRUE(deserialized2.TEST_Equals(&result));
|
||||
|
||||
// Test missing field
|
||||
CompactionServiceResult deserialized3;
|
||||
deserialized3.bytes_read = 0;
|
||||
std::string to_remove = "bytes_read=123;";
|
||||
size_t pos = output.find(to_remove);
|
||||
ASSERT_TRUE(pos != std::string::npos);
|
||||
output.erase(pos, to_remove.length());
|
||||
ASSERT_OK(CompactionServiceResult::Read(output, &deserialized3));
|
||||
mismatch.clear();
|
||||
ASSERT_FALSE(deserialized3.TEST_Equals(&result, &mismatch));
|
||||
ASSERT_EQ(mismatch, "bytes_read");
|
||||
|
||||
deserialized3.bytes_read = 123;
|
||||
ASSERT_TRUE(deserialized3.TEST_Equals(&result));
|
||||
|
||||
// Test invalid version
|
||||
output.clear();
|
||||
ASSERT_OK(result.Write(&output));
|
||||
|
||||
uint32_t data_version = DecodeFixed32(output.data());
|
||||
const size_t kDataVersionSize = sizeof(data_version);
|
||||
ASSERT_EQ(data_version,
|
||||
1U); // Update once the default data version is changed
|
||||
char buf[kDataVersionSize];
|
||||
EncodeFixed32(buf, data_version + 10); // make sure it's not valid
|
||||
output.replace(0, kDataVersionSize, buf, kDataVersionSize);
|
||||
Status s = CompactionServiceResult::Read(output, &deserialized3);
|
||||
ASSERT_TRUE(s.IsNotSupported());
|
||||
for (const auto& item : status_list) {
|
||||
item.PermitUncheckedError();
|
||||
}
|
||||
}
|
||||
|
||||
class CompactionJobTimestampTest : public CompactionJobTestBase {
|
||||
public:
|
||||
CompactionJobTimestampTest()
|
||||
: CompactionJobTestBase(test::PerThreadDBPath("compaction_job_ts_test"),
|
||||
test::ComparatorWithU64Ts(), test::EncodeInt) {}
|
||||
};
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, GCDisabled) {
|
||||
NewDB();
|
||||
|
||||
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"}});
|
||||
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"}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(10);
|
||||
|
||||
auto expected_results = 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", 7, ValueType::kTypeDeletionWithTimestamp, 97), ""},
|
||||
{KeyStr("c", 6, ValueType::kTypeDeletionWithTimestamp, 96), ""},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 95), "c5"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, NoKeyExpired) {
|
||||
NewDB();
|
||||
|
||||
auto file1 =
|
||||
mock::MakeMockFile({{KeyStr("a", 6, ValueType::kTypeValue, 100), "a6"},
|
||||
{KeyStr("b", 7, ValueType::kTypeValue, 101), "b7"},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 99), "c5"}});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 =
|
||||
mock::MakeMockFile({{KeyStr("a", 4, ValueType::kTypeValue, 98), "a4"},
|
||||
{KeyStr("c", 3, ValueType::kTypeValue, 97), "c3"}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(101);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 6, ValueType::kTypeValue, 100), "a6"},
|
||||
{KeyStr("a", 4, ValueType::kTypeValue, 98), "a4"},
|
||||
{KeyStr("b", 7, ValueType::kTypeValue, 101), "b7"},
|
||||
{KeyStr("c", 5, ValueType::kTypeValue, 99), "c5"},
|
||||
{KeyStr("c", 3, ValueType::kTypeValue, 97), "c3"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
full_history_ts_low_ = encode_u64_ts_(0);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, AllKeysExpired) {
|
||||
NewDB();
|
||||
|
||||
auto file1 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 5, ValueType::kTypeDeletionWithTimestamp, 100), ""},
|
||||
{KeyStr("b", 6, ValueType::kTypeValue, 99), "b6"}});
|
||||
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"}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(6);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("b", 0, ValueType::kTypeValue, 0), "b6"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
full_history_ts_low_ = encode_u64_ts_(std::numeric_limits<uint64_t>::max());
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
TEST_F(CompactionJobTimestampTest, SomeKeysExpired) {
|
||||
NewDB();
|
||||
|
||||
auto file1 =
|
||||
mock::MakeMockFile({{KeyStr("a", 5, ValueType::kTypeValue, 50), "a5"},
|
||||
{KeyStr("b", 6, ValueType::kTypeValue, 49), "b6"}});
|
||||
AddMockFile(file1);
|
||||
|
||||
auto file2 = mock::MakeMockFile(
|
||||
{{KeyStr("a", 3, ValueType::kTypeValue, 48), "a3"},
|
||||
{KeyStr("a", 2, ValueType::kTypeValue, 46), "a2"},
|
||||
{KeyStr("b", 4, ValueType::kTypeDeletionWithTimestamp, 47), ""}});
|
||||
AddMockFile(file2);
|
||||
|
||||
SetLastSequence(6);
|
||||
|
||||
auto expected_results =
|
||||
mock::MakeMockFile({{KeyStr("a", 5, ValueType::kTypeValue, 50), "a5"},
|
||||
{KeyStr("b", 6, ValueType::kTypeValue, 49), "b6"}});
|
||||
const auto& files = cfd_->current()->storage_info()->LevelFiles(0);
|
||||
|
||||
full_history_ts_low_ = encode_u64_ts_(49);
|
||||
RunCompaction({files}, expected_results);
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
@@ -139,18 +139,16 @@ CompressionOptions GetCompressionOptions(const MutableCFOptions& cf_options,
|
||||
if (!enable_compression) {
|
||||
return cf_options.compression_opts;
|
||||
}
|
||||
// If bottommost_compression is set and we are compacting to the
|
||||
// bottommost level then we should use the specified compression options
|
||||
// for the bottmomost_compression.
|
||||
if (cf_options.bottommost_compression != kDisableCompressionOption &&
|
||||
level >= (vstorage->num_non_empty_levels() - 1) &&
|
||||
// If bottommost_compression_opts is enabled and we are compacting to the
|
||||
// bottommost level then we should use the specified compression options.
|
||||
if (level >= (vstorage->num_non_empty_levels() - 1) &&
|
||||
cf_options.bottommost_compression_opts.enabled) {
|
||||
return cf_options.bottommost_compression_opts;
|
||||
}
|
||||
return cf_options.compression_opts;
|
||||
}
|
||||
|
||||
CompactionPicker::CompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
CompactionPicker::CompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: ioptions_(ioptions), icmp_(icmp) {}
|
||||
|
||||
@@ -532,7 +530,7 @@ bool CompactionPicker::SetupOtherInputs(
|
||||
}
|
||||
}
|
||||
if (expand_inputs) {
|
||||
ROCKS_LOG_INFO(ioptions_.info_log,
|
||||
ROCKS_LOG_INFO(ioptions_.logger,
|
||||
"[%s] Expanding@%d %" ROCKSDB_PRIszt "+%" ROCKSDB_PRIszt
|
||||
"(%" PRIu64 "+%" PRIu64 " bytes) to %" ROCKSDB_PRIszt
|
||||
"+%" ROCKSDB_PRIszt " (%" PRIu64 "+%" PRIu64 " bytes)\n",
|
||||
@@ -672,17 +670,41 @@ Compaction* CompactionPicker::CompactRange(
|
||||
// two files overlap.
|
||||
if (input_level > 0) {
|
||||
const uint64_t limit = mutable_cf_options.max_compaction_bytes;
|
||||
uint64_t total = 0;
|
||||
uint64_t input_level_total = 0;
|
||||
int hint_index = -1;
|
||||
InternalKey* smallest = nullptr;
|
||||
InternalKey* largest = nullptr;
|
||||
for (size_t i = 0; i + 1 < inputs.size(); ++i) {
|
||||
if (!smallest) {
|
||||
smallest = &inputs[i]->smallest;
|
||||
}
|
||||
largest = &inputs[i]->largest;
|
||||
|
||||
uint64_t s = inputs[i]->compensated_file_size;
|
||||
total += s;
|
||||
if (total >= limit) {
|
||||
uint64_t output_level_total = 0;
|
||||
if (output_level < vstorage->num_non_empty_levels()) {
|
||||
std::vector<FileMetaData*> files;
|
||||
vstorage->GetOverlappingInputsRangeBinarySearch(
|
||||
output_level, smallest, largest, &files, hint_index, &hint_index);
|
||||
for (const auto& file : files) {
|
||||
output_level_total += file->compensated_file_size;
|
||||
}
|
||||
}
|
||||
|
||||
input_level_total += s;
|
||||
|
||||
if (input_level_total + output_level_total >= limit) {
|
||||
covering_the_whole_range = false;
|
||||
// still include the current file, so the compaction could be larger
|
||||
// than max_compaction_bytes, which is also to make sure the compaction
|
||||
// can make progress even `max_compaction_bytes` is small (e.g. smaller
|
||||
// than an SST file).
|
||||
inputs.files.resize(i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
assert(compact_range_options.target_path_id <
|
||||
static_cast<uint32_t>(ioptions_.cf_paths.size()));
|
||||
|
||||
@@ -1006,6 +1028,7 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
// any currently-existing files.
|
||||
for (auto file_num : *input_files) {
|
||||
bool found = false;
|
||||
int input_file_level = -1;
|
||||
for (const auto& level_meta : cf_meta.levels) {
|
||||
for (const auto& file_meta : level_meta.files) {
|
||||
if (file_num == TableFileNameToNumber(file_meta.name)) {
|
||||
@@ -1015,6 +1038,7 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
" is already being compacted.");
|
||||
}
|
||||
found = true;
|
||||
input_file_level = level_meta.level;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1027,6 +1051,13 @@ Status CompactionPicker::SanitizeCompactionInputFiles(
|
||||
"Specified compaction input file " + MakeTableFileName("", file_num) +
|
||||
" does not exist in column family " + cf_meta.name + ".");
|
||||
}
|
||||
if (input_file_level > output_level) {
|
||||
return Status::InvalidArgument(
|
||||
"Cannot compact file to up level, input file: " +
|
||||
MakeTableFileName("", file_num) + " level " +
|
||||
ToString(input_file_level) + " > output level " +
|
||||
ToString(output_level));
|
||||
}
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
@@ -1045,6 +1076,8 @@ void CompactionPicker::RegisterCompaction(Compaction* c) {
|
||||
level0_compactions_in_progress_.insert(c);
|
||||
}
|
||||
compactions_in_progress_.insert(c);
|
||||
TEST_SYNC_POINT_CALLBACK("CompactionPicker::RegisterCompaction:Registered",
|
||||
c);
|
||||
}
|
||||
|
||||
void CompactionPicker::UnregisterCompaction(Compaction* c) {
|
||||
|
||||
@@ -46,7 +46,7 @@ struct CompactionInputFiles;
|
||||
// compaction style specific logic for them.
|
||||
class CompactionPicker {
|
||||
public:
|
||||
CompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
CompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp);
|
||||
virtual ~CompactionPicker();
|
||||
|
||||
@@ -218,7 +218,7 @@ class CompactionPicker {
|
||||
}
|
||||
|
||||
protected:
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
const ImmutableOptions& ioptions_;
|
||||
|
||||
// A helper function to SanitizeCompactionInputFiles() that
|
||||
// sanitizes "input_files" by adding necessary files.
|
||||
@@ -244,7 +244,7 @@ class CompactionPicker {
|
||||
// compaction.
|
||||
class NullCompactionPicker : public CompactionPicker {
|
||||
public:
|
||||
NullCompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
NullCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
virtual ~NullCompactionPicker() {}
|
||||
|
||||
@@ -45,7 +45,7 @@ Compaction* FIFOCompactionPicker::PickTTLCompaction(
|
||||
uint64_t total_size = GetTotalFilesSize(level_files);
|
||||
|
||||
int64_t _current_time;
|
||||
auto status = ioptions_.env->GetCurrentTime(&_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. "
|
||||
@@ -244,7 +244,7 @@ Compaction* FIFOCompactionPicker::CompactRange(
|
||||
assert(input_level == 0);
|
||||
assert(output_level == 0);
|
||||
*compaction_end = nullptr;
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.info_log);
|
||||
LogBuffer log_buffer(InfoLogLevel::INFO_LEVEL, ioptions_.logger);
|
||||
Compaction* c = PickCompaction(cf_name, mutable_cf_options,
|
||||
mutable_db_options, vstorage, &log_buffer);
|
||||
log_buffer.FlushBufferToLog();
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class FIFOCompactionPicker : public CompactionPicker {
|
||||
public:
|
||||
FIFOCompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
FIFOCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class LevelCompactionBuilder {
|
||||
CompactionPicker* compaction_picker,
|
||||
LogBuffer* log_buffer,
|
||||
const MutableCFOptions& mutable_cf_options,
|
||||
const ImmutableCFOptions& ioptions,
|
||||
const ImmutableOptions& ioptions,
|
||||
const MutableDBOptions& mutable_db_options)
|
||||
: cf_name_(cf_name),
|
||||
vstorage_(vstorage),
|
||||
@@ -121,7 +121,7 @@ class LevelCompactionBuilder {
|
||||
CompactionReason compaction_reason_ = CompactionReason::kUnknown;
|
||||
|
||||
const MutableCFOptions& mutable_cf_options_;
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
const ImmutableOptions& ioptions_;
|
||||
const MutableDBOptions& mutable_db_options_;
|
||||
// Pick a path ID to place a newly generated file, with its level
|
||||
static uint32_t GetPathId(const ImmutableCFOptions& ioptions,
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace ROCKSDB_NAMESPACE {
|
||||
// for description of Leveled compaction.
|
||||
class LevelCompactionPicker : public CompactionPicker {
|
||||
public:
|
||||
LevelCompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
LevelCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
virtual Compaction* PickCompaction(
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "db/compaction/compaction_picker_level.h"
|
||||
#include "db/compaction/compaction_picker_universal.h"
|
||||
|
||||
#include "logging/logging.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/string_util.h"
|
||||
@@ -31,7 +30,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
const Comparator* ucmp_;
|
||||
InternalKeyComparator icmp_;
|
||||
Options options_;
|
||||
ImmutableCFOptions ioptions_;
|
||||
ImmutableOptions ioptions_;
|
||||
MutableCFOptions mutable_cf_options_;
|
||||
MutableDBOptions mutable_db_options_;
|
||||
LevelCompactionPicker level_compaction_picker;
|
||||
@@ -142,7 +141,7 @@ class CompactionPickerTest : public testing::Test {
|
||||
if (temp_vstorage_) {
|
||||
VersionBuilder builder(FileOptions(), &ioptions_, nullptr,
|
||||
vstorage_.get(), nullptr);
|
||||
builder.SaveTo(temp_vstorage_.get());
|
||||
ASSERT_OK(builder.SaveTo(temp_vstorage_.get()));
|
||||
vstorage_ = std::move(temp_vstorage_);
|
||||
}
|
||||
vstorage_->CalculateBaseBytes(ioptions_, mutable_cf_options_);
|
||||
@@ -651,7 +650,7 @@ TEST_F(CompactionPickerTest, UniversalPeriodicCompaction3) {
|
||||
|
||||
TEST_F(CompactionPickerTest, UniversalPeriodicCompaction4) {
|
||||
// The case where universal periodic compaction couldn't form
|
||||
// a compaction that inlcudes any file marked for periodic compaction.
|
||||
// a compaction that includes any file marked for periodic compaction.
|
||||
// Right now we form the compaction anyway if it is more than one
|
||||
// sorted run. Just put the case here to validate that it doesn't
|
||||
// crash.
|
||||
@@ -801,7 +800,7 @@ TEST_F(CompactionPickerTest, CompactionPriMinOverlapping2) {
|
||||
Add(2, 6U, "150", "175",
|
||||
60000000U); // Overlaps with file 26, 27, total size 521M
|
||||
Add(2, 7U, "176", "200", 60000000U); // Overlaps with file 27, 28, total size
|
||||
// 520M, the smalelst overlapping
|
||||
// 520M, the smallest overlapping
|
||||
Add(2, 8U, "201", "300",
|
||||
60000000U); // Overlaps with file 28, 29, total size 521M
|
||||
|
||||
@@ -1229,7 +1228,7 @@ TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri1) {
|
||||
Add(0, 32U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 33U, "001", "400", 1000000000U, 0, 0);
|
||||
|
||||
// L1 total size 2GB, score 2.2. If one file being comapcted, score 1.1.
|
||||
// L1 total size 2GB, score 2.2. If one file being compacted, score 1.1.
|
||||
Add(1, 4U, "050", "300", 1000000000U, 0, 0);
|
||||
file_map_[4u].first->being_compacted = true;
|
||||
Add(1, 5U, "301", "350", 1000000000U, 0, 0);
|
||||
@@ -1262,7 +1261,7 @@ TEST_F(CompactionPickerTest, NotScheduleL1IfL0WithHigherPri2) {
|
||||
Add(0, 32U, "001", "400", 1000000000U, 0, 0);
|
||||
Add(0, 33U, "001", "400", 1000000000U, 0, 0);
|
||||
|
||||
// L1 total size 2GB, score 2.2. If one file being comapcted, score 1.1.
|
||||
// L1 total size 2GB, score 2.2. If one file being compacted, score 1.1.
|
||||
Add(1, 4U, "050", "300", 1000000000U, 0, 0);
|
||||
Add(1, 5U, "301", "350", 1000000000U, 0, 0);
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace {
|
||||
class UniversalCompactionBuilder {
|
||||
public:
|
||||
UniversalCompactionBuilder(
|
||||
const ImmutableCFOptions& ioptions, const InternalKeyComparator* icmp,
|
||||
const ImmutableOptions& ioptions, const InternalKeyComparator* icmp,
|
||||
const std::string& cf_name, const MutableCFOptions& mutable_cf_options,
|
||||
const MutableDBOptions& mutable_db_options, VersionStorageInfo* vstorage,
|
||||
UniversalCompactionPicker* picker, LogBuffer* log_buffer)
|
||||
@@ -108,7 +108,7 @@ class UniversalCompactionBuilder {
|
||||
// overlapping.
|
||||
bool IsInputFilesNonOverlapping(Compaction* c);
|
||||
|
||||
const ImmutableCFOptions& ioptions_;
|
||||
const ImmutableOptions& ioptions_;
|
||||
const InternalKeyComparator* icmp_;
|
||||
double score_;
|
||||
std::vector<SortedRun> sorted_runs_;
|
||||
@@ -486,7 +486,7 @@ Compaction* UniversalCompactionBuilder::PickCompaction() {
|
||||
}
|
||||
#endif
|
||||
// update statistics
|
||||
RecordInHistogram(ioptions_.statistics, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
RecordInHistogram(ioptions_.stats, NUM_FILES_IN_SINGLE_COMPACTION,
|
||||
c->inputs(0)->size());
|
||||
|
||||
picker_->RegisterCompaction(c);
|
||||
@@ -733,7 +733,7 @@ Compaction* UniversalCompactionBuilder::PickCompactionToReduceSortedRuns(
|
||||
}
|
||||
|
||||
// Look at overall size amplification. If size amplification
|
||||
// exceeeds the configured value, then do a compaction
|
||||
// exceeds the configured value, then do a compaction
|
||||
// of the candidate files all the way upto the earliest
|
||||
// base file (overrides configured values of file-size ratios,
|
||||
// min_merge_width and max_merge_width).
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
class UniversalCompactionPicker : public CompactionPicker {
|
||||
public:
|
||||
UniversalCompactionPicker(const ImmutableCFOptions& ioptions,
|
||||
UniversalCompactionPicker(const ImmutableOptions& ioptions,
|
||||
const InternalKeyComparator* icmp)
|
||||
: CompactionPicker(ioptions, icmp) {}
|
||||
virtual Compaction* PickCompaction(
|
||||
|
||||
@@ -0,0 +1,458 @@
|
||||
// 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).
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
class MyTestCompactionService : public CompactionService {
|
||||
public:
|
||||
MyTestCompactionService(const std::string& db_path,
|
||||
std::shared_ptr<FileSystem> fs, Options& options)
|
||||
: db_path_(db_path), fs_(fs), options_(options) {}
|
||||
|
||||
static const char* kClassName() { return "MyTestCompactionService"; }
|
||||
|
||||
const char* Name() const override { return kClassName(); }
|
||||
|
||||
CompactionServiceJobStatus Start(const std::string& compaction_service_input,
|
||||
int 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);
|
||||
return s;
|
||||
}
|
||||
|
||||
CompactionServiceJobStatus WaitForComplete(
|
||||
int job_id, std::string* compaction_service_result) override {
|
||||
std::string compaction_input;
|
||||
{
|
||||
InstrumentedMutexLock l(&mutex_);
|
||||
auto i = jobs_.find(job_id);
|
||||
if (i == jobs_.end()) {
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
compaction_input = std::move(i->second);
|
||||
jobs_.erase(i);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Status s = DB::OpenAndCompact(
|
||||
db_path_, db_path_ + "/" + ROCKSDB_NAMESPACE::ToString(job_id),
|
||||
compaction_input, compaction_service_result, options_override);
|
||||
TEST_SYNC_POINT_CALLBACK("MyTestCompactionService::WaitForComplete::End",
|
||||
compaction_service_result);
|
||||
compaction_num_.fetch_add(1);
|
||||
if (s.ok()) {
|
||||
return CompactionServiceJobStatus::kSuccess;
|
||||
} else {
|
||||
return CompactionServiceJobStatus::kFailure;
|
||||
}
|
||||
}
|
||||
|
||||
int GetCompactionNum() { return compaction_num_.load(); }
|
||||
|
||||
private:
|
||||
InstrumentedMutex mutex_;
|
||||
std::atomic_int compaction_num_{0};
|
||||
std::map<int, std::string> jobs_;
|
||||
const std::string db_path_;
|
||||
std::shared_ptr<FileSystem> fs_;
|
||||
Options options_;
|
||||
};
|
||||
|
||||
class CompactionServiceTest : public DBTestBase {
|
||||
public:
|
||||
explicit CompactionServiceTest()
|
||||
: DBTestBase("compaction_service_test", true) {}
|
||||
|
||||
protected:
|
||||
void GenerateTestData() {
|
||||
// Generate 20 files @ L2
|
||||
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());
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
|
||||
// Generate 10 files @ L1 overlap with all 20 files @ L2
|
||||
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());
|
||||
}
|
||||
MoveFilesToLevel(1);
|
||||
ASSERT_EQ(FilesPerLevel(), "0,10,20");
|
||||
}
|
||||
|
||||
void VerifyTestData() {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(CompactionServiceTest, BasicCompactions) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
|
||||
DestroyAndReopen(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());
|
||||
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
|
||||
// Test failed compaction
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImplSecondary::CompactWithoutInstallation::End", [&](void* status) {
|
||||
// override job status
|
||||
Status* s = static_cast<Status*>(status);
|
||||
*s = Status::Aborted("MyTestCompactionService failed to compact!");
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
Status s;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
int key_id = i * 20 + j * 2;
|
||||
s = Put(Key(key_id), "value_new" + ToString(key_id));
|
||||
if (s.IsAborted()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (s.IsAborted()) {
|
||||
break;
|
||||
}
|
||||
s = Flush();
|
||||
if (s.IsAborted()) {
|
||||
break;
|
||||
}
|
||||
s = dbfull()->TEST_WaitForCompact();
|
||||
if (s.IsAborted()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_TRUE(s.IsAborted());
|
||||
}
|
||||
|
||||
TEST_F(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);
|
||||
GenerateTestData();
|
||||
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
|
||||
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);
|
||||
VerifyTestData();
|
||||
|
||||
start_str = Key(120);
|
||||
start = start_str;
|
||||
comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), &start, nullptr));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
|
||||
end_str = Key(92);
|
||||
end = end_str;
|
||||
comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, &end));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
|
||||
comp_num = my_cs->GetCompactionNum();
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), comp_num + 1);
|
||||
VerifyTestData();
|
||||
}
|
||||
|
||||
TEST_F(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);
|
||||
GenerateTestData();
|
||||
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"MyTestCompactionService::Start::End", [&](void* status) {
|
||||
// override job status
|
||||
auto s = static_cast<CompactionServiceJobStatus*>(status);
|
||||
*s = CompactionServiceJobStatus::kFailure;
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_TRUE(s.IsIncomplete());
|
||||
}
|
||||
|
||||
TEST_F(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);
|
||||
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();
|
||||
|
||||
std::string start_str = Key(15);
|
||||
std::string end_str = Key(45);
|
||||
Slice start(start_str);
|
||||
Slice end(end_str);
|
||||
Status s = db_->CompactRange(CompactRangeOptions(), &start, &end);
|
||||
ASSERT_FALSE(s.ok());
|
||||
}
|
||||
|
||||
// TODO: support sub-compaction
|
||||
TEST_F(CompactionServiceTest, DISABLED_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);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
GenerateTestData();
|
||||
|
||||
auto cro = CompactRangeOptions();
|
||||
cro.max_subcompactions = 10;
|
||||
db_->CompactRange(cro, nullptr, nullptr);
|
||||
}
|
||||
|
||||
class PartialDeleteCompactionFilter : public CompactionFilter {
|
||||
public:
|
||||
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 {
|
||||
int i = std::stoi(key.ToString().substr(3));
|
||||
if (i > 5 && i <= 105) {
|
||||
return CompactionFilter::Decision::kRemove;
|
||||
}
|
||||
return CompactionFilter::Decision::kKeep;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "PartialDeleteCompactionFilter"; }
|
||||
};
|
||||
|
||||
TEST_F(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);
|
||||
|
||||
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());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// verify result
|
||||
for (int i = 0; i < 200; i++) {
|
||||
auto result = Get(Key(i));
|
||||
if (i > 5 && i <= 105) {
|
||||
ASSERT_EQ(result, "NOT_FOUND");
|
||||
} else if (i % 2) {
|
||||
ASSERT_EQ(result, "value" + ToString(i));
|
||||
} else {
|
||||
ASSERT_EQ(result, "value_new" + ToString(i));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
ASSERT_GE(my_cs->GetCompactionNum(), 1);
|
||||
}
|
||||
|
||||
TEST_F(CompactionServiceTest, Snapshot) {
|
||||
Options options = CurrentOptions();
|
||||
options.env = env_;
|
||||
options.compaction_service = std::make_shared<MyTestCompactionService>(
|
||||
dbname_, env_->GetFileSystem(), options);
|
||||
|
||||
DestroyAndReopen(options);
|
||||
|
||||
ASSERT_OK(Put(Key(1), "value1"));
|
||||
ASSERT_OK(Put(Key(2), "value1"));
|
||||
const Snapshot* s1 = db_->GetSnapshot();
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put(Key(1), "value2"));
|
||||
ASSERT_OK(Put(Key(3), "value2"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
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) {
|
||||
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);
|
||||
GenerateTestData();
|
||||
|
||||
ColumnFamilyMetaData meta;
|
||||
db_->GetColumnFamilyMetaData(&meta);
|
||||
|
||||
std::vector<std::thread> threads;
|
||||
for (const auto& file : meta.levels[1].files) {
|
||||
threads.push_back(std::thread([&]() {
|
||||
std::string fname = file.db_path + "/" + file.name;
|
||||
ASSERT_OK(db_->CompactFiles(CompactionOptions(), {fname}, 2));
|
||||
}));
|
||||
}
|
||||
|
||||
for (auto& thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
auto my_cs =
|
||||
dynamic_cast<MyTestCompactionService*>(options.compaction_service.get());
|
||||
ASSERT_EQ(my_cs->GetCompactionNum(), 10);
|
||||
ASSERT_EQ(FilesPerLevel(), "0,0,10");
|
||||
}
|
||||
|
||||
} // 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();
|
||||
}
|
||||
|
||||
#else
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int /*argc*/, char** /*argv*/) {
|
||||
fprintf(stderr,
|
||||
"SKIPPED as CompactionService is not supported in ROCKSDB_LITE\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
@@ -17,7 +17,6 @@
|
||||
#include "util/string_util.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
|
||||
using std::unique_ptr;
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
namespace {
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ Status VerifySstFileChecksum(const Options& options,
|
||||
std::unique_ptr<FSRandomAccessFile> file;
|
||||
uint64_t file_size;
|
||||
InternalKeyComparator internal_comparator(options.comparator);
|
||||
ImmutableCFOptions ioptions(options);
|
||||
ImmutableOptions ioptions(options);
|
||||
|
||||
Status s = ioptions.fs->NewRandomAccessFile(file_path,
|
||||
FileOptions(env_options),
|
||||
|
||||
+112
-38
@@ -9,7 +9,6 @@
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
@@ -20,8 +19,8 @@
|
||||
#include "db/db_test_util.h"
|
||||
#include "db/log_format.h"
|
||||
#include "db/version_set.h"
|
||||
#include "env/composite_env_wrapper.h"
|
||||
#include "file/filename.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/cache.h"
|
||||
#include "rocksdb/convenience.h"
|
||||
#include "rocksdb/db.h"
|
||||
@@ -33,16 +32,18 @@
|
||||
#include "table/mock_table.h"
|
||||
#include "test_util/testharness.h"
|
||||
#include "test_util/testutil.h"
|
||||
#include "util/cast_util.h"
|
||||
#include "util/random.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
static const int kValueSize = 1000;
|
||||
static constexpr int kValueSize = 1000;
|
||||
|
||||
class CorruptionTest : public testing::Test {
|
||||
public:
|
||||
test::ErrorEnv env_;
|
||||
std::shared_ptr<Env> env_guard_;
|
||||
test::ErrorEnv* env_;
|
||||
std::string dbname_;
|
||||
std::shared_ptr<Cache> tiny_cache_;
|
||||
Options options_;
|
||||
@@ -53,9 +54,14 @@ class CorruptionTest : public testing::Test {
|
||||
// set it to 0), test SequenceNumberRecovery will fail, likely because of a
|
||||
// bug in recovery code. Keep it 4 for now to make the test passes.
|
||||
tiny_cache_ = NewLRUCache(100, 4);
|
||||
Env* base_env = Env::Default();
|
||||
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_;
|
||||
dbname_ = test::PerThreadDBPath("corruption_test");
|
||||
options_.env = env_;
|
||||
dbname_ = test::PerThreadDBPath(env_, "corruption_test");
|
||||
Status s = DestroyDB(dbname_, options_);
|
||||
EXPECT_OK(s);
|
||||
|
||||
@@ -69,9 +75,19 @@ class CorruptionTest : public testing::Test {
|
||||
}
|
||||
|
||||
~CorruptionTest() override {
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->LoadDependency({});
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
DestroyDB(dbname_, Options());
|
||||
if (getenv("KEEP_DB")) {
|
||||
fprintf(stdout, "db is still at %s\n", dbname_.c_str());
|
||||
} else {
|
||||
Options opts;
|
||||
opts.env = env_->target();
|
||||
EXPECT_OK(DestroyDB(dbname_, opts));
|
||||
}
|
||||
delete env_;
|
||||
}
|
||||
|
||||
void CloseDb() {
|
||||
@@ -86,7 +102,7 @@ class CorruptionTest : public testing::Test {
|
||||
if (opt.env == Options().env) {
|
||||
// If env is not overridden, replace it with ErrorEnv.
|
||||
// Otherwise, the test already uses a non-default Env.
|
||||
opt.env = &env_;
|
||||
opt.env = env_;
|
||||
}
|
||||
opt.arena_block_size = 4096;
|
||||
BlockBasedTableOptions table_options;
|
||||
@@ -169,7 +185,7 @@ class CorruptionTest : public testing::Test {
|
||||
void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) {
|
||||
// Pick file to corrupt
|
||||
std::vector<std::string> filenames;
|
||||
ASSERT_OK(env_.GetChildren(dbname_, &filenames));
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &filenames));
|
||||
uint64_t number;
|
||||
FileType type;
|
||||
std::string fname;
|
||||
@@ -184,7 +200,7 @@ class CorruptionTest : public testing::Test {
|
||||
}
|
||||
ASSERT_TRUE(!fname.empty()) << filetype;
|
||||
|
||||
test::CorruptFile(fname, offset, bytes_to_corrupt);
|
||||
ASSERT_OK(test::CorruptFile(env_, fname, offset, bytes_to_corrupt));
|
||||
}
|
||||
|
||||
// corrupts exactly one file at level `level`. if no file found at level,
|
||||
@@ -194,7 +210,8 @@ class CorruptionTest : public testing::Test {
|
||||
db_->GetLiveFilesMetaData(&metadata);
|
||||
for (const auto& m : metadata) {
|
||||
if (m.level == level) {
|
||||
test::CorruptFile(dbname_ + "/" + m.name, offset, bytes_to_corrupt);
|
||||
ASSERT_OK(test::CorruptFile(env_, dbname_ + "/" + m.name, offset,
|
||||
bytes_to_corrupt));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -249,8 +266,8 @@ TEST_F(CorruptionTest, Recovery) {
|
||||
// is not available for WAL though.
|
||||
CloseDb();
|
||||
#endif
|
||||
Corrupt(kLogFile, 19, 1); // WriteBatch tag for first record
|
||||
Corrupt(kLogFile, log::kBlockSize + 1000, 1); // Somewhere in second block
|
||||
Corrupt(kWalFile, 19, 1); // WriteBatch tag for first record
|
||||
Corrupt(kWalFile, log::kBlockSize + 1000, 1); // Somewhere in second block
|
||||
ASSERT_TRUE(!TryReopen().ok());
|
||||
options_.paranoid_checks = false;
|
||||
Reopen(&options_);
|
||||
@@ -260,14 +277,14 @@ TEST_F(CorruptionTest, Recovery) {
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, RecoverWriteError) {
|
||||
env_.writable_file_error_ = true;
|
||||
env_->writable_file_error_ = true;
|
||||
Status s = TryReopen();
|
||||
ASSERT_TRUE(!s.ok());
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, NewFileErrorDuringWrite) {
|
||||
// Do enough writing to force minor compaction
|
||||
env_.writable_file_error_ = true;
|
||||
env_->writable_file_error_ = true;
|
||||
const int num =
|
||||
static_cast<int>(3 + (Options().write_buffer_size / kValueSize));
|
||||
std::string value_storage;
|
||||
@@ -283,8 +300,8 @@ TEST_F(CorruptionTest, NewFileErrorDuringWrite) {
|
||||
ASSERT_TRUE(!failed || !s.ok());
|
||||
}
|
||||
ASSERT_TRUE(!s.ok());
|
||||
ASSERT_GE(env_.num_writable_file_errors_, 1);
|
||||
env_.writable_file_error_ = false;
|
||||
ASSERT_GE(env_->num_writable_file_errors_, 1);
|
||||
env_->writable_file_error_ = false;
|
||||
Reopen();
|
||||
}
|
||||
|
||||
@@ -302,7 +319,7 @@ TEST_F(CorruptionTest, TableFile) {
|
||||
|
||||
TEST_F(CorruptionTest, VerifyChecksumReadahead) {
|
||||
Options options;
|
||||
SpecialEnv senv(Env::Default());
|
||||
SpecialEnv senv(env_->target());
|
||||
options.env = &senv;
|
||||
// Disable block cache as we are going to check checksum for
|
||||
// the same file twice and measure number of reads.
|
||||
@@ -424,6 +441,7 @@ TEST_F(CorruptionTest, CorruptedDescriptor) {
|
||||
|
||||
TEST_F(CorruptionTest, CompactionInputError) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
Reopen(&options);
|
||||
Build(10);
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
@@ -444,6 +462,7 @@ TEST_F(CorruptionTest, CompactionInputError) {
|
||||
|
||||
TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.paranoid_checks = true;
|
||||
options.write_buffer_size = 131072;
|
||||
options.max_write_buffer_number = 2;
|
||||
@@ -514,28 +533,31 @@ TEST_F(CorruptionTest, RangeDeletionCorrupted) {
|
||||
ASSERT_EQ(static_cast<size_t>(1), metadata.size());
|
||||
std::string filename = dbname_ + metadata[0].name;
|
||||
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
ASSERT_OK(options_.env->NewRandomAccessFile(filename, &file, EnvOptions()));
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader(
|
||||
new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(file),
|
||||
filename));
|
||||
FileOptions file_opts;
|
||||
const auto& fs = options_.env->GetFileSystem();
|
||||
std::unique_ptr<RandomAccessFileReader> file_reader;
|
||||
ASSERT_OK(RandomAccessFileReader::Create(fs, filename, file_opts,
|
||||
&file_reader, nullptr));
|
||||
|
||||
uint64_t file_size;
|
||||
ASSERT_OK(options_.env->GetFileSize(filename, &file_size));
|
||||
ASSERT_OK(
|
||||
fs->GetFileSize(filename, file_opts.io_options, &file_size, nullptr));
|
||||
|
||||
BlockHandle range_del_handle;
|
||||
ASSERT_OK(FindMetaBlock(
|
||||
file_reader.get(), file_size, kBlockBasedTableMagicNumber,
|
||||
ImmutableCFOptions(options_), kRangeDelBlock, &range_del_handle));
|
||||
ImmutableOptions(options_), kRangeDelBlock, &range_del_handle));
|
||||
|
||||
ASSERT_OK(TryReopen());
|
||||
test::CorruptFile(filename, static_cast<int>(range_del_handle.offset()), 1);
|
||||
ASSERT_OK(test::CorruptFile(env_, filename,
|
||||
static_cast<int>(range_del_handle.offset()), 1));
|
||||
ASSERT_TRUE(TryReopen().IsCorruption());
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, FileSystemStateCorrupted) {
|
||||
for (int iter = 0; iter < 2; ++iter) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.paranoid_checks = true;
|
||||
options.create_if_missing = true;
|
||||
Reopen(&options);
|
||||
@@ -544,7 +566,7 @@ TEST_F(CorruptionTest, FileSystemStateCorrupted) {
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
std::vector<LiveFileMetaData> metadata;
|
||||
dbi->GetLiveFilesMetaData(&metadata);
|
||||
ASSERT_GT(metadata.size(), size_t(0));
|
||||
ASSERT_GT(metadata.size(), 0);
|
||||
std::string filename = dbname_ + metadata[0].name;
|
||||
|
||||
delete db_;
|
||||
@@ -552,15 +574,15 @@ TEST_F(CorruptionTest, FileSystemStateCorrupted) {
|
||||
|
||||
if (iter == 0) { // corrupt file size
|
||||
std::unique_ptr<WritableFile> file;
|
||||
env_.NewWritableFile(filename, &file, EnvOptions());
|
||||
ASSERT_OK(env_->NewWritableFile(filename, &file, EnvOptions()));
|
||||
ASSERT_OK(file->Append(Slice("corrupted sst")));
|
||||
file.reset();
|
||||
Status x = TryReopen(&options);
|
||||
ASSERT_TRUE(x.IsCorruption());
|
||||
} else { // delete the file
|
||||
ASSERT_OK(env_.DeleteFile(filename));
|
||||
ASSERT_OK(env_->DeleteFile(filename));
|
||||
Status x = TryReopen(&options);
|
||||
ASSERT_TRUE(x.IsPathNotFound());
|
||||
ASSERT_TRUE(x.IsCorruption());
|
||||
}
|
||||
|
||||
ASSERT_OK(DestroyDB(dbname_, options_));
|
||||
@@ -574,6 +596,7 @@ static const auto& corruption_modes = {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksOnFlush) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.check_flush_compaction_key_order = false;
|
||||
options.paranoid_file_checks = true;
|
||||
options.create_if_missing = true;
|
||||
@@ -588,7 +611,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnFlush) {
|
||||
options.table_factory = mock;
|
||||
mock->SetCorruptionMode(mode);
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr);
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
Build(10);
|
||||
s = db_->Flush(FlushOptions());
|
||||
if (mode == mock::MockTableFactory::kCorruptNone) {
|
||||
@@ -601,6 +624,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnFlush) {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.paranoid_file_checks = true;
|
||||
options.create_if_missing = true;
|
||||
options.check_flush_compaction_key_order = false;
|
||||
@@ -613,7 +637,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
|
||||
std::make_shared<mock::MockTableFactory>();
|
||||
options.table_factory = mock;
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr);
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
Build(100, 2);
|
||||
// ASSERT_OK(db_->Flush(FlushOptions()));
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
@@ -630,6 +654,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksOnCompact) {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.check_flush_compaction_key_order = false;
|
||||
options.paranoid_file_checks = true;
|
||||
options.create_if_missing = true;
|
||||
@@ -639,7 +664,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
std::string start, end;
|
||||
assert(db_ != nullptr);
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
Key(3, &start), Key(7, &end)));
|
||||
auto snap = db_->GetSnapshot();
|
||||
@@ -662,6 +687,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeFirst) {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.check_flush_compaction_key_order = false;
|
||||
options.paranoid_file_checks = true;
|
||||
options.create_if_missing = true;
|
||||
@@ -670,7 +696,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
|
||||
db_ = nullptr;
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr);
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
Build(10, 0, 0);
|
||||
std::string start, end;
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
@@ -697,6 +723,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRange) {
|
||||
|
||||
TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.check_flush_compaction_key_order = false;
|
||||
options.paranoid_file_checks = true;
|
||||
options.create_if_missing = true;
|
||||
@@ -705,7 +732,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
|
||||
db_ = nullptr;
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr);
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
std::string start, end;
|
||||
Build(10);
|
||||
ASSERT_OK(db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(),
|
||||
@@ -729,6 +756,7 @@ TEST_F(CorruptionTest, ParanoidFileChecksWithDeleteRangeLast) {
|
||||
|
||||
TEST_F(CorruptionTest, LogCorruptionErrorsInCompactionIterator) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.create_if_missing = true;
|
||||
options.allow_data_in_errors = true;
|
||||
auto mode = mock::MockTableFactory::kCorruptKey;
|
||||
@@ -742,7 +770,7 @@ TEST_F(CorruptionTest, LogCorruptionErrorsInCompactionIterator) {
|
||||
options.table_factory = mock;
|
||||
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr);
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
Build(100, 2);
|
||||
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
@@ -754,6 +782,7 @@ TEST_F(CorruptionTest, LogCorruptionErrorsInCompactionIterator) {
|
||||
|
||||
TEST_F(CorruptionTest, CompactionKeyOrderCheck) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.paranoid_file_checks = false;
|
||||
options.create_if_missing = true;
|
||||
options.check_flush_compaction_key_order = false;
|
||||
@@ -764,7 +793,7 @@ TEST_F(CorruptionTest, CompactionKeyOrderCheck) {
|
||||
std::make_shared<mock::MockTableFactory>();
|
||||
options.table_factory = mock;
|
||||
ASSERT_OK(DB::Open(options, dbname_, &db_));
|
||||
assert(db_ != nullptr);
|
||||
assert(db_ != nullptr); // suppress false clang-analyze report
|
||||
mock->SetCorruptionMode(mock::MockTableFactory::kCorruptReorderKey);
|
||||
Build(100, 2);
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
@@ -777,6 +806,7 @@ TEST_F(CorruptionTest, CompactionKeyOrderCheck) {
|
||||
|
||||
TEST_F(CorruptionTest, FlushKeyOrderCheck) {
|
||||
Options options;
|
||||
options.env = env_;
|
||||
options.paranoid_file_checks = false;
|
||||
options.create_if_missing = true;
|
||||
ASSERT_OK(db_->SetOptions({{"check_flush_compaction_key_order", "true"}}));
|
||||
@@ -805,7 +835,6 @@ TEST_F(CorruptionTest, FlushKeyOrderCheck) {
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, DisableKeyOrderCheck) {
|
||||
Options options;
|
||||
ASSERT_OK(db_->SetOptions({{"check_flush_compaction_key_order", "false"}}));
|
||||
DBImpl* dbi = static_cast_with_check<DBImpl>(db_);
|
||||
|
||||
@@ -824,10 +853,55 @@ TEST_F(CorruptionTest, DisableKeyOrderCheck) {
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
}
|
||||
|
||||
TEST_F(CorruptionTest, VerifyWholeTableChecksum) {
|
||||
CloseDb();
|
||||
Options options;
|
||||
options.env = env_;
|
||||
ASSERT_OK(DestroyDB(dbname_, options));
|
||||
options.create_if_missing = true;
|
||||
options.file_checksum_gen_factory =
|
||||
ROCKSDB_NAMESPACE::GetFileChecksumGenCrc32cFactory();
|
||||
Reopen(&options);
|
||||
|
||||
Build(10, 5);
|
||||
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
CloseDb();
|
||||
|
||||
// Corrupt the first byte of each table file, this must be data block.
|
||||
Corrupt(kTableFile, 0, 1);
|
||||
|
||||
ASSERT_OK(TryReopen(&options));
|
||||
|
||||
SyncPoint::GetInstance()->DisableProcessing();
|
||||
SyncPoint::GetInstance()->ClearAllCallBacks();
|
||||
int count{0};
|
||||
SyncPoint::GetInstance()->SetCallBack(
|
||||
"DBImpl::VerifyFullFileChecksum:mismatch", [&](void* arg) {
|
||||
auto* s = reinterpret_cast<Status*>(arg);
|
||||
ASSERT_NE(s, nullptr);
|
||||
++count;
|
||||
ASSERT_NOK(*s);
|
||||
});
|
||||
SyncPoint::GetInstance()->EnableProcessing();
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsCorruption());
|
||||
ASSERT_EQ(1, count);
|
||||
}
|
||||
|
||||
} // 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();
|
||||
}
|
||||
|
||||
|
||||
+30
-21
@@ -63,6 +63,15 @@ class CuckooTableDBTest : public testing::Test {
|
||||
ASSERT_OK(DB::Open(opts, dbname_, &db_));
|
||||
}
|
||||
|
||||
void DestroyAndReopen(Options* options) {
|
||||
assert(options);
|
||||
ASSERT_OK(db_->Close());
|
||||
delete db_;
|
||||
db_ = nullptr;
|
||||
ASSERT_OK(DestroyDB(dbname_, *options));
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
Status Put(const Slice& k, const Slice& v) {
|
||||
return db_->Put(WriteOptions(), k, v);
|
||||
}
|
||||
@@ -120,10 +129,10 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
ASSERT_OK(Put("key1", "v1"));
|
||||
ASSERT_OK(Put("key2", "v2"));
|
||||
ASSERT_OK(Put("key3", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
ASSERT_EQ(3U, ptc.begin()->second->num_entries);
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
@@ -137,9 +146,9 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
ASSERT_OK(Put("key4", "v4"));
|
||||
ASSERT_OK(Put("key5", "v5"));
|
||||
ASSERT_OK(Put("key6", "v6"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
ASSERT_EQ(2U, ptc.size());
|
||||
auto row = ptc.begin();
|
||||
ASSERT_EQ(3U, row->second->num_entries);
|
||||
@@ -155,8 +164,8 @@ TEST_F(CuckooTableDBTest, Flush) {
|
||||
ASSERT_OK(Delete("key6"));
|
||||
ASSERT_OK(Delete("key5"));
|
||||
ASSERT_OK(Delete("key4"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
ASSERT_EQ(3U, ptc.size());
|
||||
row = ptc.begin();
|
||||
ASSERT_EQ(3U, row->second->num_entries);
|
||||
@@ -177,10 +186,10 @@ TEST_F(CuckooTableDBTest, FlushWithDuplicateKeys) {
|
||||
ASSERT_OK(Put("key1", "v1"));
|
||||
ASSERT_OK(Put("key2", "v2"));
|
||||
ASSERT_OK(Put("key1", "v3")); // Duplicate
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
TablePropertiesCollection ptc;
|
||||
reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc);
|
||||
ASSERT_OK(reinterpret_cast<DB*>(dbfull())->GetPropertiesOfAllTables(&ptc));
|
||||
ASSERT_EQ(1U, ptc.size());
|
||||
ASSERT_EQ(2U, ptc.begin()->second->num_entries);
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
@@ -205,12 +214,12 @@ static std::string Uint64Key(uint64_t i) {
|
||||
TEST_F(CuckooTableDBTest, Uint64Comparator) {
|
||||
Options options = CurrentOptions();
|
||||
options.comparator = test::Uint64Comparator();
|
||||
Reopen(&options);
|
||||
DestroyAndReopen(&options);
|
||||
|
||||
ASSERT_OK(Put(Uint64Key(1), "v1"));
|
||||
ASSERT_OK(Put(Uint64Key(2), "v2"));
|
||||
ASSERT_OK(Put(Uint64Key(3), "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
ASSERT_EQ("v1", Get(Uint64Key(1)));
|
||||
ASSERT_EQ("v2", Get(Uint64Key(2)));
|
||||
@@ -219,10 +228,10 @@ TEST_F(CuckooTableDBTest, Uint64Comparator) {
|
||||
|
||||
// Add more keys.
|
||||
ASSERT_OK(Delete(Uint64Key(2))); // Delete.
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
ASSERT_OK(Put(Uint64Key(3), "v0")); // Update.
|
||||
ASSERT_OK(Put(Uint64Key(4), "v4"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
ASSERT_EQ("v1", Get(Uint64Key(1)));
|
||||
ASSERT_EQ("NOT_FOUND", Get(Uint64Key(2)));
|
||||
ASSERT_EQ("v0", Get(Uint64Key(3)));
|
||||
@@ -242,11 +251,11 @@ TEST_F(CuckooTableDBTest, CompactionIntoMultipleFiles) {
|
||||
for (int idx = 0; idx < 28; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx))));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, nullptr,
|
||||
true /* disallow trivial move */));
|
||||
ASSERT_EQ("0,2", FilesPerLevel());
|
||||
for (int idx = 0; idx < 28; ++idx) {
|
||||
ASSERT_EQ(std::string(10000, 'a' + char(idx)), Get(Key(idx)));
|
||||
@@ -265,15 +274,15 @@ TEST_F(CuckooTableDBTest, SameKeyInsertedInTwoDifferentFilesAndCompacted) {
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a')));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_EQ("1", FilesPerLevel());
|
||||
|
||||
// Generate one more file in level-0, and should trigger level-0 compaction
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
ASSERT_OK(Put(Key(idx), std::string(10000, 'a' + char(idx))));
|
||||
}
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr));
|
||||
|
||||
ASSERT_EQ("0,1", FilesPerLevel());
|
||||
for (int idx = 0; idx < 11; ++idx) {
|
||||
@@ -294,7 +303,7 @@ TEST_F(CuckooTableDBTest, AdaptiveTable) {
|
||||
ASSERT_OK(Put("key1", "v1"));
|
||||
ASSERT_OK(Put("key2", "v2"));
|
||||
ASSERT_OK(Put("key3", "v3"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
// Write some keys using plain table.
|
||||
std::shared_ptr<TableFactory> block_based_factory(
|
||||
@@ -310,7 +319,7 @@ TEST_F(CuckooTableDBTest, AdaptiveTable) {
|
||||
Reopen(&options);
|
||||
ASSERT_OK(Put("key4", "v4"));
|
||||
ASSERT_OK(Put("key1", "v5"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
// Write some keys using block based table.
|
||||
options.table_factory.reset(NewAdaptiveTableFactory(
|
||||
@@ -319,7 +328,7 @@ TEST_F(CuckooTableDBTest, AdaptiveTable) {
|
||||
Reopen(&options);
|
||||
ASSERT_OK(Put("key5", "v6"));
|
||||
ASSERT_OK(Put("key2", "v7"));
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
|
||||
ASSERT_EQ("v5", Get("key1"));
|
||||
ASSERT_EQ("v7", Get("key2"));
|
||||
|
||||
+367
-59
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/flush_block_policy.h"
|
||||
#include "rocksdb/merge_operator.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "rocksdb/utilities/debug.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
#include "test_util/sync_point.h"
|
||||
#endif
|
||||
#include "util/file_checksum_helper.h"
|
||||
#include "util/random.h"
|
||||
#include "utilities/fault_injection_env.h"
|
||||
#include "utilities/merge_operators.h"
|
||||
@@ -37,7 +39,10 @@ TEST_F(DBBasicTest, OpenWhenOpen) {
|
||||
options.env = env_;
|
||||
DB* db2 = nullptr;
|
||||
Status s = DB::Open(options, dbname_, &db2);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_NOK(s) << [db2]() {
|
||||
delete db2;
|
||||
return "db2 open: ok";
|
||||
}();
|
||||
ASSERT_EQ(Status::Code::kIOError, s.code());
|
||||
ASSERT_EQ(Status::SubCode::kNone, s.subcode());
|
||||
ASSERT_TRUE(strstr(s.getState(), "lock ") != nullptr);
|
||||
@@ -142,7 +147,7 @@ TEST_F(DBBasicTest, ReadOnlyDB) {
|
||||
|
||||
// Reopen and flush memtable.
|
||||
Reopen(options);
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
// Now check keys in read only mode.
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
@@ -178,7 +183,7 @@ TEST_F(DBBasicTest, ReadOnlyDBWithWriteDBIdToManifestSet) {
|
||||
|
||||
// Reopen and flush memtable.
|
||||
Reopen(options);
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
// Now check keys in read only mode.
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
@@ -201,7 +206,7 @@ TEST_F(DBBasicTest, CompactedDB) {
|
||||
Reopen(options);
|
||||
// 1 L0 file, use CompactedDB if max_open_files = -1
|
||||
ASSERT_OK(Put("aaa", DummyString(kFileSize / 2, '1')));
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
Status s = Put("new", "value");
|
||||
@@ -219,12 +224,12 @@ TEST_F(DBBasicTest, CompactedDB) {
|
||||
Reopen(options);
|
||||
// Add more L0 files
|
||||
ASSERT_OK(Put("bbb", DummyString(kFileSize / 2, '2')));
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("aaa", DummyString(kFileSize / 2, 'a')));
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(Put("bbb", DummyString(kFileSize / 2, 'b')));
|
||||
ASSERT_OK(Put("eee", DummyString(kFileSize / 2, 'e')));
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
Close();
|
||||
|
||||
ASSERT_OK(ReadOnlyReopen(options));
|
||||
@@ -401,16 +406,18 @@ TEST_F(DBBasicTest, GetSnapshot) {
|
||||
|
||||
TEST_F(DBBasicTest, CheckLock) {
|
||||
do {
|
||||
DB* localdb;
|
||||
DB* localdb = nullptr;
|
||||
Options options = CurrentOptions();
|
||||
ASSERT_OK(TryReopen(options));
|
||||
|
||||
// second open should fail
|
||||
Status s = DB::Open(options, dbname_, &localdb);
|
||||
ASSERT_NOK(s);
|
||||
ASSERT_NOK(s) << [localdb]() {
|
||||
delete localdb;
|
||||
return "localdb open: ok";
|
||||
}();
|
||||
#ifdef OS_LINUX
|
||||
ASSERT_TRUE(s.ToString().find("lock hold by current process") !=
|
||||
std::string::npos);
|
||||
ASSERT_TRUE(s.ToString().find("lock ") != std::string::npos);
|
||||
#endif // OS_LINUX
|
||||
} while (ChangeCompactOptions());
|
||||
}
|
||||
@@ -1407,10 +1414,10 @@ TEST_F(DBBasicTest, MultiGetBatchedSortedMultiFile) {
|
||||
// mix with memtable
|
||||
ASSERT_OK(Put(1, "k1", "v1"));
|
||||
ASSERT_OK(Put(1, "k2", "v2"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "k3", "v3"));
|
||||
ASSERT_OK(Put(1, "k4", "v4"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Delete(1, "k4"));
|
||||
ASSERT_OK(Put(1, "k5", "v5"));
|
||||
ASSERT_OK(Delete(1, "no_key"));
|
||||
@@ -1453,19 +1460,19 @@ TEST_F(DBBasicTest, MultiGetBatchedDuplicateKeys) {
|
||||
// mix with memtable
|
||||
ASSERT_OK(Merge(1, "k1", "v1"));
|
||||
ASSERT_OK(Merge(1, "k2", "v2"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(2, 1);
|
||||
ASSERT_OK(Merge(1, "k3", "v3"));
|
||||
ASSERT_OK(Merge(1, "k4", "v4"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(2, 1);
|
||||
ASSERT_OK(Merge(1, "k4", "v4_2"));
|
||||
ASSERT_OK(Merge(1, "k6", "v6"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(2, 1);
|
||||
ASSERT_OK(Merge(1, "k7", "v7"));
|
||||
ASSERT_OK(Merge(1, "k8", "v8"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(2, 1);
|
||||
|
||||
get_perf_context()->Reset();
|
||||
@@ -1505,12 +1512,12 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l2_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
@@ -1519,12 +1526,12 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l1_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(1);
|
||||
@@ -1533,12 +1540,12 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevel) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l0_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
ASSERT_EQ(0, num_keys);
|
||||
@@ -1584,12 +1591,12 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevelMerge) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l2_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
@@ -1598,12 +1605,12 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevelMerge) {
|
||||
ASSERT_OK(Merge("key_" + std::to_string(i), "val_l1_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(1);
|
||||
@@ -1612,12 +1619,12 @@ TEST_F(DBBasicTest, MultiGetBatchedMultiLevelMerge) {
|
||||
ASSERT_OK(Merge("key_" + std::to_string(i), "val_l0_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
ASSERT_EQ(0, num_keys);
|
||||
@@ -1699,7 +1706,7 @@ TEST_F(DBBasicTest, MultiGetBatchedValueSize) {
|
||||
ASSERT_OK(Put(1, "k7", "v7_"));
|
||||
ASSERT_OK(Put(1, "k3", "v3_"));
|
||||
ASSERT_OK(Put(1, "k4", "v4"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Delete(1, "k4"));
|
||||
ASSERT_OK(Put(1, "k11", "v11"));
|
||||
ASSERT_OK(Delete(1, "no_key"));
|
||||
@@ -1709,7 +1716,7 @@ TEST_F(DBBasicTest, MultiGetBatchedValueSize) {
|
||||
ASSERT_OK(Put(1, "k15", "v15"));
|
||||
ASSERT_OK(Put(1, "k16", "v16"));
|
||||
ASSERT_OK(Put(1, "k17", "v17"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
ASSERT_OK(Put(1, "k1", "v1_"));
|
||||
ASSERT_OK(Put(1, "k2", "v2_"));
|
||||
@@ -1779,12 +1786,12 @@ TEST_F(DBBasicTest, MultiGetBatchedValueSizeMultiLevelMerge) {
|
||||
ASSERT_OK(Put("key_" + std::to_string(i), "val_l2_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(2);
|
||||
@@ -1793,12 +1800,12 @@ TEST_F(DBBasicTest, MultiGetBatchedValueSizeMultiLevelMerge) {
|
||||
ASSERT_OK(Merge("key_" + std::to_string(i), "val_l1_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
MoveFilesToLevel(1);
|
||||
@@ -1807,12 +1814,12 @@ TEST_F(DBBasicTest, MultiGetBatchedValueSizeMultiLevelMerge) {
|
||||
ASSERT_OK(Merge("key_" + std::to_string(i), "val_l0_" + std::to_string(i)));
|
||||
num_keys++;
|
||||
if (num_keys == 8) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
}
|
||||
if (num_keys > 0) {
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
num_keys = 0;
|
||||
}
|
||||
ASSERT_EQ(0, num_keys);
|
||||
@@ -1875,6 +1882,7 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.disable_auto_compactions = true;
|
||||
options.env = env_;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1;
|
||||
@@ -1884,7 +1892,7 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
table_options.no_block_cache = true;
|
||||
table_options.cache_index_and_filter_blocks = false;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(10, false));
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
|
||||
int total_keys = 2000;
|
||||
@@ -1901,10 +1909,10 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
keys[i] = Slice(keys_str[i]);
|
||||
ASSERT_OK(Put(1, "k" + std::to_string(i), rnd.RandomString(1000)));
|
||||
if (i % 100 == 0) {
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
}
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(2, 1);
|
||||
|
||||
for (int i = 501; i < 1000; ++i) {
|
||||
@@ -1912,11 +1920,11 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
keys[i] = Slice(keys_str[i]);
|
||||
ASSERT_OK(Put(1, "k" + std::to_string(i), rnd.RandomString(1000)));
|
||||
if (i % 100 == 0) {
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
}
|
||||
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
MoveFilesToLevel(2, 1);
|
||||
|
||||
for (int i = 1001; i < total_keys; ++i) {
|
||||
@@ -1924,10 +1932,10 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
keys[i] = Slice(keys_str[i]);
|
||||
ASSERT_OK(Put(1, "k" + std::to_string(i), rnd.RandomString(1000)));
|
||||
if (i % 100 == 0) {
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
}
|
||||
}
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
Close();
|
||||
|
||||
ReopenWithColumnFamilies({"default", "pikachu"}, options);
|
||||
@@ -1954,7 +1962,7 @@ TEST_F(DBBasicTest, MultiGetStats) {
|
||||
ASSERT_GT(hist_sst.max, 0);
|
||||
|
||||
// Minimun number of blocks read in a level.
|
||||
ASSERT_EQ(hist_data_blocks.min, 0);
|
||||
ASSERT_EQ(hist_data_blocks.min, 3);
|
||||
ASSERT_GT(hist_index_and_filter_blocks.min, 0);
|
||||
// Minimun number of sst files read in a level.
|
||||
ASSERT_GT(hist_sst.max, 0);
|
||||
@@ -2035,11 +2043,11 @@ TEST_P(DBMultiGetRowCacheTest, MultiGetBatched) {
|
||||
ASSERT_OK(Put(1, "k2", "v2"));
|
||||
ASSERT_OK(Put(1, "k3", "v3"));
|
||||
ASSERT_OK(Put(1, "k4", "v4"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
ASSERT_OK(Put(1, "k5", "v5"));
|
||||
const Snapshot* snap1 = dbfull()->GetSnapshot();
|
||||
ASSERT_OK(Delete(1, "k4"));
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
const Snapshot* snap2 = dbfull()->GetSnapshot();
|
||||
|
||||
get_perf_context()->Reset();
|
||||
@@ -2168,7 +2176,7 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
|
||||
table_options.block_size = 16 * 1024;
|
||||
ASSERT_TRUE(table_options.block_size >
|
||||
BlockBasedTable::kMultiGetReadStackBufSize);
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
Reopen(options);
|
||||
|
||||
std::string zero_str(128, '\0');
|
||||
@@ -2178,7 +2186,7 @@ TEST_F(DBBasicTest, MultiGetIOBufferOverrun) {
|
||||
std::string value(rnd.RandomString(128) + zero_str);
|
||||
assert(Put(Key(i), value) == Status::OK());
|
||||
}
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
std::vector<std::string> key_data(10);
|
||||
std::vector<Slice> keys;
|
||||
@@ -2278,6 +2286,43 @@ class TableFileListener : public EventListener {
|
||||
};
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBBasicTest, LastSstFileNotInManifest) {
|
||||
// If the last sst file is not tracked in MANIFEST,
|
||||
// or the VersionEdit for the last sst file is not synced,
|
||||
// on recovery, the last sst file should be deleted,
|
||||
// and new sst files shouldn't reuse its file number.
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
Close();
|
||||
|
||||
// Manually add a sst file.
|
||||
constexpr uint64_t kSstFileNumber = 100;
|
||||
const std::string kSstFile = MakeTableFileName(dbname_, kSstFileNumber);
|
||||
ASSERT_OK(WriteStringToFile(env_, /* data = */ "bad sst file content",
|
||||
/* fname = */ kSstFile,
|
||||
/* should_sync = */ true));
|
||||
ASSERT_OK(env_->FileExists(kSstFile));
|
||||
|
||||
TableFileListener* listener = new TableFileListener();
|
||||
options.listeners.emplace_back(listener);
|
||||
Reopen(options);
|
||||
// kSstFile should already be deleted.
|
||||
ASSERT_TRUE(env_->FileExists(kSstFile).IsNotFound());
|
||||
|
||||
ASSERT_OK(Put("k", "v"));
|
||||
ASSERT_OK(Flush());
|
||||
// New sst file should have file number > kSstFileNumber.
|
||||
std::vector<std::string>& files =
|
||||
listener->GetFiles(kDefaultColumnFamilyName);
|
||||
ASSERT_EQ(files.size(), 1);
|
||||
const std::string fname = files[0].erase(0, (dbname_ + "/").size());
|
||||
uint64_t number = 0;
|
||||
FileType type = kTableFile;
|
||||
ASSERT_TRUE(ParseFileName(fname, &number, &type));
|
||||
ASSERT_EQ(type, kTableFile);
|
||||
ASSERT_GT(number, kSstFileNumber);
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, RecoverWithMissingFiles) {
|
||||
Options options = CurrentOptions();
|
||||
DestroyAndReopen(options);
|
||||
@@ -2405,7 +2450,7 @@ TEST_F(DBBasicTest, RecoverWithNoManifest) {
|
||||
ASSERT_OK(env_->GetChildren(dbname_, &files));
|
||||
for (const auto& file : files) {
|
||||
uint64_t number = 0;
|
||||
FileType type = kLogFile;
|
||||
FileType type = kWalFile;
|
||||
if (ParseFileName(file, &number, &type) && type == kDescriptorFile) {
|
||||
ASSERT_OK(env_->DeleteFile(dbname_ + "/" + file));
|
||||
}
|
||||
@@ -2461,6 +2506,42 @@ TEST_F(DBBasicTest, SkipWALIfMissingTableFiles) {
|
||||
ASSERT_FALSE(iter->Valid());
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
|
||||
TEST_F(DBBasicTest, DisableTrackWal) {
|
||||
// If WAL tracking was enabled, and then disabled during reopen,
|
||||
// the previously tracked WALs should be removed from MANIFEST.
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
// extremely small write buffer size,
|
||||
// so that new WALs are created more frequently.
|
||||
options.write_buffer_size = 100;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
ASSERT_OK(Put("foo" + std::to_string(i), "value" + std::to_string(i)));
|
||||
}
|
||||
ASSERT_OK(dbfull()->TEST_SwitchMemtable());
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
// Some WALs are tracked.
|
||||
ASSERT_FALSE(dbfull()->TEST_GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
Close();
|
||||
|
||||
// Disable WAL tracking.
|
||||
options.track_and_verify_wals_in_manifest = false;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
// Previously tracked WALs are cleared.
|
||||
ASSERT_TRUE(dbfull()->TEST_GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
Close();
|
||||
|
||||
// Re-enable WAL tracking again.
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
options.create_if_missing = false;
|
||||
ASSERT_OK(TryReopen(options));
|
||||
ASSERT_TRUE(dbfull()->TEST_GetVersionSet()->GetWalSet().GetWals().empty());
|
||||
Close();
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBBasicTest, ManifestChecksumMismatch) {
|
||||
@@ -2490,6 +2571,64 @@ TEST_F(DBBasicTest, ManifestChecksumMismatch) {
|
||||
ASSERT_TRUE(s.IsCorruption());
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
class DBBasicTestTrackWal : public DBTestBase,
|
||||
public testing::WithParamInterface<bool> {
|
||||
public:
|
||||
DBBasicTestTrackWal()
|
||||
: DBTestBase("/db_basic_test_track_wal", /*env_do_fsync=*/false) {}
|
||||
|
||||
int CountWalFiles() {
|
||||
VectorLogPtr log_files;
|
||||
EXPECT_OK(dbfull()->GetSortedWalFiles(log_files));
|
||||
return static_cast<int>(log_files.size());
|
||||
};
|
||||
};
|
||||
|
||||
TEST_P(DBBasicTestTrackWal, DoNotTrackObsoleteWal) {
|
||||
// If a WAL becomes obsolete after flushing, but is not deleted from disk yet,
|
||||
// then if SyncWAL is called afterwards, the obsolete WAL should not be
|
||||
// tracked in MANIFEST.
|
||||
|
||||
Options options = CurrentOptions();
|
||||
options.create_if_missing = true;
|
||||
options.track_and_verify_wals_in_manifest = true;
|
||||
options.atomic_flush = GetParam();
|
||||
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"cf"}, options);
|
||||
ASSERT_EQ(handles_.size(), 2); // default, cf
|
||||
// Do not delete WALs.
|
||||
ASSERT_OK(db_->DisableFileDeletions());
|
||||
constexpr int n = 10;
|
||||
std::vector<std::unique_ptr<LogFile>> wals(n);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
// Generate a new WAL for each key-value.
|
||||
const int cf = i % 2;
|
||||
ASSERT_OK(db_->GetCurrentWalFile(&wals[i]));
|
||||
ASSERT_OK(Put(cf, "k" + std::to_string(i), "v" + std::to_string(i)));
|
||||
ASSERT_OK(Flush({0, 1}));
|
||||
}
|
||||
ASSERT_EQ(CountWalFiles(), n);
|
||||
// Since all WALs are obsolete, no WAL should be tracked in MANIFEST.
|
||||
ASSERT_OK(db_->SyncWAL());
|
||||
|
||||
// Manually delete all WALs.
|
||||
Close();
|
||||
for (const auto& wal : wals) {
|
||||
ASSERT_OK(env_->DeleteFile(LogFileName(dbname_, wal->LogNumber())));
|
||||
}
|
||||
|
||||
// If SyncWAL tracks the obsolete WALs in MANIFEST,
|
||||
// reopen will fail because the WALs are missing from disk.
|
||||
ASSERT_OK(TryReopenWithColumnFamilies({"default", "cf"}, options));
|
||||
Destroy(options);
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(DBBasicTestTrackWal, DBBasicTestTrackWal,
|
||||
testing::Bool());
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
class DBBasicTestMultiGet : public DBTestBase {
|
||||
public:
|
||||
DBBasicTestMultiGet(std::string test_dir, int num_cfs, bool compressed_cache,
|
||||
@@ -2549,12 +2688,13 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
table_options.block_cache_compressed = compressed_cache_;
|
||||
table_options.flush_block_policy_factory.reset(
|
||||
new MyFlushBlockPolicyFactory());
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
if (!compression_enabled_) {
|
||||
options.compression = kNoCompression;
|
||||
} else {
|
||||
options.compression_opts.parallel_threads = compression_parallel_threads;
|
||||
}
|
||||
options_ = options;
|
||||
Reopen(options);
|
||||
|
||||
if (num_cfs > 1) {
|
||||
@@ -2575,9 +2715,9 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
: Put(cf, Key(i), values_[i])) == Status::OK());
|
||||
}
|
||||
if (num_cfs == 1) {
|
||||
Flush();
|
||||
EXPECT_OK(Flush());
|
||||
} else {
|
||||
dbfull()->Flush(FlushOptions(), handles_[cf]);
|
||||
EXPECT_OK(dbfull()->Flush(FlushOptions(), handles_[cf]));
|
||||
}
|
||||
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
@@ -2589,9 +2729,9 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
Status::OK());
|
||||
}
|
||||
if (num_cfs == 1) {
|
||||
Flush();
|
||||
EXPECT_OK(Flush());
|
||||
} else {
|
||||
dbfull()->Flush(FlushOptions(), handles_[cf]);
|
||||
EXPECT_OK(dbfull()->Flush(FlushOptions(), handles_[cf]));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2624,6 +2764,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
bool compression_enabled() { return compression_enabled_; }
|
||||
bool has_compressed_cache() { return compressed_cache_ != nullptr; }
|
||||
bool has_uncompressed_cache() { return uncompressed_cache_ != nullptr; }
|
||||
Options get_options() { return options_; }
|
||||
|
||||
static void SetUpTestCase() {}
|
||||
static void TearDownTestCase() {}
|
||||
@@ -2679,6 +2820,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
|
||||
const char* Name() const override { return "MyBlockCache"; }
|
||||
|
||||
using Cache::Insert;
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value),
|
||||
Handle** handle = nullptr,
|
||||
@@ -2687,6 +2829,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
return target_->Insert(key, value, charge, deleter, handle, priority);
|
||||
}
|
||||
|
||||
using Cache::Lookup;
|
||||
Handle* Lookup(const Slice& key, Statistics* stats = nullptr) override {
|
||||
num_lookups_++;
|
||||
Handle* handle = target_->Lookup(key, stats);
|
||||
@@ -2709,6 +2852,7 @@ class DBBasicTestMultiGet : public DBTestBase {
|
||||
|
||||
std::shared_ptr<MyBlockCache> compressed_cache_;
|
||||
std::shared_ptr<MyBlockCache> uncompressed_cache_;
|
||||
Options options_;
|
||||
bool compression_enabled_;
|
||||
std::vector<std::string> values_;
|
||||
std::vector<std::string> uncompressable_values_;
|
||||
@@ -2851,6 +2995,123 @@ TEST_P(DBBasicTestWithParallelIO, MultiGet) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGetDirectIO) {
|
||||
class FakeDirectIOEnv : public EnvWrapper {
|
||||
class FakeDirectIOSequentialFile;
|
||||
class FakeDirectIORandomAccessFile;
|
||||
|
||||
public:
|
||||
FakeDirectIOEnv(Env* env) : EnvWrapper(env) {}
|
||||
|
||||
Status NewRandomAccessFile(const std::string& fname,
|
||||
std::unique_ptr<RandomAccessFile>* result,
|
||||
const EnvOptions& options) override {
|
||||
std::unique_ptr<RandomAccessFile> file;
|
||||
assert(options.use_direct_reads);
|
||||
EnvOptions opts = options;
|
||||
opts.use_direct_reads = false;
|
||||
Status s = target()->NewRandomAccessFile(fname, &file, opts);
|
||||
if (!s.ok()) {
|
||||
return s;
|
||||
}
|
||||
result->reset(new FakeDirectIORandomAccessFile(std::move(file)));
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
class FakeDirectIOSequentialFile : public SequentialFileWrapper {
|
||||
public:
|
||||
FakeDirectIOSequentialFile(std::unique_ptr<SequentialFile>&& file)
|
||||
: SequentialFileWrapper(file.get()), file_(std::move(file)) {}
|
||||
~FakeDirectIOSequentialFile() {}
|
||||
|
||||
bool use_direct_io() const override { return true; }
|
||||
size_t GetRequiredBufferAlignment() const override { return 1; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<SequentialFile> file_;
|
||||
};
|
||||
|
||||
class FakeDirectIORandomAccessFile : public RandomAccessFileWrapper {
|
||||
public:
|
||||
FakeDirectIORandomAccessFile(std::unique_ptr<RandomAccessFile>&& file)
|
||||
: RandomAccessFileWrapper(file.get()), file_(std::move(file)) {}
|
||||
~FakeDirectIORandomAccessFile() {}
|
||||
|
||||
bool use_direct_io() const override { return true; }
|
||||
size_t GetRequiredBufferAlignment() const override { return 1; }
|
||||
|
||||
private:
|
||||
std::unique_ptr<RandomAccessFile> file_;
|
||||
};
|
||||
};
|
||||
|
||||
std::unique_ptr<FakeDirectIOEnv> env(new FakeDirectIOEnv(env_));
|
||||
Options opts = get_options();
|
||||
opts.env = env.get();
|
||||
opts.use_direct_reads = true;
|
||||
Reopen(opts);
|
||||
|
||||
std::vector<std::string> key_data(10);
|
||||
std::vector<Slice> keys;
|
||||
// We cannot resize a PinnableSlice vector, so just set initial size to
|
||||
// largest we think we will need
|
||||
std::vector<PinnableSlice> values(10);
|
||||
std::vector<Status> statuses;
|
||||
ReadOptions ro;
|
||||
ro.fill_cache = fill_cache();
|
||||
|
||||
// Warm up the cache first
|
||||
key_data.emplace_back(Key(0));
|
||||
keys.emplace_back(Slice(key_data.back()));
|
||||
key_data.emplace_back(Key(50));
|
||||
keys.emplace_back(Slice(key_data.back()));
|
||||
statuses.resize(keys.size());
|
||||
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(CheckValue(0, values[0].ToString()));
|
||||
ASSERT_TRUE(CheckValue(50, values[1].ToString()));
|
||||
|
||||
int random_reads = env_->random_read_counter_.Read();
|
||||
key_data[0] = Key(1);
|
||||
key_data[1] = Key(51);
|
||||
keys[0] = Slice(key_data[0]);
|
||||
keys[1] = Slice(key_data[1]);
|
||||
values[0].Reset();
|
||||
values[1].Reset();
|
||||
if (uncompressed_cache_) {
|
||||
uncompressed_cache_->SetCapacity(0);
|
||||
uncompressed_cache_->SetCapacity(1048576);
|
||||
}
|
||||
dbfull()->MultiGet(ro, dbfull()->DefaultColumnFamily(), keys.size(),
|
||||
keys.data(), values.data(), statuses.data(), true);
|
||||
ASSERT_TRUE(CheckValue(1, values[0].ToString()));
|
||||
ASSERT_TRUE(CheckValue(51, values[1].ToString()));
|
||||
|
||||
bool read_from_cache = false;
|
||||
if (fill_cache()) {
|
||||
if (has_uncompressed_cache()) {
|
||||
read_from_cache = true;
|
||||
} else if (has_compressed_cache() && compression_enabled()) {
|
||||
read_from_cache = true;
|
||||
}
|
||||
}
|
||||
|
||||
int expected_reads = random_reads;
|
||||
if (!compression_enabled() || !has_compressed_cache()) {
|
||||
expected_reads += 2;
|
||||
} else {
|
||||
expected_reads += (read_from_cache ? 0 : 2);
|
||||
}
|
||||
if (env_->random_read_counter_.Read() != expected_reads) {
|
||||
ASSERT_EQ(env_->random_read_counter_.Read(), expected_reads);
|
||||
}
|
||||
Close();
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_P(DBBasicTestWithParallelIO, MultiGetWithChecksumMismatch) {
|
||||
std::vector<std::string> key_data(10);
|
||||
std::vector<Slice> keys;
|
||||
@@ -2976,7 +3237,7 @@ class DeadlineFS : public FileSystemWrapper {
|
||||
// or to simply delay but return success anyway. The latter mimics the
|
||||
// behavior of PosixFileSystem, which does not enforce any timeout
|
||||
explicit DeadlineFS(SpecialEnv* env, bool error_on_delay)
|
||||
: FileSystemWrapper(FileSystem::Default()),
|
||||
: FileSystemWrapper(env->GetFileSystem()),
|
||||
deadline_(std::chrono::microseconds::zero()),
|
||||
io_timeout_(std::chrono::microseconds::zero()),
|
||||
env_(env),
|
||||
@@ -3015,7 +3276,9 @@ class DeadlineFS : public FileSystemWrapper {
|
||||
|
||||
// Increment the IO counter and return a delay in microseconds
|
||||
IOStatus ShouldDelay(const IOOptions& opts) {
|
||||
if (!deadline_.count() && !io_timeout_.count()) {
|
||||
if (timedout_) {
|
||||
return IOStatus::TimedOut();
|
||||
} else if (!deadline_.count() && !io_timeout_.count()) {
|
||||
return IOStatus::OK();
|
||||
}
|
||||
if (!ignore_deadline_ && delay_trigger_ == io_count_++) {
|
||||
@@ -3151,7 +3414,7 @@ TEST_F(DBBasicTestMultiGetDeadline, MultiGetDeadlineExceeded) {
|
||||
std::shared_ptr<Cache> cache = NewLRUCache(1048576);
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_cache = cache;
|
||||
options.table_factory.reset(new BlockBasedTableFactory(table_options));
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
options.env = env.get();
|
||||
SetTimeElapseOnlySleepOnReopen(&options);
|
||||
ReopenWithColumnFamilies(GetCFNames(), options);
|
||||
@@ -3303,6 +3566,51 @@ TEST_F(DBBasicTest, ManifestWriteFailure) {
|
||||
Reopen(options);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
TEST_F(DBBasicTest, VerifyFileChecksums) {
|
||||
Options options = GetDefaultOptions();
|
||||
options.create_if_missing = true;
|
||||
options.env = env_;
|
||||
DestroyAndReopen(options);
|
||||
ASSERT_OK(Put("a", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument());
|
||||
|
||||
options.file_checksum_gen_factory = GetFileChecksumGenCrc32cFactory();
|
||||
Reopen(options);
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
|
||||
// Write an L0 with checksum computed.
|
||||
ASSERT_OK(Put("b", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(db_->VerifyFileChecksums(ReadOptions()));
|
||||
|
||||
// Does the right thing but with the wrong name -- using it should lead to an
|
||||
// error.
|
||||
class MisnamedFileChecksumGenerator : public FileChecksumGenCrc32c {
|
||||
public:
|
||||
MisnamedFileChecksumGenerator(const FileChecksumGenContext& context)
|
||||
: FileChecksumGenCrc32c(context) {}
|
||||
|
||||
const char* Name() const override { return "sha1"; }
|
||||
};
|
||||
|
||||
class MisnamedFileChecksumGenFactory : public FileChecksumGenCrc32cFactory {
|
||||
public:
|
||||
std::unique_ptr<FileChecksumGenerator> CreateFileChecksumGenerator(
|
||||
const FileChecksumGenContext& context) override {
|
||||
return std::unique_ptr<FileChecksumGenerator>(
|
||||
new MisnamedFileChecksumGenerator(context));
|
||||
}
|
||||
};
|
||||
|
||||
options.file_checksum_gen_factory.reset(new MisnamedFileChecksumGenFactory());
|
||||
Reopen(options);
|
||||
ASSERT_TRUE(db_->VerifyFileChecksums(ReadOptions()).IsInvalidArgument());
|
||||
}
|
||||
#endif // !ROCKSDB_LITE
|
||||
|
||||
// A test class for intercepting random reads and injecting artificial
|
||||
// delays. Used for testing the deadline/timeout feature
|
||||
class DBBasicTestDeadline
|
||||
@@ -3363,7 +3671,7 @@ TEST_P(DBBasicTestDeadline, PointLookupDeadline) {
|
||||
std::string key = "k" + ToString(i);
|
||||
ASSERT_OK(Put(key, rnd.RandomString(100)));
|
||||
}
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
bool timedout = true;
|
||||
// A timeout will be forced when the IO counter reaches this value
|
||||
|
||||
+365
-38
@@ -7,10 +7,14 @@
|
||||
// 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 <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/table.h"
|
||||
#include "util/compression.h"
|
||||
#include "util/random.h"
|
||||
|
||||
@@ -147,6 +151,19 @@ class DBBlockCacheTest : public DBTestBase {
|
||||
compressed_insert_count_ = new_insert_count;
|
||||
compressed_failure_count_ = new_failure_count;
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
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();
|
||||
InternalStats::CacheEntryRoleStats stats;
|
||||
internal_stats_ptr->TEST_GetCacheEntryRoleStats(&stats,
|
||||
/*foreground=*/false);
|
||||
return stats.entry_counts;
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
};
|
||||
|
||||
TEST_F(DBBlockCacheTest, IteratorBlockCacheUsage) {
|
||||
@@ -156,7 +173,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);
|
||||
@@ -180,7 +203,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);
|
||||
@@ -228,34 +257,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);
|
||||
@@ -267,24 +316,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());
|
||||
CheckCacheCounters(options, 1, 0, 0, 1);
|
||||
|
||||
// Load last key block.
|
||||
ASSERT_EQ("Result incomplete: Insert failed due to LRU cache being full.",
|
||||
Get(ToString(kNumBlocks - 1)));
|
||||
// Failure won't record the miss counter.
|
||||
CheckCacheCounters(options, 0, 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
|
||||
|
||||
@@ -432,6 +478,33 @@ 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));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
// A mock cache wraps LRUCache, and record how many entries have been
|
||||
@@ -446,15 +519,18 @@ class MockCache : public LRUCache {
|
||||
false /*strict_capacity_limit*/, 0.0 /*high_pri_pool_ratio*/) {
|
||||
}
|
||||
|
||||
Status Insert(const Slice& key, void* value, size_t charge,
|
||||
void (*deleter)(const Slice& key, void* value), Handle** handle,
|
||||
Priority priority) override {
|
||||
using ShardedCache::Insert;
|
||||
|
||||
Status Insert(const Slice& key, void* value,
|
||||
const Cache::CacheItemHelper* helper_cb, size_t charge,
|
||||
Handle** handle, Priority priority) override {
|
||||
DeleterFn delete_cb = helper_cb->del_cb;
|
||||
if (priority == Priority::LOW) {
|
||||
low_pri_insert_count++;
|
||||
} else {
|
||||
high_pri_insert_count++;
|
||||
}
|
||||
return LRUCache::Insert(key, value, charge, deleter, handle, priority);
|
||||
return LRUCache::Insert(key, value, charge, delete_cb, handle, priority);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -533,6 +609,7 @@ class LookupLiarCache : public CacheWrapper {
|
||||
explicit LookupLiarCache(std::shared_ptr<Cache> target)
|
||||
: CacheWrapper(std::move(target)) {}
|
||||
|
||||
using Cache::Lookup;
|
||||
Handle* Lookup(const Slice& key, Statistics* stats) override {
|
||||
if (nth_lookup_not_found_ == 1) {
|
||||
nth_lookup_not_found_ = 0;
|
||||
@@ -677,7 +754,7 @@ TEST_F(DBBlockCacheTest, ParanoidFileChecks) {
|
||||
// Create a new SST file. This will further trigger a compaction
|
||||
// and generate another file.
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(3, /* Totally 3 files created up to now */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
@@ -692,7 +769,7 @@ TEST_F(DBBlockCacheTest, ParanoidFileChecks) {
|
||||
ASSERT_OK(Put(1, "1_key4", "val4"));
|
||||
ASSERT_OK(Put(1, "9_key4", "val4"));
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(3, /* Totally 3 files created up to now */
|
||||
TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
}
|
||||
@@ -837,8 +914,9 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
Random rnd(301);
|
||||
for (auto compression_type : compression_types) {
|
||||
Options options = CurrentOptions();
|
||||
options.compression = compression_type;
|
||||
options.compression_opts.max_dict_bytes = 4096;
|
||||
options.bottommost_compression = compression_type;
|
||||
options.bottommost_compression_opts.max_dict_bytes = 4096;
|
||||
options.bottommost_compression_opts.enabled = true;
|
||||
options.create_if_missing = true;
|
||||
options.num_levels = 2;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
@@ -859,7 +937,7 @@ TEST_F(DBBlockCacheTest, CacheCompressionDict) {
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_EQ(0, NumTableFilesAtLevel(0));
|
||||
ASSERT_EQ(kNumFiles, NumTableFilesAtLevel(1));
|
||||
|
||||
@@ -886,6 +964,252 @@ 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) {
|
||||
if (roles.find(deleter) == roles.end()) {
|
||||
// Keep the stats collector
|
||||
return;
|
||||
}
|
||||
keys.push_back(key.ToString());
|
||||
};
|
||||
cache->ApplyToAllEntries(callback, opts);
|
||||
for (auto& k : keys) {
|
||||
cache->Erase(k);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DBBlockCacheTest, CacheEntryRoleStats) {
|
||||
const size_t capacity = size_t{1} << 25;
|
||||
int iterations_tested = 0;
|
||||
for (bool partition : {false, true}) {
|
||||
for (std::shared_ptr<Cache> cache :
|
||||
{NewLRUCache(capacity), NewClockCache(capacity)}) {
|
||||
if (!cache) {
|
||||
// Skip clock cache when not supported
|
||||
continue;
|
||||
}
|
||||
++iterations_tested;
|
||||
|
||||
Options options = CurrentOptions();
|
||||
SetTimeElapseOnlySleepOnReopen(&options);
|
||||
options.create_if_missing = true;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
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;
|
||||
table_options.cache_index_and_filter_blocks = true;
|
||||
table_options.filter_policy.reset(NewBloomFilterPolicy(50));
|
||||
if (partition) {
|
||||
table_options.index_type = BlockBasedTableOptions::kTwoLevelIndexSearch;
|
||||
table_options.partition_filters = true;
|
||||
}
|
||||
table_options.metadata_cache_options.top_level_index_pinning =
|
||||
PinningTier::kNone;
|
||||
table_options.metadata_cache_options.partition_pinning =
|
||||
PinningTier::kNone;
|
||||
table_options.metadata_cache_options.unpartitioned_pinning =
|
||||
PinningTier::kNone;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Create a new table.
|
||||
ASSERT_OK(Put("foo", "value"));
|
||||
ASSERT_OK(Put("bar", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_OK(Put("zfoo", "value"));
|
||||
ASSERT_OK(Put("zbar", "value"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ASSERT_EQ(2, NumTableFilesAtLevel(0));
|
||||
|
||||
// Fresh cache
|
||||
ClearCache(cache.get());
|
||||
|
||||
std::array<size_t, kNumCacheEntryRoles> expected{};
|
||||
// For CacheEntryStatsCollector
|
||||
expected[static_cast<size_t>(CacheEntryRole::kMisc)] = 1;
|
||||
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"));
|
||||
expected[static_cast<size_t>(CacheEntryRole::kFilterBlock)] += 2;
|
||||
if (partition) {
|
||||
expected[static_cast<size_t>(CacheEntryRole::kFilterMetaBlock)] += 2;
|
||||
}
|
||||
// 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"));
|
||||
expected[static_cast<size_t>(CacheEntryRole::kIndexBlock)]++;
|
||||
if (partition) {
|
||||
// top-level
|
||||
expected[static_cast<size_t>(CacheEntryRole::kIndexBlock)]++;
|
||||
}
|
||||
expected[static_cast<size_t>(CacheEntryRole::kDataBlock)]++;
|
||||
// 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"));
|
||||
expected[static_cast<size_t>(CacheEntryRole::kIndexBlock)]++;
|
||||
if (partition) {
|
||||
// top-level
|
||||
expected[static_cast<size_t>(CacheEntryRole::kIndexBlock)]++;
|
||||
}
|
||||
expected[static_cast<size_t>(CacheEntryRole::kDataBlock)]++;
|
||||
// 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;
|
||||
ASSERT_TRUE(
|
||||
db_->GetMapProperty(DB::Properties::kBlockCacheEntryStats, &values));
|
||||
|
||||
EXPECT_EQ(
|
||||
ToString(expected[static_cast<size_t>(CacheEntryRole::kIndexBlock)]),
|
||||
values["count.index-block"]);
|
||||
EXPECT_EQ(
|
||||
ToString(expected[static_cast<size_t>(CacheEntryRole::kDataBlock)]),
|
||||
values["count.data-block"]);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
class DBBlockCachePinningTest
|
||||
@@ -991,6 +1315,9 @@ TEST_P(DBBlockCachePinningTest, TwoLevelDB) {
|
||||
++expected_index_misses;
|
||||
}
|
||||
}
|
||||
if (unpartitioned_pinning_ == PinningTier::kNone) {
|
||||
++expected_compression_dict_misses;
|
||||
}
|
||||
ASSERT_EQ(expected_filter_misses,
|
||||
TestGetTickerCount(options, BLOCK_CACHE_FILTER_MISS));
|
||||
ASSERT_EQ(expected_index_misses,
|
||||
|
||||
+143
-41
@@ -7,11 +7,15 @@
|
||||
// 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 <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "options/options_helper.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "rocksdb/perf_context.h"
|
||||
#include "table/block_based/filter_policy_internal.h"
|
||||
#include "util/string_util.h"
|
||||
|
||||
namespace ROCKSDB_NAMESPACE {
|
||||
|
||||
@@ -128,8 +132,8 @@ TEST_P(DBBloomFilterTestDefFormatVersion, KeyMayExist) {
|
||||
ASSERT_EQ(cache_added, TestGetTickerCount(options, BLOCK_CACHE_ADD));
|
||||
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1],
|
||||
true /* disallow trivial move */);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1],
|
||||
true /* disallow trivial move */));
|
||||
|
||||
numopen = TestGetTickerCount(options, NO_FILE_OPENS);
|
||||
cache_added = TestGetTickerCount(options, BLOCK_CACHE_ADD);
|
||||
@@ -178,7 +182,7 @@ TEST_F(DBBloomFilterTest, GetFilterByPrefixBloomCustomPrefixExtractor) {
|
||||
ASSERT_OK(dbfull()->Put(wo, "barbarbar2", "foo2"));
|
||||
ASSERT_OK(dbfull()->Put(wo, "foofoofoo", "bar"));
|
||||
|
||||
dbfull()->Flush(fo);
|
||||
ASSERT_OK(dbfull()->Flush(fo));
|
||||
|
||||
ASSERT_EQ("foo", Get("barbarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
@@ -244,7 +248,7 @@ TEST_F(DBBloomFilterTest, GetFilterByPrefixBloom) {
|
||||
ASSERT_OK(dbfull()->Put(wo, "barbarbar2", "foo2"));
|
||||
ASSERT_OK(dbfull()->Put(wo, "foofoofoo", "bar"));
|
||||
|
||||
dbfull()->Flush(fo);
|
||||
ASSERT_OK(dbfull()->Flush(fo));
|
||||
|
||||
ASSERT_EQ("foo", Get("barbarbar"));
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_USEFUL), 0);
|
||||
@@ -297,7 +301,7 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
// ranges.
|
||||
ASSERT_OK(dbfull()->Put(wo, "aaa", ""));
|
||||
ASSERT_OK(dbfull()->Put(wo, "zzz", ""));
|
||||
dbfull()->Flush(fo);
|
||||
ASSERT_OK(dbfull()->Flush(fo));
|
||||
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("foo"));
|
||||
@@ -328,7 +332,7 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
// ranges.
|
||||
ASSERT_OK(dbfull()->Put(wo, "aaa", ""));
|
||||
ASSERT_OK(dbfull()->Put(wo, "zzz", ""));
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
// Reopen with both of whole key off and prefix extractor enabled.
|
||||
// Still no bloom filter should be used.
|
||||
@@ -351,7 +355,7 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
// ranges.
|
||||
ASSERT_OK(dbfull()->Put(wo, "aaa", ""));
|
||||
ASSERT_OK(dbfull()->Put(wo, "zzz", ""));
|
||||
db_->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
options.prefix_extractor.reset();
|
||||
bbto.whole_key_filtering = true;
|
||||
@@ -364,7 +368,7 @@ TEST_F(DBBloomFilterTest, WholeKeyFilterProp) {
|
||||
// not filtered out by key ranges.
|
||||
ASSERT_OK(dbfull()->Put(wo, "aaa", ""));
|
||||
ASSERT_OK(dbfull()->Put(wo, "zzz", ""));
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Now we have two files:
|
||||
// File 1: An older file with prefix bloom.
|
||||
@@ -467,7 +471,7 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) {
|
||||
for (int i = 0; i < N; i += 100) {
|
||||
ASSERT_OK(Put(1, Key(i), Key(i)));
|
||||
}
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
// Prevent auto compactions triggered by seeks
|
||||
env_->delay_sstable_sync_.store(true, std::memory_order_release);
|
||||
@@ -503,6 +507,21 @@ TEST_P(DBBloomFilterTestWithParam, BloomFilter) {
|
||||
ASSERT_LE(reads, 3 * N / 100);
|
||||
}
|
||||
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Sanity check some table properties
|
||||
std::map<std::string, std::string> props;
|
||||
ASSERT_TRUE(db_->GetMapProperty(
|
||||
handles_[1], DB::Properties::kAggregatedTableProperties, &props));
|
||||
uint64_t nkeys = N + N / 100;
|
||||
uint64_t filter_size = ParseUint64(props["filter_size"]);
|
||||
EXPECT_LE(filter_size,
|
||||
(partition_filters_ ? 12 : 11) * nkeys / /*bits / byte*/ 8);
|
||||
EXPECT_GE(filter_size, 10 * nkeys / /*bits / byte*/ 8);
|
||||
|
||||
uint64_t num_filter_entries = ParseUint64(props["num_filter_entries"]);
|
||||
EXPECT_EQ(num_filter_entries, nkeys);
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
env_->delay_sstable_sync_.store(false, std::memory_order_release);
|
||||
Close();
|
||||
} while (ChangeCompactOptions());
|
||||
@@ -514,24 +533,24 @@ INSTANTIATE_TEST_CASE_P(
|
||||
::testing::Values(
|
||||
std::make_tuple(BFP::kDeprecatedBlock, false,
|
||||
test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, false, test::kDefaultFormatVersion)));
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kDefaultFormatVersion)));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatDef, DBBloomFilterTestWithParam,
|
||||
::testing::Values(
|
||||
std::make_tuple(BFP::kDeprecatedBlock, false,
|
||||
test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, false, test::kDefaultFormatVersion)));
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kDefaultFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kDefaultFormatVersion)));
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(
|
||||
FormatLatest, DBBloomFilterTestWithParam,
|
||||
::testing::Values(
|
||||
std::make_tuple(BFP::kDeprecatedBlock, false,
|
||||
test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, true, test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAuto, false, test::kLatestFormatVersion)));
|
||||
std::make_tuple(BFP::kAutoBloom, true, test::kLatestFormatVersion),
|
||||
std::make_tuple(BFP::kAutoBloom, false, test::kLatestFormatVersion)));
|
||||
#endif // ROCKSDB_VALGRIND_RUN
|
||||
|
||||
TEST_F(DBBloomFilterTest, BloomFilterRate) {
|
||||
@@ -771,6 +790,14 @@ class LevelAndStyleCustomFilterPolicy : public FilterPolicy {
|
||||
const std::unique_ptr<const FilterPolicy> policy_otherwise_;
|
||||
};
|
||||
|
||||
static std::map<TableFileCreationReason, std::string>
|
||||
table_file_creation_reason_to_string{
|
||||
{TableFileCreationReason::kCompaction, "kCompaction"},
|
||||
{TableFileCreationReason::kFlush, "kFlush"},
|
||||
{TableFileCreationReason::kMisc, "kMisc"},
|
||||
{TableFileCreationReason::kRecovery, "kRecovery"},
|
||||
};
|
||||
|
||||
class TestingContextCustomFilterPolicy
|
||||
: public LevelAndStyleCustomFilterPolicy {
|
||||
public:
|
||||
@@ -783,11 +810,17 @@ class TestingContextCustomFilterPolicy
|
||||
const FilterBuildingContext& context) const override {
|
||||
test_report_ += "cf=";
|
||||
test_report_ += context.column_family_name;
|
||||
test_report_ += ",cs=";
|
||||
test_report_ += ",s=";
|
||||
test_report_ +=
|
||||
OptionsHelper::compaction_style_to_string[context.compaction_style];
|
||||
test_report_ += ",lv=";
|
||||
test_report_ += std::to_string(context.level_at_creation);
|
||||
test_report_ += ",n=";
|
||||
test_report_ += ToString(context.num_levels);
|
||||
test_report_ += ",l=";
|
||||
test_report_ += ToString(context.level_at_creation);
|
||||
test_report_ += ",b=";
|
||||
test_report_ += ToString(int{context.is_bottommost});
|
||||
test_report_ += ",r=";
|
||||
test_report_ += table_file_creation_reason_to_string[context.reason];
|
||||
test_report_ += "\n";
|
||||
|
||||
return LevelAndStyleCustomFilterPolicy::GetBuilderWithContext(context);
|
||||
@@ -805,18 +838,21 @@ class TestingContextCustomFilterPolicy
|
||||
} // namespace
|
||||
|
||||
TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
auto policy = std::make_shared<TestingContextCustomFilterPolicy>(15, 8, 5);
|
||||
Options options;
|
||||
for (bool fifo : {true, false}) {
|
||||
Options options = CurrentOptions();
|
||||
options = CurrentOptions();
|
||||
options.max_open_files = fifo ? -1 : options.max_open_files;
|
||||
options.statistics = ROCKSDB_NAMESPACE::CreateDBStatistics();
|
||||
options.compaction_style =
|
||||
fifo ? kCompactionStyleFIFO : kCompactionStyleLevel;
|
||||
|
||||
BlockBasedTableOptions table_options;
|
||||
auto policy = std::make_shared<TestingContextCustomFilterPolicy>(15, 8, 5);
|
||||
table_options.filter_policy = policy;
|
||||
table_options.format_version = 5;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
|
||||
TryReopen(options);
|
||||
CreateAndReopenWithCF({fifo ? "abe" : "bob"}, options);
|
||||
|
||||
const int maxKey = 10000;
|
||||
@@ -827,16 +863,16 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
ASSERT_OK(Put(1, Key(maxKey + 55555), Key(maxKey + 55555)));
|
||||
Flush(1);
|
||||
EXPECT_EQ(policy->DumpTestReport(),
|
||||
fifo ? "cf=abe,cs=kCompactionStyleFIFO,lv=0\n"
|
||||
: "cf=bob,cs=kCompactionStyleLevel,lv=0\n");
|
||||
fifo ? "cf=abe,s=kCompactionStyleFIFO,n=1,l=0,b=0,r=kFlush\n"
|
||||
: "cf=bob,s=kCompactionStyleLevel,n=7,l=0,b=0,r=kFlush\n");
|
||||
|
||||
for (int i = maxKey / 2; i < maxKey; i++) {
|
||||
ASSERT_OK(Put(1, Key(i), Key(i)));
|
||||
}
|
||||
Flush(1);
|
||||
EXPECT_EQ(policy->DumpTestReport(),
|
||||
fifo ? "cf=abe,cs=kCompactionStyleFIFO,lv=0\n"
|
||||
: "cf=bob,cs=kCompactionStyleLevel,lv=0\n");
|
||||
fifo ? "cf=abe,s=kCompactionStyleFIFO,n=1,l=0,b=0,r=kFlush\n"
|
||||
: "cf=bob,s=kCompactionStyleLevel,n=7,l=0,b=0,r=kFlush\n");
|
||||
|
||||
// Check that they can be found
|
||||
for (int i = 0; i < maxKey; i++) {
|
||||
@@ -864,7 +900,7 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr));
|
||||
EXPECT_EQ(policy->DumpTestReport(),
|
||||
"cf=bob,cs=kCompactionStyleLevel,lv=1\n");
|
||||
"cf=bob,s=kCompactionStyleLevel,n=7,l=1,b=1,r=kCompaction\n");
|
||||
|
||||
// Check that we now have one filter, about 9.2% FP rate (5 bits per key)
|
||||
for (int i = 0; i < maxKey; i++) {
|
||||
@@ -876,11 +912,25 @@ TEST_F(DBBloomFilterTest, ContextCustomFilterPolicy) {
|
||||
EXPECT_GE(useful_count, maxKey * 0.90);
|
||||
EXPECT_LE(useful_count, maxKey * 0.91);
|
||||
}
|
||||
} else {
|
||||
#ifndef ROCKSDB_LITE
|
||||
// Also try external SST file
|
||||
{
|
||||
std::string file_path = dbname_ + "/external.sst";
|
||||
SstFileWriter sst_file_writer(EnvOptions(), options, handles_[1]);
|
||||
ASSERT_OK(sst_file_writer.Open(file_path));
|
||||
ASSERT_OK(sst_file_writer.Put("key", "value"));
|
||||
ASSERT_OK(sst_file_writer.Finish());
|
||||
}
|
||||
// Note: kCompactionStyleLevel is default, ignored if num_levels == -1
|
||||
EXPECT_EQ(policy->DumpTestReport(),
|
||||
"cf=abe,s=kCompactionStyleLevel,n=-1,l=-1,b=0,r=kMisc\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
// Destroy
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
dbfull()->DestroyColumnFamilyHandle(handles_[1]);
|
||||
ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[1]));
|
||||
handles_[1] = nullptr;
|
||||
}
|
||||
}
|
||||
@@ -1444,9 +1494,9 @@ void PrefixScanInit(DBBloomFilterTest* dbtest) {
|
||||
snprintf(buf, sizeof(buf), "%02d______:end", 10);
|
||||
keystr = std::string(buf);
|
||||
ASSERT_OK(dbtest->Put(keystr, keystr));
|
||||
dbtest->Flush();
|
||||
dbtest->dbfull()->CompactRange(CompactRangeOptions(), nullptr,
|
||||
nullptr); // move to level 1
|
||||
ASSERT_OK(dbtest->Flush());
|
||||
ASSERT_OK(dbtest->dbfull()->CompactRange(CompactRangeOptions(), nullptr,
|
||||
nullptr)); // move to level 1
|
||||
|
||||
// GROUP 1
|
||||
for (int i = 1; i <= small_range_sstfiles; i++) {
|
||||
@@ -1563,21 +1613,21 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) {
|
||||
for (int key : keys) {
|
||||
ASSERT_OK(Put(1, Key(key), "val"));
|
||||
if (++num_inserted % 1000 == 0) {
|
||||
dbfull()->TEST_WaitForFlushMemTable();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForFlushMemTable());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
}
|
||||
ASSERT_OK(Put(1, Key(0), "val"));
|
||||
ASSERT_OK(Put(1, Key(numkeys), "val"));
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
if (NumTableFilesAtLevel(0, 1) == 0) {
|
||||
// No Level 0 file. Create one.
|
||||
ASSERT_OK(Put(1, Key(0), "val"));
|
||||
ASSERT_OK(Put(1, Key(numkeys), "val"));
|
||||
ASSERT_OK(Flush(1));
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
}
|
||||
|
||||
for (int i = 1; i < numkeys; i += 2) {
|
||||
@@ -1682,7 +1732,8 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) {
|
||||
BottommostLevelCompaction::kSkip;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = 7;
|
||||
db_->CompactRange(compact_options, handles_[1], nullptr, nullptr);
|
||||
ASSERT_TRUE(db_->CompactRange(compact_options, handles_[1], nullptr, nullptr)
|
||||
.IsNotSupported());
|
||||
|
||||
ASSERT_EQ(trivial_move, 1);
|
||||
ASSERT_EQ(non_trivial_move, 0);
|
||||
@@ -1714,10 +1765,10 @@ TEST_F(DBBloomFilterTest, OptimizeFiltersForHits) {
|
||||
|
||||
int CountIter(std::unique_ptr<Iterator>& iter, const Slice& key) {
|
||||
int count = 0;
|
||||
for (iter->Seek(key); iter->Valid() && iter->status() == Status::OK();
|
||||
iter->Next()) {
|
||||
for (iter->Seek(key); iter->Valid(); iter->Next()) {
|
||||
count++;
|
||||
}
|
||||
EXPECT_OK(iter->status());
|
||||
return count;
|
||||
}
|
||||
|
||||
@@ -1730,6 +1781,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
int using_full_builder = bfp_impl != BFP::kDeprecatedBlock;
|
||||
Options options;
|
||||
options.create_if_missing = true;
|
||||
options.env = CurrentOptions().env;
|
||||
options.prefix_extractor.reset(NewCappedPrefixTransform(4));
|
||||
options.disable_auto_compactions = true;
|
||||
options.statistics = CreateDBStatistics();
|
||||
@@ -1746,7 +1798,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterUpperBound) {
|
||||
ASSERT_OK(Put("abcdxxx1", "val2"));
|
||||
ASSERT_OK(Put("abcdxxx2", "val3"));
|
||||
ASSERT_OK(Put("abcdxxx3", "val4"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
{
|
||||
// prefix_extractor has not changed, BF will always be read
|
||||
Slice upper_bound("abce");
|
||||
@@ -1860,6 +1912,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
for (auto bfp_impl : BFP::kAllFixedImpls) {
|
||||
int using_full_builder = bfp_impl != BFP::kDeprecatedBlock;
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -1903,7 +1956,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_OK(Put("foo4", "bar4"));
|
||||
ASSERT_OK(Put("foq5", "bar5"));
|
||||
ASSERT_OK(Put("fpb", "1"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
{
|
||||
// BF is cappped:3 now
|
||||
std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
|
||||
@@ -1927,7 +1980,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterMultipleSST) {
|
||||
ASSERT_OK(Put("foo7", "bar7"));
|
||||
ASSERT_OK(Put("foq8", "bar8"));
|
||||
ASSERT_OK(Put("fpc", "2"));
|
||||
dbfull()->Flush(FlushOptions());
|
||||
ASSERT_OK(dbfull()->Flush(FlushOptions()));
|
||||
{
|
||||
// BF is fixed:2 now
|
||||
std::unique_ptr<Iterator> iter_tmp(db_->NewIterator(read_options));
|
||||
@@ -2038,10 +2091,10 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
ASSERT_EQ(TestGetTickerCount(options, BLOOM_FILTER_PREFIX_USEFUL), 0);
|
||||
}
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[2]));
|
||||
dbfull()->DestroyColumnFamilyHandle(handles_[2]);
|
||||
ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[2]));
|
||||
handles_[2] = nullptr;
|
||||
ASSERT_OK(dbfull()->DropColumnFamily(handles_[1]));
|
||||
dbfull()->DestroyColumnFamilyHandle(handles_[1]);
|
||||
ASSERT_OK(dbfull()->DestroyColumnFamilyHandle(handles_[1]));
|
||||
handles_[1] = nullptr;
|
||||
iteration++;
|
||||
}
|
||||
@@ -2052,6 +2105,7 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterNewColumnFamily) {
|
||||
TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
for (auto bfp_impl : BFP::kAllFixedImpls) {
|
||||
Options options;
|
||||
options.env = CurrentOptions().env;
|
||||
options.create_if_missing = true;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(1));
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -2113,6 +2167,54 @@ TEST_F(DBBloomFilterTest, DynamicBloomFilterOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
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.create_if_missing = true;
|
||||
constexpr size_t kPrefixLength = 4;
|
||||
options.prefix_extractor.reset(NewFixedPrefixTransform(kPrefixLength));
|
||||
options.compression = kNoCompression;
|
||||
BlockBasedTableOptions bbto;
|
||||
bbto.filter_policy.reset(NewBloomFilterPolicy(50));
|
||||
bbto.index_shortening =
|
||||
BlockBasedTableOptions::IndexShorteningMode::kNoShortening;
|
||||
bbto.block_size = 128;
|
||||
bbto.metadata_block_size = 128;
|
||||
bbto.partition_filters = true;
|
||||
bbto.index_type = BlockBasedTableOptions::IndexType::kTwoLevelIndexSearch;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(bbto));
|
||||
DestroyAndReopen(options);
|
||||
|
||||
const std::string value(64, '\0');
|
||||
|
||||
WriteOptions write_opts;
|
||||
write_opts.disableWAL = true;
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
std::ostringstream oss;
|
||||
oss << std::setfill('0') << std::setw(4) << std::fixed << i;
|
||||
ASSERT_OK(db_->Put(write_opts, oss.str(), value));
|
||||
}
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
ReadOptions read_opts;
|
||||
// Use legacy, implicit prefix seek
|
||||
read_opts.total_order_seek = false;
|
||||
read_opts.auto_prefix_mode = false;
|
||||
std::unique_ptr<Iterator> it(db_->NewIterator(read_opts));
|
||||
for (size_t i = 0; i < kNumKeys; ++i) {
|
||||
// Seek with a key after each one added but with same prefix. One will
|
||||
// surely cross a partition boundary.
|
||||
std::ostringstream oss;
|
||||
oss << std::setfill('0') << std::setw(4) << std::fixed << i << "a";
|
||||
it->SeekForPrev(oss.str());
|
||||
ASSERT_OK(it->status());
|
||||
ASSERT_TRUE(it->Valid());
|
||||
}
|
||||
it.reset();
|
||||
}
|
||||
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
+185
-54
@@ -42,7 +42,7 @@ class DBTestCompactionFilterWithCompactParam
|
||||
option_config_ == kUniversalSubcompactions) {
|
||||
assert(options.max_subcompactions > 1);
|
||||
}
|
||||
TryReopen(options);
|
||||
Reopen(options);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -82,6 +82,11 @@ class DeleteFilter : public CompactionFilter {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FilterMergeOperand(int /*level*/, const Slice& /*key*/,
|
||||
const Slice& /*operand*/) const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "DeleteFilter"; }
|
||||
};
|
||||
|
||||
@@ -190,18 +195,36 @@ class KeepFilterFactory : public CompactionFilterFactory {
|
||||
bool compaction_filter_created_;
|
||||
};
|
||||
|
||||
// This filter factory is configured with a `TableFileCreationReason`. Only
|
||||
// table files created for that reason will undergo filtering. This
|
||||
// configurability makes it useful to tests for filtering non-compaction table
|
||||
// files, such as "CompactionFilterFlush" and "CompactionFilterRecovery".
|
||||
class DeleteFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit DeleteFilterFactory(TableFileCreationReason reason)
|
||||
: reason_(reason) {}
|
||||
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& context) override {
|
||||
if (context.is_manual_compaction) {
|
||||
return std::unique_ptr<CompactionFilter>(new DeleteFilter());
|
||||
} else {
|
||||
EXPECT_EQ(reason_, context.reason);
|
||||
if (context.reason == TableFileCreationReason::kCompaction &&
|
||||
!context.is_manual_compaction) {
|
||||
// Table files created by automatic compaction do not undergo filtering.
|
||||
// Presumably some tests rely on this.
|
||||
return std::unique_ptr<CompactionFilter>(nullptr);
|
||||
}
|
||||
return std::unique_ptr<CompactionFilter>(new DeleteFilter());
|
||||
}
|
||||
|
||||
bool ShouldFilterTableFileCreation(
|
||||
TableFileCreationReason reason) const override {
|
||||
return reason_ == reason;
|
||||
}
|
||||
|
||||
const char* Name() const override { return "DeleteFilterFactory"; }
|
||||
|
||||
private:
|
||||
const TableFileCreationReason reason_;
|
||||
};
|
||||
|
||||
// Delete Filter Factory which ignores snapshots
|
||||
@@ -276,7 +299,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
for (int i = 0; i < 100000; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
ASSERT_OK(Flush(1));
|
||||
|
||||
@@ -284,10 +307,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
// the compaction is each level invokes the filter for
|
||||
// all the keys in that level.
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]));
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]));
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
@@ -314,13 +337,14 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */));
|
||||
total++;
|
||||
if (ikey.sequence != 0) {
|
||||
count++;
|
||||
}
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
}
|
||||
ASSERT_EQ(total, 100000);
|
||||
ASSERT_EQ(count, 0);
|
||||
@@ -337,10 +361,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
// means that all keys should pass at least once
|
||||
// via the compaction filter
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]));
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]));
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
||||
@@ -348,7 +372,8 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
|
||||
// create a new database with the compaction
|
||||
// filter in such a way that it deletes all keys
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>(
|
||||
TableFileCreationReason::kCompaction);
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
CreateAndReopenWithCF({"pikachu"}, options);
|
||||
@@ -369,10 +394,10 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
// verify that at the end of the compaction process,
|
||||
// nothing is left.
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]));
|
||||
ASSERT_EQ(cfilter_count, 100000);
|
||||
cfilter_count = 0;
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]));
|
||||
ASSERT_EQ(cfilter_count, 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(0, 1), 0);
|
||||
ASSERT_EQ(NumTableFilesAtLevel(1, 1), 0);
|
||||
@@ -387,6 +412,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
count++;
|
||||
iter->Next();
|
||||
}
|
||||
ASSERT_OK(iter->status());
|
||||
ASSERT_EQ(count, 0);
|
||||
}
|
||||
|
||||
@@ -405,7 +431,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */));
|
||||
ASSERT_NE(ikey.sequence, (unsigned)0);
|
||||
count++;
|
||||
iter->Next();
|
||||
@@ -419,7 +445,8 @@ TEST_F(DBTestCompactionFilter, CompactionFilter) {
|
||||
// entries in VersionEdit, but none of the 'AddFile's.
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>();
|
||||
options.compaction_filter_factory = std::make_shared<DeleteFilterFactory>(
|
||||
TableFileCreationReason::kCompaction);
|
||||
options.disable_auto_compactions = true;
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
@@ -427,9 +454,9 @@ TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) {
|
||||
// put some data
|
||||
for (int table = 0; table < 4; ++table) {
|
||||
for (int i = 0; i < 10 + table; ++i) {
|
||||
Put(ToString(table * 100 + i), "val");
|
||||
ASSERT_OK(Put(ToString(table * 100 + i), "val"));
|
||||
}
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
// this will produce empty file (delete compaction filter)
|
||||
@@ -440,6 +467,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) {
|
||||
|
||||
Iterator* itr = db_->NewIterator(ReadOptions());
|
||||
itr->SeekToFirst();
|
||||
ASSERT_OK(itr->status());
|
||||
// empty db
|
||||
ASSERT_TRUE(!itr->Valid());
|
||||
|
||||
@@ -447,6 +475,64 @@ TEST_F(DBTestCompactionFilter, CompactionFilterDeletesAll) {
|
||||
}
|
||||
#endif // ROCKSDB_LITE
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterFlush) {
|
||||
// Tests a `CompactionFilterFactory` that filters when table file is created
|
||||
// by flush.
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<DeleteFilterFactory>(TableFileCreationReason::kFlush);
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
Reopen(options);
|
||||
|
||||
// Puts and Merges are purged in flush.
|
||||
ASSERT_OK(Put("a", "v"));
|
||||
ASSERT_OK(Merge("b", "v"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("b"));
|
||||
|
||||
// However, Puts and Merges are preserved by recovery.
|
||||
ASSERT_OK(Put("a", "v"));
|
||||
ASSERT_OK(Merge("b", "v"));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("v", Get("a"));
|
||||
ASSERT_EQ("v", Get("b"));
|
||||
|
||||
// Likewise, compaction does not apply filtering.
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("v", Get("a"));
|
||||
ASSERT_EQ("v", Get("b"));
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, CompactionFilterRecovery) {
|
||||
// Tests a `CompactionFilterFactory` that filters when table file is created
|
||||
// by recovery.
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<DeleteFilterFactory>(TableFileCreationReason::kRecovery);
|
||||
options.merge_operator = MergeOperators::CreateStringAppendOperator();
|
||||
Reopen(options);
|
||||
|
||||
// Puts and Merges are purged in recovery.
|
||||
ASSERT_OK(Put("a", "v"));
|
||||
ASSERT_OK(Merge("b", "v"));
|
||||
Reopen(options);
|
||||
ASSERT_EQ("NOT_FOUND", Get("a"));
|
||||
ASSERT_EQ("NOT_FOUND", Get("b"));
|
||||
|
||||
// However, Puts and Merges are preserved by flush.
|
||||
ASSERT_OK(Put("a", "v"));
|
||||
ASSERT_OK(Merge("b", "v"));
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_EQ("v", Get("a"));
|
||||
ASSERT_EQ("v", Get("b"));
|
||||
|
||||
// Likewise, compaction does not apply filtering.
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ("v", Get("a"));
|
||||
ASSERT_EQ("v", Get("b"));
|
||||
}
|
||||
|
||||
TEST_P(DBTestCompactionFilterWithCompactParam,
|
||||
CompactionFilterWithValueChange) {
|
||||
Options options = CurrentOptions();
|
||||
@@ -463,25 +549,25 @@ TEST_P(DBTestCompactionFilterWithCompactParam,
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
|
||||
// push all files to lower levels
|
||||
ASSERT_OK(Flush(1));
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]));
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1],
|
||||
nullptr, nullptr));
|
||||
}
|
||||
|
||||
// re-write all data again
|
||||
for (int i = 0; i < 100001; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%010d", i);
|
||||
Put(1, key, value);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
|
||||
// push all files to lower levels. This should
|
||||
@@ -489,11 +575,11 @@ TEST_P(DBTestCompactionFilterWithCompactParam,
|
||||
ASSERT_OK(Flush(1));
|
||||
if (option_config_ != kUniversalCompactionMultiLevel &&
|
||||
option_config_ != kUniversalSubcompactions) {
|
||||
dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]);
|
||||
dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]);
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(0, nullptr, nullptr, handles_[1]));
|
||||
ASSERT_OK(dbfull()->TEST_CompactRange(1, nullptr, nullptr, handles_[1]));
|
||||
} else {
|
||||
dbfull()->CompactRange(CompactRangeOptions(), handles_[1], nullptr,
|
||||
nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), handles_[1],
|
||||
nullptr, nullptr));
|
||||
}
|
||||
|
||||
// verify that all keys now have the new value that
|
||||
@@ -531,7 +617,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
|
||||
ASSERT_OK(Flush());
|
||||
std::string newvalue = Get("foo");
|
||||
ASSERT_EQ(newvalue, three);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
newvalue = Get("foo");
|
||||
ASSERT_EQ(newvalue, three);
|
||||
|
||||
@@ -539,12 +625,12 @@ TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
|
||||
// merge keys.
|
||||
ASSERT_OK(db_->Put(WriteOptions(), "bar", two));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
newvalue = Get("bar");
|
||||
ASSERT_EQ("NOT_FOUND", newvalue);
|
||||
ASSERT_OK(db_->Merge(WriteOptions(), "bar", two));
|
||||
ASSERT_OK(Flush());
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
newvalue = Get("bar");
|
||||
ASSERT_EQ(two, two);
|
||||
|
||||
@@ -555,7 +641,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
|
||||
ASSERT_OK(Flush());
|
||||
newvalue = Get("foobar");
|
||||
ASSERT_EQ(newvalue, three);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
newvalue = Get("foobar");
|
||||
ASSERT_EQ(newvalue, three);
|
||||
|
||||
@@ -568,7 +654,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterWithMergeOperator) {
|
||||
ASSERT_OK(Flush());
|
||||
newvalue = Get("barfoo");
|
||||
ASSERT_EQ(newvalue, four);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
newvalue = Get("barfoo");
|
||||
ASSERT_EQ(newvalue, four);
|
||||
}
|
||||
@@ -590,21 +676,21 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
for (int i = 0; i < num_keys_per_file; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%08d%02d", i, j);
|
||||
Put(key, value);
|
||||
ASSERT_OK(Put(key, value));
|
||||
}
|
||||
dbfull()->TEST_FlushMemTable();
|
||||
ASSERT_OK(dbfull()->TEST_FlushMemTable());
|
||||
// Make sure next file is much smaller so automatic compaction will not
|
||||
// be triggered.
|
||||
num_keys_per_file /= 2;
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
// Force a manual compaction
|
||||
cfilter_count = 0;
|
||||
filter->expect_manual_compaction_.store(true);
|
||||
filter->expect_full_compaction_.store(true);
|
||||
filter->expect_cf_id_.store(0);
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(cfilter_count, 700);
|
||||
ASSERT_EQ(NumSortedRuns(0), 1);
|
||||
ASSERT_TRUE(filter->compaction_filter_created());
|
||||
@@ -624,7 +710,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextManual) {
|
||||
ASSERT_OK(iter->status());
|
||||
while (iter->Valid()) {
|
||||
ParsedInternalKey ikey(Slice(), 0, kTypeValue);
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey));
|
||||
ASSERT_OK(ParseInternalKey(iter->key(), &ikey, true /* log_err_key */));
|
||||
total++;
|
||||
if (ikey.sequence != 0) {
|
||||
count++;
|
||||
@@ -654,14 +740,14 @@ TEST_F(DBTestCompactionFilter, CompactionFilterContextCfId) {
|
||||
for (int i = 0; i < num_keys_per_file; i++) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "B%08d%02d", i, j);
|
||||
Put(1, key, value);
|
||||
ASSERT_OK(Put(1, key, value));
|
||||
}
|
||||
Flush(1);
|
||||
ASSERT_OK(Flush(1));
|
||||
// Make sure next file is much smaller so automatic compaction will not
|
||||
// be triggered.
|
||||
num_keys_per_file /= 2;
|
||||
}
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
ASSERT_TRUE(filter->compaction_filter_created());
|
||||
}
|
||||
@@ -680,9 +766,9 @@ TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
const Snapshot* snapshot = nullptr;
|
||||
for (int table = 0; table < 4; ++table) {
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Put(ToString(table * 100 + i), "val");
|
||||
ASSERT_OK(Put(ToString(table * 100 + i), "val"));
|
||||
}
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
if (table == 0) {
|
||||
snapshot = db_->GetSnapshot();
|
||||
@@ -702,6 +788,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
read_options.snapshot = snapshot;
|
||||
std::unique_ptr<Iterator> iter(db_->NewIterator(read_options));
|
||||
iter->SeekToFirst();
|
||||
ASSERT_OK(iter->status());
|
||||
int count = 0;
|
||||
while (iter->Valid()) {
|
||||
count++;
|
||||
@@ -710,6 +797,7 @@ TEST_F(DBTestCompactionFilter, CompactionFilterIgnoreSnapshot) {
|
||||
ASSERT_EQ(count, 6);
|
||||
read_options.snapshot = nullptr;
|
||||
std::unique_ptr<Iterator> iter1(db_->NewIterator(read_options));
|
||||
ASSERT_OK(iter1->status());
|
||||
iter1->SeekToFirst();
|
||||
count = 0;
|
||||
while (iter1->Valid()) {
|
||||
@@ -740,9 +828,9 @@ TEST_F(DBTestCompactionFilter, SkipUntil) {
|
||||
for (int i = table * 6; i < 39 + table * 11; ++i) {
|
||||
char key[100];
|
||||
snprintf(key, sizeof(key), "%010d", table * 100 + i);
|
||||
Put(key, std::to_string(table * 1000 + i));
|
||||
ASSERT_OK(Put(key, std::to_string(table * 1000 + i)));
|
||||
}
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
cfilter_skips = 0;
|
||||
@@ -781,10 +869,10 @@ TEST_F(DBTestCompactionFilter, SkipUntilWithBloomFilter) {
|
||||
options.create_if_missing = true;
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put("0000000010", "v10");
|
||||
Put("0000000020", "v20"); // skipped
|
||||
Put("0000000050", "v50");
|
||||
Flush();
|
||||
ASSERT_OK(Put("0000000010", "v10"));
|
||||
ASSERT_OK(Put("0000000020", "v20")); // skipped
|
||||
ASSERT_OK(Put("0000000050", "v50"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
cfilter_skips = 0;
|
||||
EXPECT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
@@ -822,13 +910,13 @@ TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalse) {
|
||||
options.compaction_filter = new TestNotSupportedFilter();
|
||||
DestroyAndReopen(options);
|
||||
|
||||
Put("a", "v10");
|
||||
Put("z", "v20");
|
||||
Flush();
|
||||
ASSERT_OK(Put("a", "v10"));
|
||||
ASSERT_OK(Put("z", "v20"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
Put("a", "v10");
|
||||
Put("z", "v20");
|
||||
Flush();
|
||||
ASSERT_OK(Put("a", "v10"));
|
||||
ASSERT_OK(Put("z", "v20"));
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
// Comapction should fail because IgnoreSnapshots() = false
|
||||
EXPECT_TRUE(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr)
|
||||
@@ -837,6 +925,49 @@ TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalse) {
|
||||
delete options.compaction_filter;
|
||||
}
|
||||
|
||||
class TestNotSupportedFilterFactory : public CompactionFilterFactory {
|
||||
public:
|
||||
explicit TestNotSupportedFilterFactory(TableFileCreationReason reason)
|
||||
: reason_(reason) {}
|
||||
|
||||
bool ShouldFilterTableFileCreation(
|
||||
TableFileCreationReason reason) const override {
|
||||
return reason_ == reason;
|
||||
}
|
||||
|
||||
std::unique_ptr<CompactionFilter> CreateCompactionFilter(
|
||||
const CompactionFilter::Context& /* context */) override {
|
||||
return std::unique_ptr<CompactionFilter>(new TestNotSupportedFilter());
|
||||
}
|
||||
|
||||
const char* Name() const override { return "TestNotSupportedFilterFactory"; }
|
||||
|
||||
private:
|
||||
const TableFileCreationReason reason_;
|
||||
};
|
||||
|
||||
TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalseDuringFlush) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<TestNotSupportedFilterFactory>(
|
||||
TableFileCreationReason::kFlush);
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("a", "v10"));
|
||||
ASSERT_TRUE(Flush().IsNotSupported());
|
||||
}
|
||||
|
||||
TEST_F(DBTestCompactionFilter, IgnoreSnapshotsFalseRecovery) {
|
||||
Options options = CurrentOptions();
|
||||
options.compaction_filter_factory =
|
||||
std::make_shared<TestNotSupportedFilterFactory>(
|
||||
TableFileCreationReason::kRecovery);
|
||||
Reopen(options);
|
||||
|
||||
ASSERT_OK(Put("a", "v10"));
|
||||
ASSERT_TRUE(TryReopen(options).IsNotSupported());
|
||||
}
|
||||
|
||||
} // namespace ROCKSDB_NAMESPACE
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
+1216
-315
File diff suppressed because it is too large
Load Diff
+23
-20
@@ -13,6 +13,7 @@
|
||||
#if !defined(ROCKSDB_LITE)
|
||||
|
||||
#include "db/db_test_util.h"
|
||||
#include "env/mock_env.h"
|
||||
#include "port/port.h"
|
||||
#include "port/stack_trace.h"
|
||||
#include "util/random.h"
|
||||
@@ -102,7 +103,8 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase) {
|
||||
}
|
||||
|
||||
// Test compact range works
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
// All data should be in the last level.
|
||||
ColumnFamilyMetaData cf_meta;
|
||||
db_->GetColumnFamilyMetaData(&cf_meta);
|
||||
@@ -141,6 +143,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
options.max_background_compactions = 2;
|
||||
options.num_levels = 5;
|
||||
options.max_compaction_bytes = 0; // Force not expanding in compactions
|
||||
options.db_host_id = ""; // Setting this messes up the file size calculation
|
||||
BlockBasedTableOptions table_options;
|
||||
table_options.block_size = 1024;
|
||||
options.table_factory.reset(NewBlockBasedTableFactory(table_options));
|
||||
@@ -165,8 +168,8 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
}));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(4U, int_prop);
|
||||
|
||||
@@ -183,8 +186,8 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
}));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(3U, int_prop);
|
||||
ASSERT_TRUE(db_->GetProperty("rocksdb.num-files-at-level1", &str_prop));
|
||||
@@ -204,8 +207,8 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
ASSERT_OK(dbfull()->SetOptions({
|
||||
{"disable_auto_compactions", "false"},
|
||||
}));
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(3U, int_prop);
|
||||
|
||||
@@ -233,8 +236,8 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
}));
|
||||
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:0");
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
ASSERT_EQ(2U, int_prop);
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
@@ -263,7 +266,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBase2) {
|
||||
}
|
||||
TEST_SYNC_POINT("DynamicLevelMaxBytesBase2:2");
|
||||
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
|
||||
thread.join();
|
||||
|
||||
@@ -301,7 +304,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesCompactRange) {
|
||||
DestroyAndReopen(options);
|
||||
|
||||
// Compact against empty DB
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
|
||||
uint64_t int_prop;
|
||||
std::string str_prop;
|
||||
@@ -315,13 +318,13 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesCompactRange) {
|
||||
ASSERT_OK(
|
||||
Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))), rnd.RandomString(80)));
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
if (NumTableFilesAtLevel(0) == 0) {
|
||||
// Make sure level 0 is not empty
|
||||
ASSERT_OK(
|
||||
Put(Key(static_cast<int>(rnd.Uniform(kMaxKey))), rnd.RandomString(80)));
|
||||
Flush();
|
||||
ASSERT_OK(Flush());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(db_->GetIntProperty("rocksdb.base-level", &int_prop));
|
||||
@@ -342,7 +345,7 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesCompactRange) {
|
||||
});
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->EnableProcessing();
|
||||
|
||||
dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(CompactRangeOptions(), nullptr, nullptr));
|
||||
ASSERT_EQ(output_levels.size(), 2);
|
||||
ASSERT_TRUE(output_levels.find(3) != output_levels.end());
|
||||
ASSERT_TRUE(output_levels.find(4) != output_levels.end());
|
||||
@@ -388,8 +391,8 @@ TEST_F(DBTestDynamicLevel, DynamicLevelMaxBytesBaseInc) {
|
||||
PutFixed32(&value, static_cast<uint32_t>(i));
|
||||
ASSERT_OK(Put(Key(i), value));
|
||||
}
|
||||
Flush();
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(Flush());
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->DisableProcessing();
|
||||
|
||||
ASSERT_EQ(non_trivial, 0);
|
||||
@@ -448,7 +451,7 @@ TEST_F(DBTestDynamicLevel, DISABLED_MigrateToDynamicLevelMaxBytesBase) {
|
||||
ASSERT_OK(Delete(Key(i / 10)));
|
||||
}
|
||||
verify_func(total_keys, false);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
|
||||
options.level_compaction_dynamic_level_bytes = true;
|
||||
options.disable_auto_compactions = true;
|
||||
@@ -463,7 +466,7 @@ TEST_F(DBTestDynamicLevel, DISABLED_MigrateToDynamicLevelMaxBytesBase) {
|
||||
CompactRangeOptions compact_options;
|
||||
compact_options.change_level = true;
|
||||
compact_options.target_level = options.num_levels - 1;
|
||||
dbfull()->CompactRange(compact_options, nullptr, nullptr);
|
||||
ASSERT_OK(dbfull()->CompactRange(compact_options, nullptr, nullptr));
|
||||
compaction_finished.store(true);
|
||||
});
|
||||
do {
|
||||
@@ -483,7 +486,7 @@ TEST_F(DBTestDynamicLevel, DISABLED_MigrateToDynamicLevelMaxBytesBase) {
|
||||
}
|
||||
|
||||
verify_func(total_keys2, false);
|
||||
dbfull()->TEST_WaitForCompact();
|
||||
ASSERT_OK(dbfull()->TEST_WaitForCompact());
|
||||
verify_func(total_keys2, false);
|
||||
|
||||
// Base level is not level 1
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user