-
Notifications
You must be signed in to change notification settings - Fork 12
Expose C_SEGMENT_SIZE_BYTES gauges
#218
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
faf46d1
7e01092
4530f93
5a6721e
8a3c74f
fa26dbe
1345908
ddcd47d
bdade52
7f8fc12
c3f0367
9a2daf9
b6f0ea0
796c0d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -84,14 +84,18 @@ | |
| -define(C_FIRST_TIMESTAMP, 3). | ||
| -define(C_CHUNKS, 4). | ||
| -define(C_SEGMENTS, 5). | ||
| -define(C_SEGMENT_SIZE_BYTES, 6). | ||
| -define(C_INDEX_SIZE_BYTES, 7). | ||
| -define(COUNTER_FIELDS, | ||
| [ | ||
| {offset, ?C_OFFSET, counter, | ||
| "The last offset (not chunk id) in the log for writers. The last offset read for readers" }, | ||
| {first_offset, ?C_FIRST_OFFSET, counter, "First offset, not updated for readers"}, | ||
| {first_timestamp, ?C_FIRST_TIMESTAMP, counter, "First timestamp, not updated for readers"}, | ||
| {chunks, ?C_CHUNKS, counter, "Number of chunks read or written, incremented even if a reader only reads the header"}, | ||
| {segments, ?C_SEGMENTS, counter, "Number of segments"} | ||
| {segments, ?C_SEGMENTS, counter, "Number of segments"}, | ||
| {segment_size_bytes, ?C_SEGMENT_SIZE_BYTES, counter, "Total size of all segment files in bytes"}, | ||
| {index_size_bytes, ?C_INDEX_SIZE_BYTES, counter, "Total size of all index files in bytes"} | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like all of these are
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree for a future PR |
||
| ] | ||
| ). | ||
|
|
||
|
|
@@ -596,10 +600,13 @@ init(#{dir := Dir, | |
| TailInfo = {LastChId + LastNum, | ||
| {LastEpoch, LastChId, LastTs}}, | ||
|
|
||
| {InitSegBytes, InitIdxBytes} = sum_log_sizes(Config), | ||
| counters:put(Cnt, ?C_FIRST_OFFSET, FstChId), | ||
| counters:put(Cnt, ?C_FIRST_TIMESTAMP, FstTs), | ||
| counters:put(Cnt, ?C_OFFSET, LastChId + LastNum - 1), | ||
| counters:put(Cnt, ?C_SEGMENTS, NumSegments), | ||
|
lukas8219 marked this conversation as resolved.
Outdated
lukas8219 marked this conversation as resolved.
Outdated
|
||
| counters:put(Cnt, ?C_SEGMENT_SIZE_BYTES, InitSegBytes), | ||
| counters:put(Cnt, ?C_INDEX_SIZE_BYTES, InitIdxBytes), | ||
|
the-mikedavis marked this conversation as resolved.
Outdated
|
||
| osiris_log_shared:set_first_chunk_id(Shared, FstChId), | ||
| osiris_log_shared:set_last_chunk_id(Shared, LastChId), | ||
| ?DEBUG_(Name, " next offset ~b first offset ~b", | ||
|
|
@@ -632,6 +639,8 @@ init(#{dir := Dir, | |
| {ok, IdxFd} = open(IdxFilename, ?FILE_OPTS_WRITE), | ||
| {ok, _} = file:position(SegFd, ?LOG_HEADER_SIZE), | ||
| counters:put(Cnt, ?C_SEGMENTS, 1), | ||
| counters:put(Cnt, ?C_SEGMENT_SIZE_BYTES, ?LOG_HEADER_SIZE), | ||
| counters:put(Cnt, ?C_INDEX_SIZE_BYTES, ?IDX_HEADER_SIZE), | ||
| %% the segment could potentially have trailing data here so we'll | ||
| %% do a truncate just in case. The index would have been truncated | ||
| %% earlier | ||
|
|
@@ -924,16 +933,18 @@ chunk_id_index_scan0(Fd, ChunkId) -> | |
| delete_segment_from_index(Index) -> | ||
| File = segment_from_index_file(Index), | ||
| ?DEBUG("osiris_log: deleting segment ~ts", [File]), | ||
| SegSize = file_size_or_zero(File), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this incurs an additional syscall for all calls of this function, even if they don't make use of it. Perhaps we need two different functions or the file size if got independently where needed.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've used the Size from previous read. What I am not completely sure is if, there's any chance the segment size would change between the first |
||
| IdxSize = file_size_or_zero(Index), | ||
| ok = prim_file:delete(Index), | ||
| ok = prim_file:delete(File), | ||
| ok. | ||
| {SegSize, IdxSize}. | ||
|
|
||
| truncate_to(_Name, _Range, _EpochOffsets, []) -> | ||
| %% the target log is empty | ||
| []; | ||
| truncate_to(_Name, _Range, [], IdxFiles) -> | ||
| %% ????? this means the entire log is out | ||
| [begin ok = delete_segment_from_index(I) end || I <- IdxFiles], | ||
| [delete_segment_from_index(I) || I <- IdxFiles], | ||
| []; | ||
| truncate_to(Name, RemoteRange, [{E, ChId} | NextEOs], IdxFiles) -> | ||
| case find_segment_for_offset(ChId, IdxFiles) of | ||
|
|
@@ -958,8 +969,7 @@ truncate_to(Name, RemoteRange, [{E, ChId} | NextEOs], IdxFiles) -> | |
| true -> | ||
| %% there is no overlap, need to delete all | ||
| %% local segments | ||
| [begin ok = delete_segment_from_index(I) end | ||
| || I <- IdxFiles], | ||
| [delete_segment_from_index(I) || I <- IdxFiles], | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure we pattern match from above is really necessary?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah looks like it isn't since |
||
| []; | ||
| false -> | ||
| %% there is overlap | ||
|
|
@@ -1001,7 +1011,7 @@ truncate_to(Name, RemoteRange, [{E, ChId} | NextEOs], IdxFiles) -> | |
| fun (I) -> | ||
| case index_file_first_offset(I) > ChId of | ||
| true -> | ||
| ok = delete_segment_from_index(I), | ||
| _ = delete_segment_from_index(I), | ||
| false; | ||
| false -> | ||
| true | ||
|
|
@@ -2254,35 +2264,34 @@ update_retention(Retention, | |
|
|
||
| -spec evaluate_retention(file:filename_all(), [retention_spec()]) -> | ||
| {range(), FirstTimestamp :: osiris:timestamp(), | ||
| NumRemainingFiles :: non_neg_integer()}. | ||
| NumRemainingFiles :: non_neg_integer(), | ||
| {DeletedSegBytes :: non_neg_integer(), DeletedIndexBytes :: non_neg_integer()}}. | ||
|
lukas8219 marked this conversation as resolved.
Outdated
the-mikedavis marked this conversation as resolved.
Outdated
|
||
| evaluate_retention(Dir, Specs) when is_list(Dir) -> | ||
| % convert to binary for faster operations later | ||
| % mostly in segment_from_index_file/1 | ||
|
the-mikedavis marked this conversation as resolved.
|
||
| evaluate_retention(unicode:characters_to_binary(Dir), Specs); | ||
| evaluate_retention(Dir, Specs) when is_binary(Dir) -> | ||
|
|
||
| {Time, Result} = timer:tc( | ||
| fun() -> | ||
| IdxFiles0 = sorted_index_files(Dir), | ||
| IdxFiles = evaluate_retention0(IdxFiles0, Specs), | ||
| {IdxFiles, DeletedBytes} = | ||
| evaluate_retention0(IdxFiles0, Specs, {0, 0}), | ||
| OffsetRange = offset_range_from_idx_files(IdxFiles), | ||
| FirstTs = first_timestamp_from_index_files(IdxFiles), | ||
| {OffsetRange, FirstTs, length(IdxFiles)} | ||
| {OffsetRange, FirstTs, length(IdxFiles), DeletedBytes} | ||
| end), | ||
| ?DEBUG_(<<>>," (~w) completed in ~fms", [Specs, Time/1_000]), | ||
| Result. | ||
|
|
||
| evaluate_retention0(IdxFiles, []) -> | ||
| IdxFiles; | ||
| evaluate_retention0(IdxFiles, [{max_bytes, MaxSize} | Specs]) -> | ||
| RemIdxFiles = eval_max_bytes(IdxFiles, MaxSize), | ||
| evaluate_retention0(RemIdxFiles, Specs); | ||
| evaluate_retention0(IdxFiles, [{max_age, Age} | Specs]) -> | ||
| RemIdxFiles = eval_age(IdxFiles, Age), | ||
| evaluate_retention0(RemIdxFiles, Specs). | ||
| evaluate_retention0(IdxFiles, [], DeletedBytes) -> | ||
| {IdxFiles, DeletedBytes}; | ||
| evaluate_retention0(IdxFiles, [{max_bytes, MaxSize} | Specs], {AccSeg, AccIdx}) -> | ||
| {RemIdxFiles, {DelSeg, DelIdx}} = eval_max_bytes(IdxFiles, MaxSize), | ||
| evaluate_retention0(RemIdxFiles, Specs, {AccSeg + DelSeg, AccIdx + DelIdx}); | ||
| evaluate_retention0(IdxFiles, [{max_age, Age} | Specs], {AccSeg, AccIdx}) -> | ||
| {RemIdxFiles, {DelSeg, DelIdx}} = eval_age(IdxFiles, Age), | ||
| evaluate_retention0(RemIdxFiles, Specs, {AccSeg + DelSeg, AccIdx + DelIdx}). | ||
|
lukas8219 marked this conversation as resolved.
Outdated
|
||
|
|
||
| eval_age([_] = IdxFiles, _Age) -> | ||
| IdxFiles; | ||
| {IdxFiles, {0, 0}}; | ||
| eval_age([IdxFile | IdxFiles] = AllIdxFiles, Age) -> | ||
| case last_timestamp_in_index_file(IdxFile) of | ||
| {ok, Ts} -> | ||
|
lukas8219 marked this conversation as resolved.
Outdated
lukas8219 marked this conversation as resolved.
Outdated
|
||
|
|
@@ -2292,42 +2301,66 @@ eval_age([IdxFile | IdxFiles] = AllIdxFiles, Age) -> | |
| %% the oldest timestamp is older than retention | ||
| %% and there are other segments available | ||
| %% we can delete | ||
| ok = delete_segment_from_index(IdxFile), | ||
| eval_age(IdxFiles, Age); | ||
| {SegSize, IdxSize} = delete_segment_from_index(IdxFile), | ||
| {RemIdxFiles, {AccSeg, AccIdx}} = eval_age(IdxFiles, Age), | ||
| {RemIdxFiles, {AccSeg + SegSize, AccIdx + IdxSize}}; | ||
| false -> | ||
| AllIdxFiles | ||
| {AllIdxFiles, {0, 0}} | ||
| end; | ||
| _Err -> | ||
| AllIdxFiles | ||
| {AllIdxFiles, {0, 0}} | ||
| end; | ||
| eval_age([], _Age) -> | ||
| %% this could happen if retention is evaluated whilst | ||
| %% a stream is being deleted | ||
| []. | ||
| {[], {0, 0}}. | ||
|
|
||
| eval_max_bytes([], _) -> []; | ||
| eval_max_bytes([], _) -> {[], {0, 0}}; | ||
|
lukas8219 marked this conversation as resolved.
Outdated
|
||
| eval_max_bytes(IdxFiles, MaxSize) -> | ||
| [Latest|Older] = lists:reverse(IdxFiles), | ||
| [Latest | Older] = lists:reverse(IdxFiles), | ||
| eval_max_bytes(Older, | ||
| %% for retention eval it is ok to use a file size function | ||
| %% that implicitly return 0 when file is not found | ||
| MaxSize - file_size_or_zero( | ||
| segment_from_index_file(Latest)), | ||
| [Latest]). | ||
| [Latest], | ||
| {0, 0}). | ||
|
|
||
| eval_max_bytes([], _, Acc) -> | ||
| Acc; | ||
| eval_max_bytes([IdxFile | Rest], Limit, Acc) -> | ||
| eval_max_bytes([], _, Acc, DeletedBytes) -> | ||
| {Acc, DeletedBytes}; | ||
| eval_max_bytes([IdxFile | Rest], Limit, Acc, {AccSeg, AccIdx}) -> | ||
| SegFile = segment_from_index_file(IdxFile), | ||
| Size = file_size(SegFile), | ||
| case Size =< Limit of | ||
| true -> | ||
| eval_max_bytes(Rest, Limit - Size, [IdxFile | Acc]); | ||
| eval_max_bytes(Rest, Limit - Size, [IdxFile | Acc], {AccSeg, AccIdx}); | ||
| false -> | ||
| [ok = delete_segment_from_index(Seg) || Seg <- [IdxFile | Rest]], | ||
| Acc | ||
| {TotalSeg, TotalIdx} = | ||
| lists:foldl(fun(Seg, {S, I}) -> | ||
| {SegSize, IdxSize} = delete_segment_from_index(Seg), | ||
| {S + SegSize, I + IdxSize} | ||
| end, | ||
| {AccSeg, AccIdx}, | ||
| [IdxFile | Rest]), | ||
| {Acc, {TotalSeg, TotalIdx}} | ||
| end. | ||
|
|
||
| %% Sums the on-disk sizes of all segment and index files in a log directory. | ||
| %% Used once at writer/replica init to initialise the size counters. | ||
| sum_log_sizes(Config) -> | ||
| IdxFiles = case Config of | ||
| #{index_files := F} -> F; | ||
| #{dir := Dir} -> sorted_index_files(Dir) | ||
| end, | ||
| lists:foldl( | ||
| fun(IdxFile, {SegAcc, IdxAcc}) -> | ||
| SegFile = segment_from_index_file(IdxFile), | ||
| {SegAcc + file_size_or_zero(SegFile), | ||
| IdxAcc + file_size_or_zero(IdxFile)} | ||
| end, | ||
| {0, 0}, | ||
| IdxFiles). | ||
|
|
||
| file_size(Path) -> | ||
| case prim_file:read_file_info(Path) of | ||
| {ok, #file_info{size = Size}} -> | ||
|
|
@@ -2571,6 +2604,8 @@ write_chunk(Chunk, | |
| %% update counters | ||
| counters:put(CntRef, ?C_OFFSET, NextOffset - 1), | ||
| counters:add(CntRef, ?C_CHUNKS, 1), | ||
| counters:add(CntRef, ?C_SEGMENT_SIZE_BYTES, Size), | ||
| counters:add(CntRef, ?C_INDEX_SIZE_BYTES, ?INDEX_RECORD_SIZE_B), | ||
| maybe_set_first_offset(Next, Cfg), | ||
| State#?MODULE{mode = | ||
| Write#write{tail_info = {NextOffset, | ||
|
|
@@ -2806,6 +2841,8 @@ open_new_segment(#?MODULE{cfg = #cfg{name = Name, | |
| {ok, _} = file:position(Fd, eof), | ||
| {ok, _} = file:position(IdxFd, eof), | ||
| counters:add(Cnt, ?C_SEGMENTS, 1), | ||
| counters:add(Cnt, ?C_SEGMENT_SIZE_BYTES, ?LOG_HEADER_SIZE), | ||
| counters:add(Cnt, ?C_INDEX_SIZE_BYTES, ?IDX_HEADER_SIZE), | ||
|
|
||
| State0#?MODULE{current_file = Filename, | ||
| fd = Fd, | ||
|
|
@@ -3170,13 +3207,15 @@ trigger_retention_eval(#?MODULE{cfg = | |
|
|
||
| %% updates first offset and first timestamp | ||
| %% after retention has been evaluated | ||
| EvalFun = fun ({{FstOff, _}, FstTs, NumSegLeft}) | ||
| EvalFun = fun ({{FstOff, _}, FstTs, NumSegLeft, {DelSegBytes, DelIdxBytes}}) | ||
| when is_integer(FstOff), | ||
| is_integer(FstTs) -> | ||
| osiris_log_shared:set_first_chunk_id(Shared, FstOff), | ||
| counters:put(Cnt, ?C_FIRST_OFFSET, FstOff), | ||
| counters:put(Cnt, ?C_FIRST_TIMESTAMP, FstTs), | ||
| counters:put(Cnt, ?C_SEGMENTS, NumSegLeft); | ||
| counters:put(Cnt, ?C_SEGMENTS, NumSegLeft), | ||
| counters:sub(Cnt, ?C_SEGMENT_SIZE_BYTES, DelSegBytes), | ||
| counters:sub(Cnt, ?C_INDEX_SIZE_BYTES, DelIdxBytes); | ||
| (_) -> | ||
| ok | ||
| end, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it's worthwhile to track index data. It would mean an extra fstat for index files during recovery and retention to gather the size data. Since chunk headers are at minimum 48 bytes per chunk, the segment will always be larger (48 > 29 for each index record). If these metrics aim to answer a question of "which stream is taking up all of my data" I think segment size metrics are enough.
I think there's a separate question of "are my chunks so small they're hurting performance" and that could be answered by a bytes-per-chunk metric instead. Tracking index data here would indirectly hint towards that rather than answer it clearly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not strong on any claim here as I also believe that for big clusters, with loads of streams, that's not a cheap price to pay.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@the-mikedavis
To track or not to track it: I thought maybe, if the overhead is not big, to keep going with both. Easier to catch bugs if users have this level of visibility.
Now that was
myselfassuming there's achancethe index file could have holes/get bloated somehow - which could have been naive as Streams are running for lots of years and there's not a single complaint about it.I'll good with removing the index tracking from this PR