Packages
ra
0.9.2
3.1.9
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.2
3.0.1
3.0.0
3.0.0-beta.1
2.17.3
2.17.2
2.17.1
2.17.0
2.16.13
2.16.12
2.16.11
2.16.10
2.16.9
2.16.8
2.16.7
2.16.6
2.16.5
2.16.4
2.16.3
2.16.2
2.16.1
2.16.0
2.16.0-pre.12
2.16.0-pre.11
2.16.0-pre.10
2.16.0-pre.9
2.16.0-pre.8
2.16.0-pre.7
2.16.0-pre.6
2.16.0-pre.5
2.16.0-pre.4
2.16.0-pre.3
2.16.0-pre.2
2.16.0-pre.1
2.15.4
2.15.3
2.15.2
2.15.1
2.15.0
2.14.0
2.13.6
2.13.5
2.13.4
2.13.3
2.13.2
2.13.1
2.13.0
2.13.0-pre.1
2.12.0
2.11.0
2.11.0-pre.1
2.10.2-pre.2
2.10.2-pre.1
2.10.1
2.10.0
2.10.0-pre.3
2.10.0-pre.2
2.10.0-pre.1
2.9.10-pre.1
2.9.1
2.9.1-pre.2
2.9.1-pre.1
2.9.0
2.8.0
retired
2.7.3
2.7.2
2.7.1
2.7.0
2.7.0-pre.3
2.7.0-pre.2
2.7.0-pre.1
2.6.3
2.6.2
2.6.1
2.6.0-pre.1
2.5.1
2.5.1-pre.1
2.5.0
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
retired
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.13
2.0.12
2.0.11
2.0.10
2.0.9
2.0.8
2.0.7
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.1.9
1.1.8
1.1.7
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.9.6
0.9.5
0.9.4
0.9.2
0.3.3
retired
0.3.2
retired
0.3.1
retired
Raft library
Current section
Files
Jump to
Current section
Files
src/ra_log_segment_writer.erl
%% @hidden
-module(ra_log_segment_writer).
-behaviour(gen_server).
-export([start_link/1,
accept_mem_tables/2,
accept_mem_tables/3,
delete_segments/3,
delete_segments/4,
release_segments/2,
my_segments/1,
my_segments/2,
await/0,
await/1,
overview/0
]).
-export([init/1,
handle_call/3,
handle_cast/2,
handle_info/2,
terminate/2,
code_change/3
]).
-record(state, {data_dir :: file:filename(),
segment_conf = #{} :: ra_log_segment:ra_log_segment_options(),
active_segments = #{} :: #{ra_uid() =>
ra_log_segment:state()}}).
-include("ra.hrl").
-define(AWAIT_TIMEOUT, 30000).
%%% ra_log_segment_writer
%%% receives a set of closed mem_segments from the wal
%%% appends to the current segment for the ra server
%%% notifies the ra server of any new/updates segments
%%%===================================================================
%%% API functions
%%%===================================================================
start_link(Config) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [Config], []).
accept_mem_tables(Tables, WalFile) ->
accept_mem_tables(?MODULE, Tables, WalFile).
accept_mem_tables(_SegmentWriter, [], undefined) ->
ok;
accept_mem_tables(SegmentWriter, Tables, WalFile) ->
gen_server:cast(SegmentWriter, {mem_tables, Tables, WalFile}).
-spec delete_segments(ra_uid(), ra_index(),
[file:filename_all()]) -> ok.
delete_segments(Who, SnapIdx, SegmentFiles) ->
delete_segments(?MODULE, Who, SnapIdx, SegmentFiles).
%% delete segmetns for a ra server. Takes a list of absolute filenames and deletes
%% all but the latest one immedately and sends the latest one to the segment
%% writer process to delete in case it is still open.
-spec delete_segments(atom() | pid(), ra_uid(),
ra_index(), [file:filename_all()]) -> ok.
delete_segments(SegWriter, Who, SnapIdx, SegmentFiles) ->
% delete all closed segment files
gen_server:cast(SegWriter, {delete_segments, Who, SnapIdx, SegmentFiles}).
-spec release_segments(atom() | pid(), ra_uid()) -> ok.
release_segments(SegWriter, Who) ->
gen_server:call(SegWriter, {release_segments, Who}).
%% returns the absolute filenames of all the segments
-spec my_segments(ra_uid()) -> [file:filename()].
my_segments(Who) ->
my_segments(?MODULE, Who).
-spec my_segments(atom() | pid(), ra_uid()) -> [file:filename()].
my_segments(SegWriter, Who) ->
gen_server:call(SegWriter, {my_segments, Who}, infinity).
-spec overview() -> #{}.
overview() ->
gen_server:call(?MODULE, overview).
% used to wait for the segment writer to finish processing anything in flight
await() ->
await(?MODULE).
await(SegWriter) ->
IsAlive = fun IsAlive(undefined) -> false;
IsAlive(P) when is_pid(P) ->
is_process_alive(P);
IsAlive(A) when is_atom(A) ->
IsAlive(whereis(A))
end,
case IsAlive(SegWriter) of
true ->
gen_server:call(SegWriter, await, ?AWAIT_TIMEOUT);
false ->
% if it is down it isn't processing anything
ok
end.
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
init([#{data_dir := DataDir} = Conf]) ->
process_flag(trap_exit, true),
SegmentConf = maps:get(segment_conf, Conf, #{}),
{ok, #state{data_dir = DataDir,
segment_conf = SegmentConf}}.
handle_call(await, _From, State) ->
{reply, ok, State};
handle_call({release_segments, Who}, _From,
#state{active_segments = ActiveSegments0} = State0) ->
case maps:take(Who, ActiveSegments0) of
{Seg, ActiveSegments} ->
% defensive
_ = ra_log_segment:close(Seg),
{reply, ok, State0#state{active_segments = ActiveSegments}};
error ->
{reply, ok, State0}
end;
handle_call({my_segments, Who}, _From,
#state{data_dir = DataDir} = State) ->
Dir = filename:join(DataDir, ra_lib:to_list(Who)),
SegFiles = lists:sort(filelib:wildcard(filename:join(Dir, "*.segment"))),
{reply, SegFiles, State};
handle_call(overview, _From, State) ->
{reply, get_overview(State), State}.
handle_cast({mem_tables, Tables, WalFile}, State0) ->
State = lists:foldl(fun do_segment/2, State0, Tables),
% delete wal file once done
% TODO: test scenario when server crashes after segments but before
% deleting walfile
% can we make segment writer idempotent somehow
?INFO("segment_writer: deleting wal file: ~s~n",
[filename:basename(WalFile)]),
%% temporarily disable wal deletion
%% TODO: this shoudl be a debug option config?
% Base = filename:basename(WalFile),
% BkFile = filename:join([State0#state.data_dir, "wals", Base]),
% filelib:ensure_dir(BkFile),
% file:copy(WalFile, BkFile),
_ = file:delete(WalFile),
%% ensure we release any bin refs that might have been acquired during
%% segment write
true = erlang:garbage_collect(),
{noreply, State};
handle_cast({delete_segments, Who, Idx, [SegmentFile | SegmentFiles]},
#state{active_segments = ActiveSegments} = State0) ->
[_ = file:delete(F) || F <- SegmentFiles],
case ActiveSegments of
#{Who := Seg} ->
case ra_log_segment:is_same_as(Seg, SegmentFile) of
true ->
% the segment file is the correct one
case ra_log_segment:range(Seg) of
{_From, To} when To =< Idx ->
% segment can be deleted
ok = ra_log_segment:close(Seg),
ok = file:delete(SegmentFile),
{noreply,
State0#state{active_segments =
maps:remove(Who, ActiveSegments)}};
_ ->
{noreply, State0}
end;
false ->
%% don't validate the file got deleted as it is possible
%% to get multiple requests under high load
_ = file:delete(SegmentFile),
{noreply, State0}
end;
_ ->
% if it isn't active we can just delete it
_ = file:delete(SegmentFile),
{noreply, State0}
end.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, #state{active_segments = ActiveSegments}) ->
% ensure any open segments are closed
[ok = ra_log_segment:close(Seg)
|| Seg <- maps:values(ActiveSegments)],
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
get_overview(#state{active_segments = Active,
segment_conf = Conf}) ->
#{active_segments => maps:size(Active),
segment_conf => Conf
}.
do_segment({ServerUId, StartIdx0, EndIdx, Tid},
#state{data_dir = DataDir,
segment_conf = SegConf,
active_segments = ActiveSegments} = State) ->
Dir = filename:join(DataDir, binary_to_list(ServerUId)),
case filelib:is_dir(Dir) of
true ->
%% check that the directory exists -
%% if it does not exist the server has
%% never been started or has been deleted
%% and we should just continue
Segment0 = case ActiveSegments of
#{ServerUId := S} -> S;
_ -> open_file(Dir, SegConf)
end,
case Segment0 of
undefined ->
State;
_ ->
{Segment1, Closed0} =
append_to_segment(ServerUId, Tid, StartIdx0, EndIdx,
Segment0, SegConf),
% fsync
{ok, Segment} = ra_log_segment:sync(Segment1),
% notify writerid of new segment update
% includes the full range of the segment
ClosedSegRefs = [ra_log_segment:segref(S) || S <- Closed0],
SegRefs = case ra_log_segment:segref(Segment) of
undefined ->
ClosedSegRefs;
SRef ->
[SRef | ClosedSegRefs]
end,
ok = send_segments(ServerUId, Tid, SegRefs),
State#state{active_segments =
ActiveSegments#{ServerUId => Segment}}
end;
false ->
?INFO("segment_writer: skipping segment as directory ~s does "
"not exist~n", [Dir]),
State
end.
start_index(ServerUId, StartIdx0) ->
case ets:lookup(ra_log_snapshot_state, ServerUId) of
[{_, SnapIdx}] ->
max(SnapIdx + 1, StartIdx0);
[] ->
StartIdx0
end.
send_segments(ServerUId, Tid, Segments) ->
Msg = {ra_log_event, {segments, Tid, Segments}},
try ra_directory:send(ServerUId, Msg) of
_ -> ok
catch
ErrType:Err ->
?INFO("ra_log_segment_writer: error sending "
"ra_log_event to: "
"~s. Error:~n~w:~W~n",
[ServerUId, ErrType, Err, 7]),
ok
end.
append_to_segment(UId, Tid, StartIdx0, EndIdx, Seg, SegConf) ->
StartIdx = start_index(UId, StartIdx0),
% EndIdx + 1 because FP
append_to_segment(UId, Tid, StartIdx, EndIdx+1, Seg, [], SegConf).
append_to_segment(_, _, StartIdx, EndIdx, Seg, Closed, _)
when StartIdx >= EndIdx ->
{Seg, Closed};
append_to_segment(UId, Tid, Idx, EndIdx, Seg0, Closed, SegConf) ->
[{_, Term, Data0}] = ets:lookup(Tid, Idx),
Data = term_to_binary(Data0),
case ra_log_segment:append(Seg0, Idx, Term, Data) of
{ok, Seg} ->
append_to_segment(UId, Tid, Idx+1, EndIdx, Seg, Closed, SegConf);
{error, full} ->
% close and open a new segment
Seg = open_successor_segment(Seg0, SegConf),
%% re-evaluate snapshot state for the server in case a snapshot
%% has completed during segmeng flush
StartIdx = start_index(UId, Idx),
% recurse
% TODO: there is a micro-inefficiency here in that we need to
% call term_to_binary and do an ETS lookup again that we have
% already done.
append_to_segment(UId, Tid, StartIdx, EndIdx, Seg,
[Seg0 | Closed], SegConf)
end.
find_segment_files(Dir) ->
lists:reverse(
lists:sort(filelib:wildcard(filename:join(Dir, "*.segment")))).
open_successor_segment(CurSeg, SegConf) ->
Fn0 = ra_log_segment:filename(CurSeg),
Fn = ra_lib:zpad_filename_incr(Fn0),
ok = ra_log_segment:close(CurSeg),
{ok, Seg} = ra_log_segment:open(Fn, SegConf),
Seg.
open_file(Dir, SegConf) ->
File = case find_segment_files(Dir) of
[] ->
F = ra_lib:zpad_filename("", "segment", 1),
Fn = filename:join(Dir, F),
_ = file:make_dir(Dir),
Fn;
[F | _] ->
F
end,
%% there is a small chance we'll get here without the target directory
%% existing this could happend during server deletion
case filelib:ensure_dir(File) of
ok ->
case ra_log_segment:open(File, SegConf#{mode => append}) of
{ok, Segment} ->
Segment;
Err ->
?WARN("segment_writer: failed to open segment file ~w"
"error: ~W", [File, Err]),
undefined
end;
{error, Err} ->
?WARN("segment_writer: failed to create directory ~w, Err: ~w~n",
[File, Err]),
undefined
end.