Initial hot_backup
The idea being that a consistent inker manifest and set of journal files is guaranteed - with hard links to the actual manifest files.
This commit is contained in:
parent
a1269e5274
commit
0838ff34e5
7 changed files with 211 additions and 37 deletions
|
@ -1,3 +1,6 @@
|
||||||
|
% File paths
|
||||||
|
-define(JOURNAL_FP, "journal").
|
||||||
|
-define(LEDGER_FP, "ledger").
|
||||||
|
|
||||||
%% Tag to be used on standard Riak KV objects
|
%% Tag to be used on standard Riak KV objects
|
||||||
-define(RIAK_TAG, o_rkv).
|
-define(RIAK_TAG, o_rkv).
|
||||||
|
|
|
@ -63,6 +63,7 @@
|
||||||
book_compactjournal/2,
|
book_compactjournal/2,
|
||||||
book_islastcompactionpending/1,
|
book_islastcompactionpending/1,
|
||||||
book_trimjournal/1,
|
book_trimjournal/1,
|
||||||
|
book_hotbackup/1,
|
||||||
book_close/1,
|
book_close/1,
|
||||||
book_destroy/1,
|
book_destroy/1,
|
||||||
book_isempty/2]).
|
book_isempty/2]).
|
||||||
|
@ -76,8 +77,6 @@
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
-define(CACHE_SIZE, 2500).
|
-define(CACHE_SIZE, 2500).
|
||||||
-define(JOURNAL_FP, "journal").
|
|
||||||
-define(LEDGER_FP, "ledger").
|
|
||||||
-define(SNAPSHOT_TIMEOUT, 300000).
|
-define(SNAPSHOT_TIMEOUT, 300000).
|
||||||
-define(CACHE_SIZE_JITTER, 25).
|
-define(CACHE_SIZE_JITTER, 25).
|
||||||
-define(JOURNAL_SIZE_JITTER, 20).
|
-define(JOURNAL_SIZE_JITTER, 20).
|
||||||
|
@ -587,6 +586,19 @@ book_destroy(Pid) ->
|
||||||
gen_server:call(Pid, destroy, infinity).
|
gen_server:call(Pid, destroy, infinity).
|
||||||
|
|
||||||
|
|
||||||
|
-spec book_hotbackup(pid()) -> {async, fun()}.
|
||||||
|
%% @doc Backup the Bookie
|
||||||
|
%% Return a function that will take a backup of a snapshot of the Journal.
|
||||||
|
%% The function will be 1-arity, and can be passed the absolute folder name
|
||||||
|
%% to store the backup.
|
||||||
|
%%
|
||||||
|
%% Backup files are hard-linked. Does not work in head_only mode
|
||||||
|
%%
|
||||||
|
%% TODO: Can extend to head_only mode, and also support another parameter
|
||||||
|
%% which would backup persisted part of ledger (to make restart faster)
|
||||||
|
book_hotbackup(Pid) ->
|
||||||
|
gen_server:call(Pid, hot_backup, infinity).
|
||||||
|
|
||||||
-spec book_isempty(pid(), leveled_codec:tag()) -> boolean().
|
-spec book_isempty(pid(), leveled_codec:tag()) -> boolean().
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Confirm if the store is empty, or if it contains a Key and Value for a
|
%% Confirm if the store is empty, or if it contains a Key and Value for a
|
||||||
|
@ -842,6 +854,19 @@ handle_call(confirm_compact, _From, State)
|
||||||
handle_call(trim, _From, State) when State#state.head_only == true ->
|
handle_call(trim, _From, State) when State#state.head_only == true ->
|
||||||
PSQN = leveled_penciller:pcl_persistedsqn(State#state.penciller),
|
PSQN = leveled_penciller:pcl_persistedsqn(State#state.penciller),
|
||||||
{reply, leveled_inker:ink_trim(State#state.inker, PSQN), State};
|
{reply, leveled_inker:ink_trim(State#state.inker, PSQN), State};
|
||||||
|
handle_call(hot_backup, _From, State) when State#state.head_only == false ->
|
||||||
|
ok = leveled_inker:ink_roll(State#state.inker),
|
||||||
|
BackupFun =
|
||||||
|
fun(InkerSnapshot) ->
|
||||||
|
fun(BackupPath) ->
|
||||||
|
ok = leveled_inker:ink_backup(InkerSnapshot, BackupPath),
|
||||||
|
ok = leveled_inker:ink_close(InkerSnapshot)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
InkerOpts =
|
||||||
|
#inker_options{start_snapshot=true, source_inker=State#state.inker},
|
||||||
|
{ok, Snapshot} = leveled_inker:ink_snapstart(InkerOpts),
|
||||||
|
{reply, {async, BackupFun(Snapshot)}, State};
|
||||||
handle_call(close, _From, State) ->
|
handle_call(close, _From, State) ->
|
||||||
leveled_inker:ink_close(State#state.inker),
|
leveled_inker:ink_close(State#state.inker),
|
||||||
leveled_penciller:pcl_close(State#state.penciller),
|
leveled_penciller:pcl_close(State#state.penciller),
|
||||||
|
@ -1019,8 +1044,8 @@ set_options(Opts) ->
|
||||||
PCLL0CacheSize = proplists:get_value(max_pencillercachesize, Opts),
|
PCLL0CacheSize = proplists:get_value(max_pencillercachesize, Opts),
|
||||||
RootPath = proplists:get_value(root_path, Opts),
|
RootPath = proplists:get_value(root_path, Opts),
|
||||||
|
|
||||||
JournalFP = RootPath ++ "/" ++ ?JOURNAL_FP,
|
JournalFP = filename:join(RootPath, ?JOURNAL_FP),
|
||||||
LedgerFP = RootPath ++ "/" ++ ?LEDGER_FP,
|
LedgerFP = filename:join(RootPath, ?LEDGER_FP),
|
||||||
ok = filelib:ensure_dir(JournalFP),
|
ok = filelib:ensure_dir(JournalFP),
|
||||||
ok = filelib:ensure_dir(LedgerFP),
|
ok = filelib:ensure_dir(LedgerFP),
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@
|
||||||
cdb_destroy/1,
|
cdb_destroy/1,
|
||||||
cdb_deletepending/1,
|
cdb_deletepending/1,
|
||||||
cdb_deletepending/3,
|
cdb_deletepending/3,
|
||||||
|
cdb_isrolling/1,
|
||||||
hashtable_calc/2]).
|
hashtable_calc/2]).
|
||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
@ -380,6 +381,13 @@ cdb_filename(Pid) ->
|
||||||
cdb_keycheck(Pid, Key) ->
|
cdb_keycheck(Pid, Key) ->
|
||||||
gen_fsm:sync_send_event(Pid, {key_check, Key}, infinity).
|
gen_fsm:sync_send_event(Pid, {key_check, Key}, infinity).
|
||||||
|
|
||||||
|
-spec cdb_isrolling(pid()) -> boolean().
|
||||||
|
%% @doc
|
||||||
|
%% Check to see if a cdb file is still rolling
|
||||||
|
cdb_isrolling(Pid) ->
|
||||||
|
gen_fsm:sync_send_all_state_event(Pid, cdb_isrolling, infinity).
|
||||||
|
|
||||||
|
|
||||||
%%%============================================================================
|
%%%============================================================================
|
||||||
%%% gen_server callbacks
|
%%% gen_server callbacks
|
||||||
%%%============================================================================
|
%%%============================================================================
|
||||||
|
@ -744,6 +752,8 @@ handle_sync_event(cdb_firstkey, _From, StateName, State) ->
|
||||||
{reply, FirstKey, StateName, State};
|
{reply, FirstKey, StateName, State};
|
||||||
handle_sync_event(cdb_filename, _From, StateName, State) ->
|
handle_sync_event(cdb_filename, _From, StateName, State) ->
|
||||||
{reply, State#state.filename, StateName, State};
|
{reply, State#state.filename, StateName, State};
|
||||||
|
handle_sync_event(cdb_isrolling, _From, StateName, State) ->
|
||||||
|
{reply, StateName == rolling, StateName, State};
|
||||||
handle_sync_event(cdb_close, _From, delete_pending, State) ->
|
handle_sync_event(cdb_close, _From, delete_pending, State) ->
|
||||||
leveled_log:log("CDB05",
|
leveled_log:log("CDB05",
|
||||||
[State#state.filename, delete_pending, cdb_close]),
|
[State#state.filename, delete_pending, cdb_close]),
|
||||||
|
|
|
@ -161,6 +161,8 @@ reader(SQN, RootPath) ->
|
||||||
%% disk
|
%% disk
|
||||||
writer(Manifest, ManSQN, RootPath) ->
|
writer(Manifest, ManSQN, RootPath) ->
|
||||||
ManPath = leveled_inker:filepath(RootPath, manifest_dir),
|
ManPath = leveled_inker:filepath(RootPath, manifest_dir),
|
||||||
|
ok = filelib:ensure_dir(ManPath),
|
||||||
|
% When writing during backups, may not have been generated
|
||||||
NewFN = filename:join(ManPath,
|
NewFN = filename:join(ManPath,
|
||||||
integer_to_list(ManSQN) ++ "." ++ ?MANIFEST_FILEX),
|
integer_to_list(ManSQN) ++ "." ++ ?MANIFEST_FILEX),
|
||||||
TmpFN = filename:join(ManPath,
|
TmpFN = filename:join(ManPath,
|
||||||
|
@ -198,10 +200,10 @@ complete_filex() ->
|
||||||
?MANIFEST_FILEX.
|
?MANIFEST_FILEX.
|
||||||
|
|
||||||
|
|
||||||
%%%============================================================================
|
-spec from_list(list()) -> manifest().
|
||||||
%%% Internal Functions
|
%% @doc
|
||||||
%%%============================================================================
|
%% Convert from a flat list into a manifest with lookup jumps.
|
||||||
|
%% The opposite of to_list/1
|
||||||
from_list(Manifest) ->
|
from_list(Manifest) ->
|
||||||
% Manifest should already be sorted with the highest SQN at the head
|
% Manifest should already be sorted with the highest SQN at the head
|
||||||
% This will be maintained so that we can fold from the left, and find
|
% This will be maintained so that we can fold from the left, and find
|
||||||
|
@ -209,6 +211,11 @@ from_list(Manifest) ->
|
||||||
% reads are more common than stale reads
|
% reads are more common than stale reads
|
||||||
lists:foldr(fun prepend_entry/2, [], Manifest).
|
lists:foldr(fun prepend_entry/2, [], Manifest).
|
||||||
|
|
||||||
|
|
||||||
|
%%%============================================================================
|
||||||
|
%%% Internal Functions
|
||||||
|
%%%============================================================================
|
||||||
|
|
||||||
prepend_entry(Entry, AccL) ->
|
prepend_entry(Entry, AccL) ->
|
||||||
{SQN, _FN, _PidR, _LastKey} = Entry,
|
{SQN, _FN, _PidR, _LastKey} = Entry,
|
||||||
case AccL of
|
case AccL of
|
||||||
|
|
|
@ -113,6 +113,8 @@
|
||||||
ink_printmanifest/1,
|
ink_printmanifest/1,
|
||||||
ink_close/1,
|
ink_close/1,
|
||||||
ink_doom/1,
|
ink_doom/1,
|
||||||
|
ink_roll/1,
|
||||||
|
ink_backup/2,
|
||||||
build_dummy_journal/0,
|
build_dummy_journal/0,
|
||||||
clean_testdir/1,
|
clean_testdir/1,
|
||||||
filepath/2,
|
filepath/2,
|
||||||
|
@ -235,10 +237,10 @@ ink_fetch(Pid, PrimaryKey, SQN) ->
|
||||||
ink_keycheck(Pid, PrimaryKey, SQN) ->
|
ink_keycheck(Pid, PrimaryKey, SQN) ->
|
||||||
gen_server:call(Pid, {key_check, PrimaryKey, SQN}, infinity).
|
gen_server:call(Pid, {key_check, PrimaryKey, SQN}, infinity).
|
||||||
|
|
||||||
-spec ink_registersnapshot(pid(), pid()) -> {list(), pid()}.
|
-spec ink_registersnapshot(pid(), pid()) -> {list(), pid(), integer()}.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Register a snapshot clone for the process, returning the Manifest and the
|
%% Register a snapshot clone for the process, returning the Manifest and the
|
||||||
%% pid of the active journal.
|
%% pid of the active journal, as well as the JournalSQN.
|
||||||
ink_registersnapshot(Pid, Requestor) ->
|
ink_registersnapshot(Pid, Requestor) ->
|
||||||
gen_server:call(Pid, {register_snapshot, Requestor}, infinity).
|
gen_server:call(Pid, {register_snapshot, Requestor}, infinity).
|
||||||
|
|
||||||
|
@ -389,6 +391,18 @@ ink_compactionpending(Pid) ->
|
||||||
ink_trim(Pid, PersistedSQN) ->
|
ink_trim(Pid, PersistedSQN) ->
|
||||||
gen_server:call(Pid, {trim, PersistedSQN}, infinity).
|
gen_server:call(Pid, {trim, PersistedSQN}, infinity).
|
||||||
|
|
||||||
|
-spec ink_roll(pid()) -> ok.
|
||||||
|
%% @doc
|
||||||
|
%% Roll the active journal
|
||||||
|
ink_roll(Pid) ->
|
||||||
|
gen_server:call(Pid, roll, infinity).
|
||||||
|
|
||||||
|
-spec ink_backup(pid(), string()) -> ok.
|
||||||
|
%% @doc
|
||||||
|
%% Backup the journal to the specified path
|
||||||
|
ink_backup(Pid, BackupPath) ->
|
||||||
|
gen_server:call(Pid, {backup, BackupPath}).
|
||||||
|
|
||||||
-spec ink_getmanifest(pid()) -> list().
|
-spec ink_getmanifest(pid()) -> list().
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Allows the clerk to fetch the manifest at the point it starts a compaction
|
%% Allows the clerk to fetch the manifest at the point it starts a compaction
|
||||||
|
@ -428,11 +442,13 @@ init([InkerOpts]) ->
|
||||||
{undefined, true} ->
|
{undefined, true} ->
|
||||||
SrcInker = InkerOpts#inker_options.source_inker,
|
SrcInker = InkerOpts#inker_options.source_inker,
|
||||||
{Manifest,
|
{Manifest,
|
||||||
ActiveJournalDB} = ink_registersnapshot(SrcInker, self()),
|
ActiveJournalDB,
|
||||||
{ok, #state{manifest=Manifest,
|
JournalSQN} = ink_registersnapshot(SrcInker, self()),
|
||||||
active_journaldb=ActiveJournalDB,
|
{ok, #state{manifest = Manifest,
|
||||||
source_inker=SrcInker,
|
active_journaldb = ActiveJournalDB,
|
||||||
is_snapshot=true}};
|
source_inker = SrcInker,
|
||||||
|
journal_sqn = JournalSQN,
|
||||||
|
is_snapshot = true}};
|
||||||
%% Need to do something about timeout
|
%% Need to do something about timeout
|
||||||
{_RootPath, false} ->
|
{_RootPath, false} ->
|
||||||
start_from_file(InkerOpts)
|
start_from_file(InkerOpts)
|
||||||
|
@ -477,7 +493,8 @@ handle_call({register_snapshot, Requestor}, _From , State) ->
|
||||||
State#state.manifest_sqn}|State#state.registered_snapshots],
|
State#state.manifest_sqn}|State#state.registered_snapshots],
|
||||||
leveled_log:log("I0002", [Requestor, State#state.manifest_sqn]),
|
leveled_log:log("I0002", [Requestor, State#state.manifest_sqn]),
|
||||||
{reply, {State#state.manifest,
|
{reply, {State#state.manifest,
|
||||||
State#state.active_journaldb},
|
State#state.active_journaldb,
|
||||||
|
State#state.journal_sqn},
|
||||||
State#state{registered_snapshots=Rs}};
|
State#state{registered_snapshots=Rs}};
|
||||||
handle_call({confirm_delete, ManSQN}, _From, State) ->
|
handle_call({confirm_delete, ManSQN}, _From, State) ->
|
||||||
CheckSQNFun =
|
CheckSQNFun =
|
||||||
|
@ -535,6 +552,56 @@ handle_call(compaction_pending, _From, State) ->
|
||||||
handle_call({trim, PersistedSQN}, _From, State) ->
|
handle_call({trim, PersistedSQN}, _From, State) ->
|
||||||
ok = leveled_iclerk:clerk_trim(State#state.clerk, self(), PersistedSQN),
|
ok = leveled_iclerk:clerk_trim(State#state.clerk, self(), PersistedSQN),
|
||||||
{reply, ok, State};
|
{reply, ok, State};
|
||||||
|
handle_call(roll, _From, State) ->
|
||||||
|
NewSQN = State#state.journal_sqn + 1,
|
||||||
|
{NewJournalP, Manifest1, NewManSQN} =
|
||||||
|
roll_active(State#state.active_journaldb,
|
||||||
|
State#state.manifest,
|
||||||
|
NewSQN,
|
||||||
|
State#state.cdb_options,
|
||||||
|
State#state.root_path,
|
||||||
|
State#state.manifest_sqn),
|
||||||
|
{reply, ok, State#state{journal_sqn = NewSQN,
|
||||||
|
manifest = Manifest1,
|
||||||
|
manifest_sqn = NewManSQN,
|
||||||
|
active_journaldb = NewJournalP}};
|
||||||
|
handle_call({backup, BackupPath}, _from, State)
|
||||||
|
when State#state.is_snapshot == true ->
|
||||||
|
SW = os:timestamp(),
|
||||||
|
BackupJFP = filepath(filename:join(BackupPath, ?JOURNAL_FP), journal_dir),
|
||||||
|
ok = filelib:ensure_dir(BackupJFP),
|
||||||
|
BackupFun =
|
||||||
|
fun({SQN, FN, PidR, LastKey}, Acc) ->
|
||||||
|
case SQN < State#state.journal_sqn of
|
||||||
|
true ->
|
||||||
|
BaseFN = filename:basename(FN),
|
||||||
|
BackupName = filename:join(BackupJFP, BaseFN),
|
||||||
|
false = when_not_rolling(PidR),
|
||||||
|
case file:make_link(FN ++ "." ++ ?JOURNAL_FILEX,
|
||||||
|
BackupName ++ "." ++ ?JOURNAL_FILEX) of
|
||||||
|
ok ->
|
||||||
|
ok;
|
||||||
|
{error, eexist} ->
|
||||||
|
ok
|
||||||
|
end,
|
||||||
|
[{SQN, BackupName, PidR, LastKey}|Acc];
|
||||||
|
false ->
|
||||||
|
leveled_log:log("I0021", [FN, SQN, State#state.journal_sqn]),
|
||||||
|
Acc
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
BackupManifest =
|
||||||
|
lists:foldr(BackupFun,
|
||||||
|
[],
|
||||||
|
leveled_imanifest:to_list(State#state.manifest)),
|
||||||
|
leveled_imanifest:writer(leveled_imanifest:from_list(BackupManifest),
|
||||||
|
State#state.manifest_sqn,
|
||||||
|
filename:join(BackupPath, ?JOURNAL_FP)),
|
||||||
|
leveled_log:log_timer("I0020",
|
||||||
|
[filename:join(BackupPath, ?JOURNAL_FP),
|
||||||
|
length(BackupManifest)],
|
||||||
|
SW),
|
||||||
|
{reply, ok, State};
|
||||||
handle_call(close, _From, State) ->
|
handle_call(close, _From, State) ->
|
||||||
case State#state.is_snapshot of
|
case State#state.is_snapshot of
|
||||||
true ->
|
true ->
|
||||||
|
@ -596,9 +663,9 @@ start_from_file(InkOpts) ->
|
||||||
% Determine filepaths
|
% Determine filepaths
|
||||||
RootPath = InkOpts#inker_options.root_path,
|
RootPath = InkOpts#inker_options.root_path,
|
||||||
JournalFP = filepath(RootPath, journal_dir),
|
JournalFP = filepath(RootPath, journal_dir),
|
||||||
filelib:ensure_dir(JournalFP),
|
ok = filelib:ensure_dir(JournalFP),
|
||||||
CompactFP = filepath(RootPath, journal_compact_dir),
|
CompactFP = filepath(RootPath, journal_compact_dir),
|
||||||
filelib:ensure_dir(CompactFP),
|
ok = filelib:ensure_dir(CompactFP),
|
||||||
ManifestFP = filepath(RootPath, manifest_dir),
|
ManifestFP = filepath(RootPath, manifest_dir),
|
||||||
ok = filelib:ensure_dir(ManifestFP),
|
ok = filelib:ensure_dir(ManifestFP),
|
||||||
% The IClerk must start files with the compaction file path so that they
|
% The IClerk must start files with the compaction file path so that they
|
||||||
|
@ -645,6 +712,19 @@ start_from_file(InkOpts) ->
|
||||||
clerk = Clerk}}.
|
clerk = Clerk}}.
|
||||||
|
|
||||||
|
|
||||||
|
when_not_rolling(CDB) ->
|
||||||
|
RollerFun =
|
||||||
|
fun(Sleep, WasRolling) ->
|
||||||
|
case WasRolling of
|
||||||
|
false ->
|
||||||
|
false;
|
||||||
|
true ->
|
||||||
|
timer:sleep(Sleep),
|
||||||
|
leveled_cdb:cdb_isrolling(CDB)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
lists:foldl(RollerFun, true, [0, 1000, 10000, 100000]).
|
||||||
|
|
||||||
-spec shutdown_snapshots(list(tuple())) -> ok.
|
-spec shutdown_snapshots(list(tuple())) -> ok.
|
||||||
%% @doc
|
%% @doc
|
||||||
%% Shutdown any snapshots before closing the store
|
%% Shutdown any snapshots before closing the store
|
||||||
|
@ -710,29 +790,20 @@ put_object(LedgerKey, Object, KeyChanges, State) ->
|
||||||
State#state{journal_sqn=NewSQN},
|
State#state{journal_sqn=NewSQN},
|
||||||
byte_size(JournalBin)};
|
byte_size(JournalBin)};
|
||||||
roll ->
|
roll ->
|
||||||
SWroll = os:timestamp(),
|
{NewJournalP, Manifest1, NewManSQN} =
|
||||||
LastKey = leveled_cdb:cdb_lastkey(ActiveJournal),
|
roll_active(ActiveJournal,
|
||||||
ok = leveled_cdb:cdb_roll(ActiveJournal),
|
State#state.manifest,
|
||||||
Manifest0 = leveled_imanifest:append_lastkey(State#state.manifest,
|
NewSQN,
|
||||||
ActiveJournal,
|
State#state.cdb_options,
|
||||||
LastKey),
|
|
||||||
CDBopts = State#state.cdb_options,
|
|
||||||
ManEntry = start_new_activejournal(NewSQN,
|
|
||||||
State#state.root_path,
|
State#state.root_path,
|
||||||
CDBopts),
|
State#state.manifest_sqn),
|
||||||
{_, _, NewJournalP, _} = ManEntry,
|
|
||||||
Manifest1 = leveled_imanifest:add_entry(Manifest0, ManEntry, true),
|
|
||||||
ok = leveled_imanifest:writer(Manifest1,
|
|
||||||
State#state.manifest_sqn + 1,
|
|
||||||
State#state.root_path),
|
|
||||||
ok = leveled_cdb:cdb_put(NewJournalP,
|
ok = leveled_cdb:cdb_put(NewJournalP,
|
||||||
JournalKey,
|
JournalKey,
|
||||||
JournalBin),
|
JournalBin),
|
||||||
leveled_log:log_timer("I0008", [], SWroll),
|
|
||||||
{rolling,
|
{rolling,
|
||||||
State#state{journal_sqn=NewSQN,
|
State#state{journal_sqn=NewSQN,
|
||||||
manifest=Manifest1,
|
manifest=Manifest1,
|
||||||
manifest_sqn = State#state.manifest_sqn + 1,
|
manifest_sqn = NewManSQN,
|
||||||
active_journaldb=NewJournalP},
|
active_journaldb=NewJournalP},
|
||||||
byte_size(JournalBin)}
|
byte_size(JournalBin)}
|
||||||
end.
|
end.
|
||||||
|
@ -756,6 +827,26 @@ get_object(LedgerKey, SQN, Manifest, ToIgnoreKeyChanges) ->
|
||||||
leveled_codec:from_inkerkv(Obj, ToIgnoreKeyChanges).
|
leveled_codec:from_inkerkv(Obj, ToIgnoreKeyChanges).
|
||||||
|
|
||||||
|
|
||||||
|
-spec roll_active(pid(), leveled_imanifest:manifest(),
|
||||||
|
integer(), #cdb_options{}, string(), integer()) ->
|
||||||
|
{pid(), leveled_imanifest:manifest(), integer()}.
|
||||||
|
%% @doc
|
||||||
|
%% Roll the active journal, and start a new active journal, updating the
|
||||||
|
%% manifest
|
||||||
|
roll_active(ActiveJournal, Manifest, NewSQN, CDBopts, RootPath, ManifestSQN) ->
|
||||||
|
SWroll = os:timestamp(),
|
||||||
|
LastKey = leveled_cdb:cdb_lastkey(ActiveJournal),
|
||||||
|
ok = leveled_cdb:cdb_roll(ActiveJournal),
|
||||||
|
Manifest0 =
|
||||||
|
leveled_imanifest:append_lastkey(Manifest, ActiveJournal, LastKey),
|
||||||
|
ManEntry =
|
||||||
|
start_new_activejournal(NewSQN, RootPath, CDBopts),
|
||||||
|
{_, _, NewJournalP, _} = ManEntry,
|
||||||
|
Manifest1 = leveled_imanifest:add_entry(Manifest0, ManEntry, true),
|
||||||
|
ok = leveled_imanifest:writer(Manifest1, ManifestSQN + 1, RootPath),
|
||||||
|
leveled_log:log_timer("I0008", [], SWroll),
|
||||||
|
{NewJournalP, Manifest1, ManifestSQN + 1}.
|
||||||
|
|
||||||
-spec key_check(leveled_codec:ledger_key(),
|
-spec key_check(leveled_codec:ledger_key(),
|
||||||
integer(),
|
integer(),
|
||||||
leveled_imanifest:manifest()) -> missing|probably.
|
leveled_imanifest:manifest()) -> missing|probably.
|
||||||
|
@ -1014,7 +1105,6 @@ sequencenumbers_fromfilenames(Filenames, Regex, IntName) ->
|
||||||
[],
|
[],
|
||||||
Filenames).
|
Filenames).
|
||||||
|
|
||||||
|
|
||||||
filepath(RootPath, journal_dir) ->
|
filepath(RootPath, journal_dir) ->
|
||||||
RootPath ++ "/" ++ ?FILES_FP ++ "/";
|
RootPath ++ "/" ++ ?FILES_FP ++ "/";
|
||||||
filepath(RootPath, manifest_dir) ->
|
filepath(RootPath, manifest_dir) ->
|
||||||
|
|
|
@ -280,6 +280,10 @@
|
||||||
{"I0019",
|
{"I0019",
|
||||||
{info, "After ~w PUTs total prepare time is ~w total cdb time is ~w "
|
{info, "After ~w PUTs total prepare time is ~w total cdb time is ~w "
|
||||||
++ "and max prepare time is ~w and max cdb time is ~w"}},
|
++ "and max prepare time is ~w and max cdb time is ~w"}},
|
||||||
|
{"I0020",
|
||||||
|
{info, "Journal backup completed to path=~s with file_count=~w"}},
|
||||||
|
{"I0021",
|
||||||
|
{info, "Ingoring filename=~s with SQN=~w and JournalSQN=~w"}},
|
||||||
|
|
||||||
{"IC001",
|
{"IC001",
|
||||||
{info, "Closed for reason ~w so maybe leaving garbage"}},
|
{info, "Closed for reason ~w so maybe leaving garbage"}},
|
||||||
|
|
|
@ -2,7 +2,8 @@
|
||||||
-include_lib("common_test/include/ct.hrl").
|
-include_lib("common_test/include/ct.hrl").
|
||||||
-include("include/leveled.hrl").
|
-include("include/leveled.hrl").
|
||||||
-export([all/0]).
|
-export([all/0]).
|
||||||
-export([retain_strategy/1,
|
-export([hot_backup_simple/1,
|
||||||
|
retain_strategy/1,
|
||||||
recovr_strategy/1,
|
recovr_strategy/1,
|
||||||
aae_missingjournal/1,
|
aae_missingjournal/1,
|
||||||
aae_bustedjournal/1,
|
aae_bustedjournal/1,
|
||||||
|
@ -10,6 +11,7 @@
|
||||||
]).
|
]).
|
||||||
|
|
||||||
all() -> [
|
all() -> [
|
||||||
|
hot_backup_simple,
|
||||||
retain_strategy,
|
retain_strategy,
|
||||||
recovr_strategy,
|
recovr_strategy,
|
||||||
aae_missingjournal,
|
aae_missingjournal,
|
||||||
|
@ -17,6 +19,39 @@ all() -> [
|
||||||
journal_compaction_bustedjournal
|
journal_compaction_bustedjournal
|
||||||
].
|
].
|
||||||
|
|
||||||
|
|
||||||
|
hot_backup_simple(_Config) ->
|
||||||
|
% The journal may have a hot backup. This allows for an online Bookie
|
||||||
|
% to be sent a message to prepare a backup function, which an asynchronous
|
||||||
|
% worker can then call to generate a backup taken at the point in time
|
||||||
|
% the original message was processsed.
|
||||||
|
%
|
||||||
|
% The basic test is to:
|
||||||
|
% 1 - load a Bookie, take a backup, delete the original path, restore from
|
||||||
|
% that path
|
||||||
|
RootPath = testutil:reset_filestructure(),
|
||||||
|
BookOpts = [{root_path, RootPath},
|
||||||
|
{cache_size, 1000},
|
||||||
|
{max_journalsize, 10000000},
|
||||||
|
{sync_strategy, testutil:sync_strategy()}],
|
||||||
|
{ok, Spcl1, LastV1} = rotating_object_check(BookOpts, "Bucket1", 10000),
|
||||||
|
{ok, Book1} = leveled_bookie:book_start(BookOpts),
|
||||||
|
{async, BackupFun} = leveled_bookie:book_hotbackup(Book1),
|
||||||
|
BackupPath = testutil:reset_filestructure("backup0"),
|
||||||
|
ok = BackupFun(BackupPath),
|
||||||
|
ok = leveled_bookie:book_close(Book1),
|
||||||
|
RootPath = testutil:reset_filestructure(),
|
||||||
|
BookOptsBackup = [{root_path, BackupPath},
|
||||||
|
{cache_size, 2000},
|
||||||
|
{max_journalsize, 20000000},
|
||||||
|
{sync_strategy, testutil:sync_strategy()}],
|
||||||
|
{ok, BookBackup} = leveled_bookie:book_start(BookOptsBackup),
|
||||||
|
ok = testutil:check_indexed_objects(BookBackup, "Bucket1", Spcl1, LastV1),
|
||||||
|
ok = leveled_bookie:book_close(BookBackup),
|
||||||
|
BackupPath = testutil:reset_filestructure("backup0").
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
retain_strategy(_Config) ->
|
retain_strategy(_Config) ->
|
||||||
RootPath = testutil:reset_filestructure(),
|
RootPath = testutil:reset_filestructure(),
|
||||||
BookOpts = [{root_path, RootPath},
|
BookOpts = [{root_path, RootPath},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue