From commits-return-34741-archive-asf-public=cust-asf.ponee.io@couchdb.apache.org Wed Sep 19 19:20:18 2018 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx-eu-01.ponee.io (Postfix) with SMTP id CAF25180677 for ; Wed, 19 Sep 2018 19:20:17 +0200 (CEST) Received: (qmail 91396 invoked by uid 500); 19 Sep 2018 17:20:16 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 91329 invoked by uid 99); 19 Sep 2018 17:20:16 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 19 Sep 2018 17:20:16 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 5406682CBD; Wed, 19 Sep 2018 17:20:16 +0000 (UTC) Date: Wed, 19 Sep 2018 17:20:17 +0000 To: "commits@couchdb.apache.org" Subject: [couchdb] 01/01: Handle database deletions asynchronously MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit From: davisp@apache.org In-Reply-To: <153737761615.7186.4218020274818863227@gitbox.apache.org> References: <153737761615.7186.4218020274818863227@gitbox.apache.org> X-Git-Host: gitbox.apache.org X-Git-Repo: couchdb X-Git-Refname: refs/heads/couch-server-ets-lru X-Git-Reftype: branch X-Git-Rev: a5667dc8a61242bfd3e8d5a7241c8f838cad125d X-Git-NotificationType: diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated Message-Id: <20180919172016.5406682CBD@gitbox.apache.org> This is an automated email from the ASF dual-hosted git repository. davisp pushed a commit to branch couch-server-ets-lru in repository https://gitbox.apache.org/repos/asf/couchdb.git commit a5667dc8a61242bfd3e8d5a7241c8f838cad125d Author: Paul J. Davis AuthorDate: Wed Sep 19 11:54:51 2018 -0500 Handle database deletions asynchronously This changes deletion handlding to be asynchronous in couch_server. The biggest change here is that open requests during a deletion will return not_found before deletions occur. --- src/couch/src/couch_server.erl | 177 ++++++++++++++++++++++++------------- src/couch/src/couch_server_int.hrl | 1 + 2 files changed, 118 insertions(+), 60 deletions(-) diff --git a/src/couch/src/couch_server.erl b/src/couch/src/couch_server.erl index 8189f90..15b131d 100644 --- a/src/couch/src/couch_server.erl +++ b/src/couch/src/couch_server.erl @@ -396,17 +396,21 @@ close_lru(NewestDbName, Lru0, Skipped0) -> close_lru_int(DbName) -> case ets:update_element(couch_dbs, DbName, {#entry.lock, locked}) of true -> - [#entry{db = Db, pid = Pid}] = ets:lookup(couch_dbs, DbName), - case couch_db:is_idle(Db) of - true -> - true = ets:delete(couch_dbs, DbName), - true = ets:delete(couch_dbs_pid_to_name, Pid), - exit(Pid, kill), - closed; - false -> - ElemSpec = {#entry.lock, unlocked}, - true = ets:update_element(couch_dbs, DbName, ElemSpec), - skipped + case ets:lookup(couch_dbs, DbName) of + [#entry{req_type = delete}] -> + ignored; + [#entry{db = Db, pid = Pid}] -> + case couch_db:is_idle(Db) of + true -> + true = ets:delete(couch_dbs, DbName), + true = ets:delete(couch_dbs_pid_to_name, Pid), + exit(Pid, kill), + closed; + false -> + Op = {#entry.lock, unlocked}, + true = ets:update_element(couch_dbs, DbName, Op), + skipped + end end; false -> ignored @@ -471,6 +475,62 @@ open_async_int(Server, DbName, Options) -> end. +delete_async(Server, From, DbName, Options) -> + Parent = self(), + T0 = os:timestamp(), + Deleter = spawn_link(fun() -> + Res = delete_async_int(Server, DbName, Options), + gen_server:call(Parent, {delete_result, DbName, Res}, infinity), + unlink(Parent), + case Res == ok of + true -> + % Track latency times for successful deletions + Diff = timer:now_diff(os:timestamp(), T0) / 1000, + couch_stats:update_histogram([couchdb, db_delete_time], Diff); + false -> + % Log unsuccessful results + couch_log:info("delete_result error ~p for ~s", [Res, DbName]) + end + end), + true = ets:insert(couch_dbs, #entry{ + name = DbName, + pid = Opener, + lock = locked, + waiters = [From], + req_type = delete, + db_options = Options + }), + true = ets:insert(couch_dbs_pid_to_name, {Deleter, DbName}), + Server. + + +delete_async_int(Server, DbName, Options) -> + DbNameList = binary_to_list(DbName), + case check_dbname(Server, DbNameList) of + ok -> + couch_db_plugin:on_delete(DbName, Options), + + DelOpt = [{context, delete} | Options], + + % Make sure and remove all compaction data + delete_compaction_files(DbNameList, Options), + + {ok, {Engine, FilePath}} = get_engine(Server, DbNameList), + RootDir = Server#server.root_dir, + case couch_db_engine:delete(Engine, RootDir, FilePath, DelOpt) of + ok -> + couch_event:notify(DbName, deleted), + ok; + {error, enoent} -> + not_found; + Else -> + Else + end; + Error1 -> + Error1 + end. + + handle_call(close_lru, _From, #server{lru=Lru} = Server) -> case close_lru(Lru) of {true, NewLru} -> @@ -496,11 +556,11 @@ handle_call({open_result, DbName, {ok, Db}}, {Opener, _}, Server) -> % db was deleted during async open exit(DbPid, kill), {reply, ok, Server}; - [#entry{pid = Opener, req_type = ReqType, waiters = Waiters} = Entry] -> + [#entry{pid = Opener, next_req = NextReq, waiters = Waiters} = Entry] -> link(DbPid), [gen_server:reply(Waiter, {ok, Db}) || Waiter <- Waiters], % Cancel the creation request if it exists. - case ReqType of + case NextReq of {create, DbName, _Options, CrFrom} -> gen_server:reply(CrFrom, file_exists); _ -> @@ -536,14 +596,14 @@ handle_call({open_result, DbName, Error}, {Opener, _}, Server) -> [] -> % db was deleted during async open {reply, ok, Server}; - [#entry{pid = Opener, req_type = ReqType, waiters = Waiters} = Entry] -> + [#entry{pid = Opener, next_req = NextReq, waiters = Waiters} = Entry] -> [gen_server:reply(Waiter, Error) || Waiter <- Waiters], true = ets:delete(couch_dbs, DbName), true = ets:delete(couch_dbs_pid_to_name, Opener), - NewServer = case ReqType of + NewServer = case NextReq of {create, DbName, Options, CrFrom} -> open_async(Server, CrFrom, DbName, Options); - _ -> + undefined -> Server end, {reply, ok, db_closed(NewServer, Entry#entry.db_options)}; @@ -552,6 +612,20 @@ handle_call({open_result, DbName, Error}, {Opener, _}, Server) -> % was in our mailbox and is now stale. Ignore it. {reply, ok, Server} end; +handle_call({delete_result, DbName, Res}, {Deleter, _}, Server) -> + true = ets:delete(couch_dbs_pid_to_name, Deleter), + % Assert that we're not mixing up deletion requests with + % anything else as that would be hair on fire bad. + [#entry{pid = Deleter} = Entry] = ets:lookup(couch_dbs, DbName), + true = ets:delete(couch_dbs, DbName), + [gen_server:reply(Waiter, Res) || Waiter <- Entry#entry.waiters], + NewServer = case Entry#entry.next_req of + {create, DbName, Options, CrFrom} -> + open_async(Server, CrFrom, DbName, Options); + undefined -> + Server + end, + {noreply, NewServer}; handle_call({open, DbName, Options}, From, Server) -> case ets:lookup(couch_dbs, DbName) of [] -> @@ -561,6 +635,8 @@ handle_call({open, DbName, Options}, From, Server) -> {CloseError, Server2} -> {reply, CloseError, Server2} end; + [#entry{req_type = delete}] -> + {reply, {error, file_not_found}, Server}; [#entry{waiters = Waiters} = Entry] when is_list(Waiters) -> true = ets:insert(couch_dbs, Entry#entry{waiters = [From | Waiters]}), NumWaiters = length(Waiters), @@ -582,57 +658,38 @@ handle_call({create, DbName, Options}, From, Server) -> {CloseError, Server2} -> {reply, CloseError, Server2} end; - [#entry{req_type = open} = Entry] -> + [#entry{req_type = RT} = Entry] when RT == open; RT == delete -> % We're trying to create a database while someone is in - % the middle of trying to open it. We allow one creator - % to wait while we figure out if it'll succeed. + % the middle of trying to open or delete it. We allow one + % creator to wait while we figure out if it'll succeed. CrOptions = [create | Options], Req = {create, DbName, CrOptions, From}, - true = ets:insert(couch_dbs, Entry#entry{req_type = Req}), + true = ets:insert(couch_dbs, Entry#entry{next_req = Req}), {noreply, Server}; [_AlreadyRunningDb] -> {reply, file_exists, Server} end; -handle_call({delete, DbName, Options}, _From, Server) -> - DbNameList = binary_to_list(DbName), - case check_dbname(Server, DbNameList) of - ok -> - Server2 = - case ets:lookup(couch_dbs, DbName) of - [] -> Server; - [#entry{pid = Pid, waiters = Waiters} = Entry] when is_list(Waiters) -> - true = ets:delete(couch_dbs, DbName), - true = ets:delete(couch_dbs_pid_to_name, Pid), - exit(Pid, kill), - [gen_server:reply(Waiter, not_found) || Waiter <- Waiters], - db_closed(Server, Entry#entry.db_options); - [#entry{pid = Pid} = Entry] -> - true = ets:delete(couch_dbs, DbName), - true = ets:delete(couch_dbs_pid_to_name, Pid), - exit(Pid, kill), - db_closed(Server, Entry#entry.db_options) +handle_call({delete, DbName, Options}, From, Server) -> + case ets:lookup(couch_dbs, DbName) of + [] -> + {noreply, delete_async(Server, From, DbName, Options)}; + [#entry{req_type = delete, waiters = Waiters} = Entry] -> + true = ets:insert(couch_dbs, Entry#entry{waiters = [From | Waiters]}), + NumWaiters = length(Waiters), + if NumWaiters =< 10 orelse NumWaiters rem 10 /= 0 -> ok; true -> + Fmt = "~b clients waiting to delete db ~s", + couch_log:info(Fmt, [length(Waiters), DbName]) end, - - couch_db_plugin:on_delete(DbName, Options), - - DelOpt = [{context, delete} | Options], - - % Make sure and remove all compaction data - delete_compaction_files(DbNameList, Options), - - {ok, {Engine, FilePath}} = get_engine(Server, DbNameList), - RootDir = Server#server.root_dir, - case couch_db_engine:delete(Engine, RootDir, FilePath, DelOpt) of - ok -> - couch_event:notify(DbName, deleted), - {reply, ok, Server2}; - {error, enoent} -> - {reply, not_found, Server2}; - Else -> - {reply, Else, Server2} - end; - Error -> - {reply, Error, Server} + {noreply, Server}; + [#entry{pid = Pid, waiters = Waiters} = Entry] -> + true = ets:delete(couch_dbs, DbName), + true = ets:delete(couch_dbs_pid_to_name, Pid), + exit(Pid, kill), + if not is_list(Waiters) -> ok; true -> + [gen_server:reply(Waiter, not_found) || Waiter <- Waiters] + end, + NewServer = db_closed(Server, Entry#entry.db_options), + {noreply, delete_async(NewServer, From, DbName, Options)} end; handle_call({db_updated, Db}, _From, Server0) -> DbName = couch_db:name(Db), @@ -696,7 +753,7 @@ handle_info({'EXIT', Pid, Reason}, Server) -> % We kill databases on purpose so there's no reason % to log that fact. So we restrict logging to "interesting" % reasons. - if Reason /= normal orelse Reason /= killed -> + if Reason == normal orelse Reason == killed -> ok; true -> couch_log:info("db ~s died with reason ~p", [DbName, Reason]) end, if not is_list(Waiters) -> ok; true -> diff --git a/src/couch/src/couch_server_int.hrl b/src/couch/src/couch_server_int.hrl index 537a6ab..7ab9e60 100644 --- a/src/couch/src/couch_server_int.hrl +++ b/src/couch/src/couch_server_int.hrl @@ -18,6 +18,7 @@ lock, waiters, req_type, + next_req, db_options, start_time }).