You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by va...@apache.org on 2019/05/16 23:18:22 UTC
[couchdb] 01/01: CouchDB background jobs WIP
This is an automated email from the ASF dual-hosted git repository.
vatamane pushed a commit to branch prototype/rfc-couch-jobs
in repository https://gitbox.apache.org/repos/asf/couchdb.git
commit f8c2411739082c4708f4b87b97d314a070d6f060
Author: Nick Vatamaniuc <va...@apache.org>
AuthorDate: Thu May 16 19:11:50 2019 -0400
CouchDB background jobs WIP
So far got:
- Main API module (couch_jobs.er)
- Supervisor and app structures
- FDB read/write code in couch_jobs_fdb.erl
In couch_jobs_fdb.erl have:
- All jobs creation API: add(), remove(), submit(), get_job()
- Half of the worker API: finish(), resubmit()
- Directory path caching
- Metadata changes check
Still need:
- Finish worker API: accept() and update()
- Activity monitor implemenation
- An example worker of some sort (Maybe activity monitor can be worker as well...)
---
rebar.config.script | 1 +
rel/reltool.config | 2 +
src/couch_jobs/.gitignore | 4 +
src/couch_jobs/README.md | 5 +
src/couch_jobs/src/couch_jobs.app.src | 30 +++
src/couch_jobs/src/couch_jobs.erl | 96 +++++++++
src/couch_jobs/src/couch_jobs_app.erl | 26 +++
src/couch_jobs/src/couch_jobs_fdb.erl | 358 ++++++++++++++++++++++++++++++++++
src/couch_jobs/src/couch_jobs_sup.erl | 46 +++++
9 files changed, 568 insertions(+)
diff --git a/rebar.config.script b/rebar.config.script
index 3b58bcb..2def724 100644
--- a/rebar.config.script
+++ b/rebar.config.script
@@ -76,6 +76,7 @@ SubDirs = [
"src/couch_tests",
"src/ddoc_cache",
"src/fabric",
+ "src/couch_jobs",
"src/global_changes",
"src/mango",
"src/rexi",
diff --git a/rel/reltool.config b/rel/reltool.config
index 1051d2e..afebc44 100644
--- a/rel/reltool.config
+++ b/rel/reltool.config
@@ -34,6 +34,7 @@
couch,
couch_epi,
couch_index,
+ couch_jobs,
couch_log,
couch_mrview,
couch_plugins,
@@ -90,6 +91,7 @@
{app, config, [{incl_cond, include}]},
{app, couch, [{incl_cond, include}]},
{app, couch_epi, [{incl_cond, include}]},
+ {app, couch_jobs, [{incl_cond, include}]},
{app, couch_index, [{incl_cond, include}]},
{app, couch_log, [{incl_cond, include}]},
{app, couch_mrview, [{incl_cond, include}]},
diff --git a/src/couch_jobs/.gitignore b/src/couch_jobs/.gitignore
new file mode 100644
index 0000000..62c8b07
--- /dev/null
+++ b/src/couch_jobs/.gitignore
@@ -0,0 +1,4 @@
+*.beam
+.eunit
+ebin/couch_workers.app
+.DS_Store
\ No newline at end of file
diff --git a/src/couch_jobs/README.md b/src/couch_jobs/README.md
new file mode 100644
index 0000000..b2910a5
--- /dev/null
+++ b/src/couch_jobs/README.md
@@ -0,0 +1,5 @@
+CouchDB Jobs Application
+=========================
+
+Run background jobs in CouchDB
+
diff --git a/src/couch_jobs/src/couch_jobs.app.src b/src/couch_jobs/src/couch_jobs.app.src
new file mode 100644
index 0000000..51de1ab
--- /dev/null
+++ b/src/couch_jobs/src/couch_jobs.app.src
@@ -0,0 +1,30 @@
+% 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.
+
+{application, couch_jobs, [
+ {description, "CouchDB Jobs"},
+ {vsn, git},
+ {mod, {couch_jobs_app, []}},
+ {registered, [
+ couch_jobs_sup,
+ couch_jobs_global,
+ couch_jobs_local
+ ]},
+ {applications, [
+ kernel,
+ stdlib,
+ erlfdb,
+ couch_log,
+ config,
+ fabric
+ ]}
+]}.
diff --git a/src/couch_jobs/src/couch_jobs.erl b/src/couch_jobs/src/couch_jobs.erl
new file mode 100644
index 0000000..6166706
--- /dev/null
+++ b/src/couch_jobs/src/couch_jobs.erl
@@ -0,0 +1,96 @@
+% 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_jobs).
+
+-export([
+ add/3,
+ remove/2,
+ resubmit/2,
+ get_job/2,
+
+ accept/1,
+ accept/2,
+ finish/5,
+ resubmit/4,
+ update/5
+]).
+
+
+%% Job Creation API
+
+add(Type, JobId, JobOpts) ->
+ try
+ ok = validate_jobopts(JobOpts)
+ catch
+ Tag:Err -> {error, {invalid_job_args, Tag, Err}}
+ end,
+ couch_jobs_fdb:tx(couch_jobs_fdb:get_jtx(), fun(JTx) ->
+ couch_job_fdb:add(JTx, Type, JobId, JobOpts)
+ end).
+
+
+remove(Type, JobId) ->
+ % Add the bit about cancelling the job if it is running
+ % and waiting for it to stop, then remove it.
+ couch_jobs_fdb:tx(couch_jobs_fdb:get_jtx(), fun(JTx) ->
+ couch_jobs_fdb:remove(JTx, Type, JobId)
+ end).
+
+
+resubmit(Type, JobId) ->
+ couch_jobs_fdb:tx(couch_jobs_fdb:get_jtx(), fun(JTx) ->
+ couch_jobs_fdb:resubmit(JTx, Type, JobId)
+ end).
+
+
+get_job(Type, JobId) ->
+ couch_jobs_fdb:tx(couch_jobs_fdb:get_jtx(), fun(JTx) ->
+ couch_jobs_fdb:get_job(JTx, Type, JobId)
+ end).
+
+
+%% Worker Implementation API
+
+accept(Type) ->
+ accept(Type, undefined).
+
+
+accept(Type, MaxPriority) ->
+ couch_jobs_fdb:tx(couch_jobs_fdb:get_jtx(), fun(JTx) ->
+ couch_jobs_fdb:accept(JTx, Type, MaxPriority)
+ end).
+
+
+finish(Tx, Type, JobId, JobOpts, WorkerLockId) ->
+ couch_jobs_fdb:tx(couch_jobs_fdb:get_jtx(Tx), fun(JTx) ->
+ couch_jobs_fdb:finish(JTx, Type, JobId, JobOpts, WorkerLockId)
+ end).
+
+
+resubmit(Tx, Type, JobId, WorkerLockId) ->
+ couch_jobs_fdb:tx(couch_jobs_fdb:get_jtx(Tx), fun(JTx) ->
+ couch_jobs_fdb:resubmit(JTx, Type, JobId, WorkerLockId)
+ end).
+
+
+update(Tx, Type, JobId, JobOpts, WorkerLockId) ->
+ couch_jobs_fdb:tx(couch_jobs_fdb:get_jtx(Tx), fun(JTx) ->
+ couch_jobs_fdb:update(JTx, Type, JobId, JobOpts, WorkerLockId)
+ end).
+
+
+%% Private utils
+
+validate_jobopts(#{} = JobOpts) ->
+ jiffy:encode(JobOpts),
+ ok.
diff --git a/src/couch_jobs/src/couch_jobs_app.erl b/src/couch_jobs/src/couch_jobs_app.erl
new file mode 100644
index 0000000..720b948
--- /dev/null
+++ b/src/couch_jobs/src/couch_jobs_app.erl
@@ -0,0 +1,26 @@
+% 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_jobs_app).
+
+
+-behaviour(application).
+
+
+-export([
+ start/2,
+ stop/1
+]).
+
+
+start(_Type, []) ->
+ couch_jobs_sup:start_link().
+
+
+stop([]) ->
+ ok.
diff --git a/src/couch_jobs/src/couch_jobs_fdb.erl b/src/couch_jobs/src/couch_jobs_fdb.erl
new file mode 100644
index 0000000..2f58591
--- /dev/null
+++ b/src/couch_jobs/src/couch_jobs_fdb.erl
@@ -0,0 +1,358 @@
+% 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_jobs_fdb).
+
+-export([
+ add/4,
+ remove/3,
+ resubmit/3,
+ get_job/3,
+
+ accept/2,
+ accept/3,
+ finish/5,
+ resubmit/4,
+ update/5,
+
+ init_cache/0,
+
+ get_jtx/0,
+ get_jtx/1,
+
+ tx/1
+]).
+
+
+% JobOpts field definitions
+%
+-define(OPT_PRIORITY, <<"priority">>).
+-define(OPT_DATA, <<"data">>).
+-define(OPT_CANCEL, <<"cancel">>).
+-define(OPT_RESUBMIT, <<"resubmit">>).
+
+% These might be in a fabric public .hrl eventually
+%
+-define(uint2bin(I), binary:encode_unsigned(I, little)).
+-define(bin2uint(I), binary:decode_unsigned(I, little)).
+-define(UNSET_VS, {versionstamp, 16#FFFFFFFFFFFFFFFF, 16#FFFF}).
+-define(METADATA_VERSION_KEY, <<"$metadata_version_key$">>).
+
+% Data model definitions
+% Switch these to numbers eventually.
+%
+-define(JOBS, <<"couch_jobs">>).
+-define(DATA, <<"data">>).
+-define(PENDING, <<"pending">>).
+-define(WATCHES, <<"watches">>).
+-define(ACTIVITY_TIMEOUT, <<"activity_timeout">>).
+-define(ACTIVITY, <<"activity">>).
+
+%%% Data model %%%
+
+%% (?JOBS, ?DATA, Type, JobId) = (Sequence, WorkerLockId, Priority, JobOpts)
+%% (?JOBS, ?PENDING, Type, Priority, JobId) = ""
+%% (?JOBS, ?WATCHES, Type) = Sequence
+%% (?JOBS, ?ACTIVITY_TIMEOUT, Type) = ActivityTimeout
+%% (?JOBS, ?ACTIVITY, Type, Sequence) = JobId
+
+
+add(#{jtx := true} = JTx0, Type, JobId, JobOpts) ->
+ #{tx := Tx, jobs_path := JobsPath} = JTx = get_jtx(JTx0),
+ Key = pack({?DATA, Type, JobId}, JobsPath),
+ case erlfdb:wait(erlfdb:get(Tx, Key)) of
+ <<_/binary>> ->
+ {error, duplicate_job};
+ not_found ->
+ Priority = maps:get(?OPT_PRIORITY, JobOpts, ?UNSET_VS),
+ Val = pack({null, null, Priority, jiffy:encode(JobOpts)}),
+ case has_versionstamp(Priority) of
+ true -> erlfdb:set_versionstamped_value(Tx, Key, Val);
+ false -> erlfdb:set(Tx, Key, Val)
+ end,
+ pending_enqueue(JTx, Type, Priority, JobId),
+ ok
+ end.
+
+
+remove(#{jtx := true} = JTx0, Type, JobId) ->
+ #{tx := Tx, jobs_path := JobsPath} = JTx = get_jtx(JTx0),
+ Key = pack({?DATA, Type, JobId}, JobsPath),
+ case get_job(Tx, Key) of
+ {_, WorkerLockId, _, _} when WorkerLockId =/= null ->
+ {error, job_is_running};
+ {_, _, null, _, _} ->
+ erlfdb:clear(Tx, Key);
+ {_, _, Priority, _, _} ->
+ pending_remove(JTx, Type, Priority, JobId),
+ erlfdb:clear(Tx, Key);
+ not_found ->
+ not_found
+ end.
+
+
+resubmit(#{jtx := true} = JTx, Type, JobId) ->
+ #{tx := Tx, jobs_path := JobsPath} = get_jtx(JTx),
+ Key = pack({?DATA, Type, JobId}, JobsPath),
+ case get_job(Tx, Key) of
+ {_, _, _, #{?OPT_RESUBMIT := true}} ->
+ ok;
+ {Seq, WorkerLockId, Priority, #{} = JobOpts} ->
+ set_resubmit(Tx, Key, Seq, WorkerLockId, Priority, JobOpts);
+ not_found ->
+ not_found
+ end.
+
+
+get_job(#{jtx := true} = JTx, Type, JobId) ->
+ #{tx := Tx, jobs_path := JobsPath} = get_jtx(JTx),
+ Key = pack({?DATA, Type, JobId}, JobsPath),
+ case get_job(Tx, Key) of
+ {_, WorkerLockId, Priority, JobOpts} ->
+ {ok, JobOpts, job_state(WorkerLockId, Priority)};
+ not_found ->
+ not_found
+ end.
+
+
+% Worker public API
+
+accept(#{jtx := true} = JTx, Type) ->
+ accept(JTx, Type, undefined).
+
+
+accept(#{jtx := true} = JTx0, Type, MaxPriority) ->
+ #{tx := Tx, jobs_path := JobsPath} = JTx = get_jtx(JTx0),
+ PendingPrefix = pack({?PENDING, Type}, JobsPath),
+ WorkerLockId = fabric2_util:uuid(),
+ % Dequeue item from front of queue (use MaxPriority) for get_range
+ % Create activity entry
+ % Update sequence if jobs table
+ % Update sequence in "watches"
+ {ok, <<"AJobId">>, WorkerLockId}.
+
+
+
+finish(#{jtx := true} = JTx0, Type, JobId, JobOpts, WorkerLockId) ->
+ #{tx := Tx, jobs_path := JobsPath} = JTx = get_jtx(JTx0),
+ Key = pack({?DATA, Type, JobId}, JobsPath),
+ try
+ {Seq, _, _, JobOpts} = get_job_or_raise_status(Tx, Key, WorkerLockId),
+ ActivityKey = pack({?ACTIVITY, Type, Seq}, JobsPath),
+ erlfdb:clear(Tx, ActivityKey),
+ case maps:get(?OPT_RESUBMIT, JobOpts, false) of
+ true ->
+ Priority = maps:get(?OPT_PRIORITY, JobOpts, ?UNSET_VS),
+ JobOpts1 = maps:without([?OPT_PRIORITY], JobOpts),
+ Val = pack({null, null, Priority, jiffy:encode(JobOpts1)}),
+ case has_versionstamp(Priority) of
+ true -> erlfdb:set_versionstamped_value(Tx, Key, Val);
+ false -> erlfdb:set(Tx, Key, Val)
+ end,
+ pending_enqueue(JTx, Type, Priority, JobId);
+ false ->
+ Val = pack({null, null, null, jiffy:encode(JobOpts)}),
+ erlfdb:set(Tx, Key, Val)
+ end
+ catch
+ throw:worker_conflict -> worker_conflict;
+ throw:canceled -> canceled
+ end.
+
+
+resubmit(#{jtx := true} = JTx, Type, JobId, WorkerLockId) ->
+ #{tx := Tx, jobs_path := JobsPath} = get_jtx(JTx),
+ Key = pack({?DATA, Type, JobId}, JobsPath),
+ try
+ case get_job_or_raise_status(Tx, Key, WorkerLockId) of
+ {_, _, _, #{?OPT_RESUBMIT := true}} ->
+ ok;
+ {Seq, WorkerLockId, Priority, #{} = JobOpts} ->
+ set_resubmit(Tx, Key, Seq, WorkerLockId, Priority, JobOpts)
+ end
+ catch
+ throw:worker_conflict -> worker_conflict;
+ throw:canceled -> canceled
+ end.
+
+
+update(#{jtx := true} = JTx, Type, JobId, JobOpts, WorkerLockId) ->
+ #{tx := Tx, jobs_path := JobsPath} = get_jtx(JTx),
+ % TODO
+ ok. % worker_conflict | canceled
+
+
+% Cache initialization API. Called from the supervisor just to create the ETS
+% table. It returns `ignore` to tell supervisor it won't actually start any
+% process, which is what we want here.
+%
+init_cache() ->
+ ConcurrencyOpts = [{read_concurrency, true}, {write_concurrency, true}],
+ ets:new(?MODULE, [public, named_table] ++ ConcurrencyOpts),
+ ignore.
+
+
+% Cached job transaction object. This object wraps a transaction, caches the
+% directory lookup path, and the metadata version. The function can be used from
+% or outside the transaction. When used from a transaction it will verify if
+% the metadata was changed, and will refresh automatically.
+%
+get_jtx() ->
+ get_jtx(undefined).
+
+
+get_jtx(#{tx := Tx} = _TxDb) ->
+ get_jtx(Tx);
+
+get_jtx(undefined = _Tx) ->
+ case ets:lookup(?MODULE, ?JOBS) of
+ [{_, #{} = JTx}] -> JTx;
+ [] -> update_jtx_cache(init_jtx(undefined))
+ end;
+
+get_jtx({erlfdb_transaction, _} = Tx) ->
+ case ets:lookup(?MODULE, ?JOBS) of
+ [{_, #{} = JTx}] -> ensure_current(JTx#{tx := Tx});
+ [] -> update_jtx_cache(init_jtx(Tx))
+ end.
+
+
+% Private API helper functions
+
+
+get_job(Tx = {erlfdb_transaction, _}, Key) ->
+ case erlfdb:wait(erlfdb:get(Tx, Key)) of
+ <<_/binary>> = Val ->
+ {Seq, WorkerLockId, JobOptsBin} = unpack(Val),
+ JobOpts = jiffy:decode(JobOptsBin, [return_maps]),
+ {Seq, WorkerLockId, JobOpts};
+ not_found ->
+ not_found
+ end.
+
+
+get_job_or_raise_status(Tx, Key, WorkerLockId) ->
+ case get_job(Tx, Key) of
+ {_, CurWorkerLockId, _, _} when WorkerLockId =/= CurWorkerLockId ->
+ throw(worker_conflict);
+ {_, _, _, #{?OPT_CANCEL := true}} ->
+ throw(canceled);
+ {_, _, _, #{}} = Res ->
+ Res
+ end.
+
+
+set_resubmit(Tx, Key, Seq, WorkerLockId, Priority, JobOpts0) ->
+ JobOpts = JobOpts0#{?OPT_RESUBMIT => true},
+ Val = pack({Seq, WorkerLockId, Priority, jiffy:encode(JobOpts)}),
+ erlfdb:set(Tx, Key, Val).
+
+
+pending_enqueue(#{jtx := true} = JTx, Type, Priority, JobId) ->
+ #{tx := Tx, jobs_path := JobsPath} = JTx,
+ Key = pack({?PENDING, Type, Priority, JobId}, JobsPath),
+ case has_versionstamp(Priority) of
+ true -> erlfdb:set_versionstamped_key(Tx, Key, null);
+ false -> erlfdb:set(Tx, Key, null)
+ end.
+
+pending_remove(#{jtx := true} = JTx, Type, Priority, JobId) ->
+ #{tx := Tx, jobs_path := JobsPath} = JTx,
+ Key = pack({?PENDING, Type, Priority, JobId}, JobsPath),
+ erlfdb:clear(Tx, Key).
+
+
+has_versionstamp(?UNSET_VS) ->
+ true;
+
+has_versionstamp(Tuple) when is_tuple(Tuple) ->
+ has_versionstamp(tuple_to_list(Tuple));
+
+has_versionstamp([Elem | Rest]) ->
+ has_versionstamp(Elem) orelse has_versionstamp(Rest);
+
+has_versionstamp(_Other) ->
+ false.
+
+
+job_state(WorkerLockId, Priority) ->
+ case {WorkerLockId, Priority} of
+ {null, null} ->
+ finished;
+ {WorkerLockId, _} when WorkerLockId =/= null ->
+ running;
+ {_, Priority} when Priority =/= null ->
+ pending;
+ ErrorState ->
+ error({invalid_job_state, ErrorState})
+ end.
+
+
+pack(Val) ->
+ erlfdb_tuple:pack(Val).
+
+
+pack(Val, Prefix) ->
+ erlfdb_tuple:pack(Val, Prefix).
+
+
+unpack(Val) ->
+ erlfdb_tuple:unpack(Val).
+
+
+unpack(Val, Prefix) ->
+ erlfdb_tuple:unpack(Val, Prefix).
+
+
+tx(Fun) when is_function(Fun, 1) ->
+ fabric2_fdb:transactional(Fun).
+
+
+tx(Tx, Fun) when is_function(Fun, 1) ->
+ fabric2_fdb:transactional(Tx, Fun).
+
+
+% This a transaction context object similar to the Db = #{} one from fabric2_fdb.
+% It's is used to cache the jobs path directory (to avoid extra lookups on every
+% operation) and to check for metadata changes (in case directory changes).
+%
+init_jtx(undefined) ->
+ tx(fun(Tx) -> init_jtx(Tx) end);
+
+init_jtx({erlfdb_transaction, _} = Tx) ->
+ Root = erlfdb_directory:root(),
+ CouchDB = erlfdb_directory:create_or_open(Tx, Root, [<<"couchdb">>]),
+ LayerPrefix = erlfdb_directory:get_name(CouchDB),
+ JobsPrefix = erlfdb_tuple:pack({?JOBS}, LayerPrefix),
+ Version = erlfdb:wait(erlfdb:get(Tx, ?METADATA_VERSION_KEY)),
+ % layer_prefix, md_version and tx here match db map fields in fabric2_fdb
+ % but we also assert that this is a job transaction using the jtx => true field
+ #{
+ jtx => true,
+ tx => Tx,
+ layer_prefix => LayerPrefix,
+ jobs_prefix => JobsPrefix,
+ md_version => Version
+ }.
+
+
+ensure_current(#{jtx := true, tx := Tx, md_version := Version} = JTx) ->
+ case erlfdb:wait(erlfdb:get(Tx, ?METADATA_VERSION_KEY)) of
+ Version -> JTx;
+ _NewVersion -> update_jtx_cache(init_jtx(Tx))
+ end.
+
+
+update_jtx_cache(#{jtx := true} = JTx) ->
+ CachedJTx = JTx#{tx := undefined},
+ ets:insert(?MODULE, {?JOBS, CachedJTx}),
+ JTx.
diff --git a/src/couch_jobs/src/couch_jobs_sup.erl b/src/couch_jobs/src/couch_jobs_sup.erl
new file mode 100644
index 0000000..8a7832a
--- /dev/null
+++ b/src/couch_jobs/src/couch_jobs_sup.erl
@@ -0,0 +1,46 @@
+%
+% 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_jobs_sup).
+
+
+-behaviour(supervisor).
+
+
+-export([
+ start_link/0
+]).
+
+-export([
+ init/1
+]).
+
+
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+
+init([]) ->
+ Flags = #{
+ strategy => one_for_one,
+ intensity => 1,
+ period => 5
+ },
+ Children = [
+ #{
+ id => couch_jobs_fdb,
+ restart => transient,
+ start => {couch_jobs_fdb, init_cache, []}
+ }
+ ],
+ {ok, {Flags, Children}}.