diff options
author | jiangph <jiangph@cn.ibm.com> | 2018-09-20 21:01:26 +0800 |
---|---|---|
committer | jiangph <jiangph@cn.ibm.com> | 2018-09-21 18:00:43 +0800 |
commit | 542a37011513966e3ed182410e9f6fb5896708c0 (patch) | |
tree | aa507b9241638c7861e1e5db129c5eb587f2d046 | |
parent | 28ba48da5877a5d471253c1b587af7e3b3121fd9 (diff) | |
download | couchdb-COUCHDB-3226-downgrade-clustered-purge.tar.gz |
Add downgrade function to downgrade database with clustered purgeCOUCHDB-3226-downgrade-clustered-purge
- provide downgrade functionality in case there is need to downgrade
database which was modified by builds with clustered purge feature.
After database is modified by builds with clustered purge feature,
the disk version is bumped to 7. Also, there are two newly introduced
trees: PurgeTreeState and PurgeSeqTreeState.
Once downgrade function is called, the disk version is back to the
latest version of current build. Also, purge_seq and purged_docs
are re-introduced again.
COUCHDB-3226
Add test
-rw-r--r-- | src/couch/src/couch_bt_engine.erl | 96 | ||||
-rw-r--r-- | src/couch/src/couch_bt_engine_header.erl | 5 | ||||
-rw-r--r-- | src/couch/test/couch_bt_engine_downgrade_tests.erl | 165 | ||||
-rw-r--r-- | src/couch/test/fixtures/db_with_v6_and_0_purge_req.couch | bin | 0 -> 61644 bytes | |||
-rw-r--r-- | src/couch/test/fixtures/db_with_v7_and_0_purge_req.couch | bin | 0 -> 41156 bytes | |||
-rw-r--r-- | src/couch/test/fixtures/db_with_v7_and_1_purge_req.couch | bin | 0 -> 45262 bytes | |||
-rw-r--r-- | src/couch/test/fixtures/db_with_v7_and_2_purge_req.couch | bin | 0 -> 49352 bytes |
7 files changed, 262 insertions, 4 deletions
diff --git a/src/couch/src/couch_bt_engine.erl b/src/couch/src/couch_bt_engine.erl index ee0d6d864..2899e4556 100644 --- a/src/couch/src/couch_bt_engine.erl +++ b/src/couch/src/couch_bt_engine.erl @@ -85,7 +85,11 @@ seq_tree_reduce/2, local_tree_split/1, - local_tree_join/2 + local_tree_join/2, + + purge_tree_reduce/2, + purge_seq_tree_split/1, + purge_seq_tree_join/2 ]). @@ -101,6 +105,9 @@ -include("couch_bt_engine.hrl"). +-define(CLUSTERED_PURGE_DISK_VERSION, 7). + + exists(FilePath) -> case filelib:is_file(FilePath) of true -> @@ -627,6 +634,21 @@ local_tree_join(Id, {Rev, BodyData}) when is_integer(Rev) -> }. +purge_seq_tree_split({PurgeSeq, UUID, DocId, Revs}) -> + {PurgeSeq, {UUID, DocId, Revs}}. + + +purge_seq_tree_join(PurgeSeq, {UUID, DocId, Revs}) -> + {PurgeSeq, UUID, DocId, Revs}. + + +purge_tree_reduce(reduce, IdRevs) -> + % count the number of purge requests + length(IdRevs); +purge_tree_reduce(rereduce, Reds) -> + lists:sum(Reds). + + set_update_seq(#st{header = Header} = St, UpdateSeq) -> {ok, St#st{ header = couch_bt_engine_header:set(Header, [ @@ -681,8 +703,17 @@ init_state(FilePath, Fd, Header0, Options) -> Compression = couch_compress:get_compression_method(), - Header1 = couch_bt_engine_header:upgrade(Header0), - Header = set_default_security_object(Fd, Header1, Compression, Options), + DiskVersion = couch_bt_engine_header:disk_version(Header0), + Latest = couch_bt_engine_header:latest_disk_version(), + Header1 = case DiskVersion of + N when N =< Latest -> + Header0; + ?CLUSTERED_PURGE_DISK_VERSION -> + downgrade_purge_info(Fd, Header0) + end, + + Header2 = couch_bt_engine_header:upgrade(Header1), + Header = set_default_security_object(Fd, Header2, Compression, Options), IdTreeState = couch_bt_engine_header:id_tree_state(Header), {ok, IdTree} = couch_btree:open(IdTreeState, Fd, [ @@ -727,7 +758,7 @@ init_state(FilePath, Fd, Header0, Options) -> % to be written to disk. case Header /= Header0 of true -> - {ok, NewSt} = commit_data(St), + {ok, NewSt} = commit_data(St#st{needs_commit = true}), NewSt; false -> St @@ -763,6 +794,63 @@ set_default_security_object(Fd, Header, Compression, Options) -> end. +% Rollback for clustered purge. +downgrade_purge_info(Fd, Header) -> + { + db_header, + _DiskVer, + UpSeq, + _Unused, + IdTreeState, + SeqTreeState, + LocalTreeState, + PurgeTreeState, + PurgeSeqTreeState, + SecurityPtr, + RevsLimit, + Uuid, + Epochs, + CompactedSeq, + _PDocsLimit + } = Header, + + {PSeq, PurgedDocsPtr} = case PurgeTreeState of + nil -> + {0, nil}; + PurgeSeqInOldVer when is_integer(PurgeSeqInOldVer)-> + {PurgeSeqInOldVer, nil}; + _ when is_tuple(PurgeTreeState) -> + {ok, PSTree} = couch_btree:open(PurgeSeqTreeState, Fd, [ + {split, fun ?MODULE:purge_seq_tree_split/1}, + {join, fun ?MODULE:purge_seq_tree_join/2}, + {reduce, fun ?MODULE:purge_tree_reduce/2} + ]), + Fun = fun({PurgeSeq, _, _, _}, _Reds, _Acc) -> + {stop, PurgeSeq} + end, + {ok, _, PurgeSeq} = couch_btree:fold(PSTree, Fun, 0, [{dir, rev}]), + Compression = couch_compress:get_compression_method(), + AppendOps = [{compression, Compression}], + {ok, Ptr, _} = couch_file:append_term(Fd, [], AppendOps), + if PurgeSeq > 0 -> {PurgeSeq + 2, Ptr}; true -> {0, Ptr} end + end, + + NewHeader = couch_bt_engine_header:new(), + couch_bt_engine_header:set(NewHeader, [ + {update_seq, UpSeq}, + {id_tree_state, IdTreeState}, + {seq_tree_state, SeqTreeState}, + {local_tree_state, LocalTreeState}, + {purge_seq, PSeq}, + {purged_docs, PurgedDocsPtr}, + {security_ptr, SecurityPtr}, + {revs_limit, RevsLimit}, + {uuid, Uuid}, + {epochs, Epochs}, + {compacted_seq, CompactedSeq} + ]). + + delete_compaction_files(FilePath) -> RootDir = config:get("couchdb", "database_dir", "."), DelOpts = [{context, compaction}], diff --git a/src/couch/src/couch_bt_engine_header.erl b/src/couch/src/couch_bt_engine_header.erl index 3d24f3189..2eaa6ff8b 100644 --- a/src/couch/src/couch_bt_engine_header.erl +++ b/src/couch/src/couch_bt_engine_header.erl @@ -26,6 +26,7 @@ -export([ disk_version/1, + latest_disk_version/0, update_seq/1, id_tree_state/1, seq_tree_state/1, @@ -134,6 +135,10 @@ disk_version(Header) -> get_field(Header, disk_version). +latest_disk_version() -> + ?LATEST_DISK_VERSION. + + update_seq(Header) -> get_field(Header, update_seq). diff --git a/src/couch/test/couch_bt_engine_downgrade_tests.erl b/src/couch/test/couch_bt_engine_downgrade_tests.erl new file mode 100644 index 000000000..b080de351 --- /dev/null +++ b/src/couch/test/couch_bt_engine_downgrade_tests.erl @@ -0,0 +1,165 @@ +% Licensed under the Apache License, Version 2.0 (the "License"); you may not +% use this file except in compliance with the License. You may obtain a copy of +% the License at +% +% http://www.apache.org/licenses/LICENSE-2.0 +% +% Unless required by applicable law or agreed to in writing, software +% distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +% WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +% License for the specific language governing permissions and limitations under +% the License. + +-module(couch_bt_engine_downgrade_tests). + +-include_lib("couch/include/couch_eunit.hrl"). +-include_lib("couch/include/couch_db.hrl"). + + +setup() -> + Ctx = test_util:start_couch(), + DbDir = config:get("couchdb", "database_dir"), + DbFileNames = [ + "db_with_v6_and_0_purge_req.couch", + "db_with_v7_and_0_purge_req.couch", + "db_with_v7_and_1_purge_req.couch", + "db_with_v7_and_2_purge_req.couch" + ], + NewPaths = lists:map(fun(DbFileName) -> + OldDbFilePath = filename:join([?FIXTURESDIR, DbFileName]), + NewDbFilePath = filename:join([DbDir, DbFileName]), + ok = filelib:ensure_dir(NewDbFilePath), + file:delete(NewDbFilePath), + {ok, _} = file:copy(OldDbFilePath, NewDbFilePath), + NewDbFilePath + end, DbFileNames), + {Ctx, NewPaths}. + + +teardown({Ctx, Paths}) -> + test_util:stop_couch(Ctx), + lists:foreach(fun(Path) -> + file:delete(Path) + end, Paths). + + +downgrade_test_() -> + { + "Couch Bt Engine downgrade tests", + { + setup, + fun setup/0, + fun teardown/1, + [ + t_no_downgrade_without_purge_req(), + t_downgrade_without_purge_req(), + t_downgrade_with_1_purge_req(), + t_downgrade_with_2_purge_req() + ] + } + }. + + +t_no_downgrade_without_purge_req() -> + ?_test(begin + % There are three documents in the fixture + % db with zero purge entries + DbName = <<"db_with_v6_and_0_purge_req">>, + ?assertEqual(6, get_disk_version_from_header(DbName)), + {ok, PurgeSeq} = couch_util:with_db(DbName, fun(Db) -> + couch_db:get_purge_seq(Db) + end), + ?assertEqual(0, PurgeSeq), + + {ok, _} = save_doc(DbName, {[{<<"_id">>, <<"doc4">>}, {<<"v">>, 1}]}), + {ok, _} = save_doc(DbName, {[{<<"_id">>, <<"doc5">>}, {<<"v">>, 2}]}), + + couch_util:with_db(DbName, fun(Db) -> + ?assertEqual({ok, 5}, couch_db:get_doc_count(Db)), + ?assertEqual({ok, 0}, couch_db:get_purge_seq(Db)), + ?assertEqual(6, couch_db_engine:get_disk_version(Db)) + end) + + end). + +t_downgrade_without_purge_req() -> + ?_test(begin + % There are three documents in the fixture + % db with zero purge entries + DbName = <<"db_with_v7_and_0_purge_req">>, + ?assertEqual(7, get_disk_version_from_header(DbName)), + {ok, PurgeSeq} = couch_util:with_db(DbName, fun(Db) -> + couch_db:get_purge_seq(Db) + end), + ?assertEqual(0, PurgeSeq), + + {ok, _} = save_doc(DbName, {[{<<"_id">>, <<"doc4">>}, {<<"v">>, 1}]}), + {ok, _} = save_doc(DbName, {[{<<"_id">>, <<"doc5">>}, {<<"v">>, 2}]}), + + couch_util:with_db(DbName, fun(Db) -> + ?assertEqual({ok, 5}, couch_db:get_doc_count(Db)), + ?assertEqual({ok, 0}, couch_db:get_purge_seq(Db)), + ?assertEqual(6, couch_db_engine:get_disk_version(Db)) + end) + end). + + +t_downgrade_with_1_purge_req() -> + ?_test(begin + % There are two documents in the fixture database + % with a single purge entry + DbName = <<"db_with_v7_and_1_purge_req">>, + ?assertEqual(7, get_disk_version_from_header(DbName)), + {ok, PurgeReq} = couch_util:with_db(DbName, fun(Db) -> + couch_db:get_purge_seq(Db) + end), + ?assertEqual(3, PurgeReq), + + {ok, _} = save_doc(DbName, {[{<<"_id">>, <<"doc4">>}, {<<"v">>, 1}]}), + {ok, _} = save_doc(DbName, {[{<<"_id">>, <<"doc5">>}, {<<"v">>, 2}]}), + + couch_util:with_db(DbName, fun(Db) -> + ?assertEqual({ok, 4}, couch_db:get_doc_count(Db)), + ?assertEqual({ok, 3}, couch_db:get_purge_seq(Db)), + ?assertEqual(6, couch_db_engine:get_disk_version(Db)) + end) + end). + + +t_downgrade_with_2_purge_req() -> + ?_test(begin + % There is one document in the fixture database + % with two docs that have been purged + DbName = <<"db_with_v7_and_2_purge_req">>, + ?assertEqual(7, get_disk_version_from_header(DbName)), + {ok, PurgeReq} = couch_util:with_db(DbName, fun(Db) -> + couch_db:get_purge_seq(Db) + end), + ?assertEqual(4, PurgeReq), + + {ok, _} = save_doc(DbName, {[{<<"_id">>, <<"doc4">>}, {<<"v">>, 1}]}), + {ok, _} = save_doc(DbName, {[{<<"_id">>, <<"doc5">>}, {<<"v">>, 2}]}), + + couch_util:with_db(DbName, fun(Db) -> + ?assertEqual({ok, 3}, couch_db:get_doc_count(Db)), + ?assertEqual({ok, 4}, couch_db:get_purge_seq(Db)), + ?assertEqual(6, couch_db_engine:get_disk_version(Db)) + end) + end). + + +get_disk_version_from_header(DbFileName) -> + DbDir = config:get("couchdb", "database_dir"), + DbFilePath = filename:join([DbDir, ?l2b(?b2l(DbFileName) ++ ".couch")]), + {ok, Fd} = couch_file:open(DbFilePath, []), + {ok, Header} = couch_file:read_header(Fd), + DiskVerison = couch_bt_engine_header:disk_version(Header), + couch_file:close(Fd), + DiskVerison. + + +save_doc(DbName, Json) -> + Doc = couch_doc:from_json_obj(Json), + couch_util:with_db(DbName, fun(Db) -> + couch_db:update_doc(Db, Doc, []) + end). diff --git a/src/couch/test/fixtures/db_with_v6_and_0_purge_req.couch b/src/couch/test/fixtures/db_with_v6_and_0_purge_req.couch Binary files differnew file mode 100644 index 000000000..814feb8e1 --- /dev/null +++ b/src/couch/test/fixtures/db_with_v6_and_0_purge_req.couch diff --git a/src/couch/test/fixtures/db_with_v7_and_0_purge_req.couch b/src/couch/test/fixtures/db_with_v7_and_0_purge_req.couch Binary files differnew file mode 100644 index 000000000..98a8a18b1 --- /dev/null +++ b/src/couch/test/fixtures/db_with_v7_and_0_purge_req.couch diff --git a/src/couch/test/fixtures/db_with_v7_and_1_purge_req.couch b/src/couch/test/fixtures/db_with_v7_and_1_purge_req.couch Binary files differnew file mode 100644 index 000000000..0996a88b1 --- /dev/null +++ b/src/couch/test/fixtures/db_with_v7_and_1_purge_req.couch diff --git a/src/couch/test/fixtures/db_with_v7_and_2_purge_req.couch b/src/couch/test/fixtures/db_with_v7_and_2_purge_req.couch Binary files differnew file mode 100644 index 000000000..2f17a0d65 --- /dev/null +++ b/src/couch/test/fixtures/db_with_v7_and_2_purge_req.couch |