summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Vatamaniuc <vatamane@apache.org>2019-09-24 02:25:39 -0400
committerNick Vatamaniuc <nickva@users.noreply.github.com>2019-09-24 11:03:51 -0400
commiteed8fb189dcbc2fe17eff798d16228ae036ce0ac (patch)
tree09a2e9e805a735f2799699d117e93f553fba195f
parent01a9228c91a42b5a99bd1478a9ad8c578b09b70f (diff)
downloadcouchdb-eed8fb189dcbc2fe17eff798d16228ae036ce0ac.tar.gz
Fix doc counts for replicated deletions
Do not decrement `doc_count` stat if document was previously missing, or if it was already deleted. Deleted documents could be brought in by replication. In that case, if there were more replicated documents than the current `doc_count`, the `doc_count` could even underflow the 64 bit unsigned integer range and end up somewhere in the vicinity of 2^64. The counter, of course, would still be incorrect even if it didn't underflow, the huge value would just make the issue more visible.
-rw-r--r--src/fabric/src/fabric2_fdb.erl12
-rw-r--r--src/fabric/test/fabric2_doc_count_tests.erl26
2 files changed, 36 insertions, 2 deletions
diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl
index ccfeb3c06..5c58da482 100644
--- a/src/fabric/src/fabric2_fdb.erl
+++ b/src/fabric/src/fabric2_fdb.erl
@@ -556,7 +556,7 @@ write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) ->
{not_found, #{deleted := false}} ->
created;
{not_found, #{deleted := true}} ->
- deleted;
+ replicate_deleted;
{#{deleted := true}, #{deleted := false}} ->
recreated;
{#{deleted := false}, #{deleted := false}} ->
@@ -564,10 +564,14 @@ write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) ->
{#{deleted := false}, #{deleted := true}} ->
deleted;
{#{deleted := true}, #{deleted := true}} ->
- deleted
+ ignore
end,
case UpdateStatus of
+ replicate_deleted ->
+ ok;
+ ignore ->
+ ok;
deleted ->
ADKey = erlfdb_tuple:pack({?DB_ALL_DOCS, DocId}, DbPrefix),
ok = erlfdb:clear(Tx, ADKey);
@@ -614,6 +618,10 @@ write_doc(#{} = Db0, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) ->
end,
incr_stat(Db, <<"doc_count">>, 1),
incr_stat(Db, <<"doc_del_count">>, -1);
+ replicate_deleted ->
+ incr_stat(Db, <<"doc_del_count">>, 1);
+ ignore ->
+ ok;
deleted ->
if not IsDDoc -> ok; true ->
incr_stat(Db, <<"doc_design_count">>, -1)
diff --git a/src/fabric/test/fabric2_doc_count_tests.erl b/src/fabric/test/fabric2_doc_count_tests.erl
index 37d08404d..743ae7665 100644
--- a/src/fabric/test/fabric2_doc_count_tests.erl
+++ b/src/fabric/test/fabric2_doc_count_tests.erl
@@ -30,6 +30,7 @@ doc_count_test_() ->
fun cleanup/1,
{with, [
fun normal_docs/1,
+ fun replicated_docs/1,
fun design_docs/1,
fun local_docs/1
]}
@@ -109,6 +110,31 @@ normal_docs({Db, _}) ->
).
+replicated_docs({Db, _}) ->
+ {DocCount, DelDocCount, DDocCount, LDocCount} = get_doc_counts(Db),
+
+ Opts = [replicated_changes],
+ {R1, R2, R3} = {<<"r1">>, <<"r2">>, <<"r3">>},
+
+ % First case is a simple replicated update
+ Doc1 = #doc{id = <<"rd1">>, revs = {1, [R1]}},
+ {ok, {1, R1}} = fabric2_db:update_doc(Db, Doc1, Opts),
+ check_doc_counts(Db, DocCount + 1, DelDocCount, DDocCount, LDocCount),
+
+ % Here a deleted document is replicated into the db. Doc count should not
+ % change, only deleted doc count.
+ Doc2 = #doc{id = <<"rd2">>, revs = {1, [R2]}, deleted = true},
+ {ok, {1, R2}} = fabric2_db:update_doc(Db, Doc2, Opts),
+ check_doc_counts(Db, DocCount + 1, DelDocCount + 1, DDocCount, LDocCount),
+
+ % Here we extended the deleted document's rev path but keep it deleted.
+ % Deleted doc count doesn't bumped since the document was already counted
+ % as deleted
+ Doc3 = #doc{id = <<"rd2">>, revs = {2, [R3, R2]}, deleted = true},
+ {ok, {2, R3}} = fabric2_db:update_doc(Db, Doc3, Opts),
+ check_doc_counts(Db, DocCount + 1, DelDocCount + 1 , DDocCount, LDocCount).
+
+
design_docs({Db, _}) ->
{DocCount, DelDocCount, DDocCount, LDocCount} = get_doc_counts(Db),