460
460
% % Default manifest implementation which lists the configured dir for index
461
461
% % files.
462
462
-record (manifest ,
463
- {dir :: file :filename_all (),
464
- index_fd :: file :io_device () | undefined }).
463
+ {name :: osiris :name (),
464
+ directory :: file :filename_all (),
465
+ index_fd :: file :io_device () | undefined ,
466
+ retention :: [retention_spec ()],
467
+ retention_eval_fun :: fun ()}).
465
468
466
469
-opaque state () :: #? MODULE {}.
467
470
-type manifest () :: # manifest {}.
@@ -492,20 +495,10 @@ init(Config) ->
492
495
-spec init (config (), writer | acceptor ) -> state ().
493
496
init (#{dir := Dir ,
494
497
name := Name ,
495
- epoch := Epoch } = Config ,
498
+ epoch := Epoch } = Config0 ,
496
499
WriterType ) ->
497
- % % scan directory for segments if in write mode
498
- MaxSizeBytes = maps :get (max_segment_size_bytes , Config ,
499
- ? DEFAULT_MAX_SEGMENT_SIZE_B ),
500
- MaxSizeChunks = application :get_env (osiris , max_segment_size_chunks ,
501
- ? DEFAULT_MAX_SEGMENT_SIZE_C ),
502
- Retention = maps :get (retention , Config , []),
503
- FilterSize = maps :get (filter_size , Config , ? DEFAULT_FILTER_SIZE ),
504
500
? INFO (" Stream: ~ts will use ~ts for osiris log data directory" ,
505
501
[Name , Dir ]),
506
- ? DEBUG_ (Name , " max_segment_size_bytes: ~b ,
507
- max_segment_size_chunks ~b , retention ~w , filter size ~b " ,
508
- [MaxSizeBytes , MaxSizeChunks , Retention , FilterSize ]),
509
502
ok = filelib :ensure_dir (Dir ),
510
503
case file :make_dir (Dir ) of
511
504
ok ->
@@ -516,26 +509,40 @@ init(#{dir := Dir,
516
509
throw (Err )
517
510
end ,
518
511
519
- Cnt = make_counter (Config ),
512
+ ManifestMod = application :get_env (osiris , log_manifest , ? MODULE ),
513
+ {Info , Config , Manifest0 } = case Config0 of
514
+ #{acceptor_manifest := {I , M }} ->
515
+ {I , Config0 , M };
516
+ _ ->
517
+ Config1 = with_defaults (Config0 ),
518
+ ManifestMod :writer_manifest (Config1 )
519
+ end ,
520
+
521
+ MaxSizeChunks = application :get_env (osiris , max_segment_size_chunks ,
522
+ ? DEFAULT_MAX_SEGMENT_SIZE_C ),
523
+ #{max_segment_size_bytes := MaxSizeBytes ,
524
+ retention := Retention ,
525
+ filter_size := FilterSize ,
526
+ shared := Shared ,
527
+ counter := Cnt ,
528
+ counter_id := CounterId ,
529
+ tracking_config := TrackingConfig } = Config ,
530
+ ? DEBUG_ (Name , " max_segment_size_bytes: ~b ,
531
+ max_segment_size_chunks ~b , retention ~w , filter size ~b " ,
532
+ [MaxSizeBytes , MaxSizeChunks , Retention , FilterSize ]),
520
533
% % initialise offset counter to -1 as 0 is the first offset in the log and
521
534
% % it hasn't necessarily been written yet, for an empty log the first offset
522
535
% % is initialised to 0 however and will be updated after each retention run.
523
536
counters :put (Cnt , ? C_OFFSET , - 1 ),
524
537
counters :put (Cnt , ? C_SEGMENTS , 0 ),
525
- Shared = case Config of
526
- #{shared := S } ->
527
- S ;
528
- _ ->
529
- osiris_log_shared :new ()
530
- end ,
531
538
Cfg = # cfg {directory = Dir ,
532
539
name = Name ,
533
540
max_segment_size_bytes = MaxSizeBytes ,
534
541
max_segment_size_chunks = MaxSizeChunks ,
535
- tracking_config = maps : get ( tracking_config , Config , #{}) ,
542
+ tracking_config = TrackingConfig ,
536
543
retention = Retention ,
537
544
counter = Cnt ,
538
- counter_id = counter_id ( Config ) ,
545
+ counter_id = CounterId ,
539
546
shared = Shared ,
540
547
filter_size = FilterSize },
541
548
DefaultNextOffset = case Config of
@@ -546,14 +553,6 @@ init(#{dir := Dir,
546
553
0
547
554
end ,
548
555
549
- ManifestMod = application :get_env (osiris , log_manifest , ? MODULE ),
550
- {Info , Manifest0 } = case Config of
551
- #{acceptor_manifest := {I , M }} ->
552
- acceptor = WriterType ,
553
- {I , M };
554
- _ ->
555
- ManifestMod :writer_manifest (Config )
556
- end ,
557
556
case Info of
558
557
#{num_segments := 0 } ->
559
558
osiris_log_shared :set_first_chunk_id (Shared , DefaultNextOffset - 1 ),
@@ -638,16 +637,11 @@ init(#{dir := Dir,
638
637
fd = SegFd }
639
638
end .
640
639
641
- writer_manifest (#{dir := Dir } = Config ) ->
642
- ok = filelib :ensure_dir (Dir ),
643
- case file :make_dir (Dir ) of
644
- ok ->
645
- ok ;
646
- {error , eexist } ->
647
- ok ;
648
- Err ->
649
- throw (Err )
650
- end ,
640
+ writer_manifest (#{dir := Dir ,
641
+ name := Name ,
642
+ retention := RetentionSpec ,
643
+ counter := Cnt ,
644
+ shared := Shared } = Config ) ->
651
645
ok = maybe_fix_corrupted_files (Config ),
652
646
Info = case first_and_last_seginfos (Config ) of
653
647
none ->
@@ -681,8 +675,24 @@ writer_manifest(#{dir := Dir} = Config) ->
681
675
active_segment => SegInfo ,
682
676
segment_offsets => SegmentOffsets }
683
677
end ,
678
+ % % updates first offset and first timestamp
679
+ % % after retention has been evaluated
680
+ EvalFun = fun ({{FstOff , _ }, FstTs , NumSegLeft })
681
+ when is_integer (FstOff ),
682
+ is_integer (FstTs ) ->
683
+ osiris_log_shared :set_first_chunk_id (Shared , FstOff ),
684
+ counters :put (Cnt , ? C_FIRST_OFFSET , FstOff ),
685
+ counters :put (Cnt , ? C_FIRST_TIMESTAMP , FstTs ),
686
+ counters :put (Cnt , ? C_SEGMENTS , NumSegLeft );
687
+ (_ ) ->
688
+ ok
689
+ end ,
690
+ Manifest = # manifest {name = Name ,
691
+ directory = Dir ,
692
+ retention = RetentionSpec ,
693
+ retention_eval_fun = EvalFun },
684
694
% % The segment_opened event will create the index fd.
685
- {Info , # manifest { dir = Dir } }.
695
+ {Info , Config , Manifest }.
686
696
687
697
maybe_fix_corrupted_files ([]) ->
688
698
ok ;
@@ -910,16 +920,16 @@ evaluate_tracking_snapshot(#?MODULE{mode = #write{type = writer}} = State0, Trk0
910
920
{State0 , Trk0 }
911
921
end .
912
922
913
- % -spec
914
923
-spec init_acceptor (range (), list (), config ()) ->
915
924
state ().
916
- init_acceptor (Range , EpochOffsets0 , Conf ) ->
925
+ init_acceptor (Range , EpochOffsets0 , Conf0 ) ->
917
926
EpochOffsets =
918
927
lists :reverse (
919
928
lists :sort (EpochOffsets0 )),
920
929
ManifestMod = application :get_env (osiris , log_manifest , ? MODULE ),
921
- {Info , Manifest } = ManifestMod :acceptor_manifest (Range , EpochOffsets ,
922
- Conf ),
930
+ Conf1 = with_defaults (Conf0 ),
931
+ {Info , Conf , Manifest } = ManifestMod :acceptor_manifest (Range , EpochOffsets ,
932
+ Conf1 ),
923
933
InitOffset = case Range of
924
934
empty -> 0 ;
925
935
{O , _ } -> O
@@ -2215,12 +2225,14 @@ format_status(#?MODULE{cfg = #cfg{directory = Dir,
2215
2225
2216
2226
-spec update_retention ([retention_spec ()], state ()) -> state ().
2217
2227
update_retention (Retention ,
2218
- #? MODULE {cfg = # cfg {name = Name ,
2219
- retention = Retention0 } = Cfg } = State0 )
2228
+ #? MODULE {mode = # write {manifest = {ManifestMod , Manifest0 }} =
2229
+ Write0 ,
2230
+ cfg = # cfg {name = Name }} = State0 )
2220
2231
when is_list (Retention ) ->
2221
- ? DEBUG_ (Name , " from: ~w to ~w " , [Retention0 , Retention ]),
2222
- State = State0 #? MODULE {cfg = Cfg # cfg {retention = Retention }},
2223
- trigger_retention_eval (State ).
2232
+ ? DEBUG_ (Name , " updating retention to ~w " , [Retention ]),
2233
+ Manifest = ManifestMod :handle_event ({retention_updated , Retention },
2234
+ Manifest0 ),
2235
+ State0 #? MODULE {mode = Write0 # write {manifest = {ManifestMod , Manifest }}}.
2224
2236
2225
2237
-spec evaluate_retention (file :filename_all (), [retention_spec ()]) ->
2226
2238
{range (), FirstTimestamp :: osiris :timestamp (),
@@ -2518,13 +2530,12 @@ write_chunk(Chunk,
2518
2530
State ) ->
2519
2531
case max_segment_size_reached (State ) of
2520
2532
true ->
2521
- trigger_retention_eval (
2522
- write_chunk (Chunk ,
2523
- ChType ,
2524
- Timestamp ,
2525
- Epoch ,
2526
- NumRecords ,
2527
- open_new_segment (State )));
2533
+ write_chunk (Chunk ,
2534
+ ChType ,
2535
+ Timestamp ,
2536
+ Epoch ,
2537
+ NumRecords ,
2538
+ open_new_segment (State ));
2528
2539
false ->
2529
2540
NextOffset = Next + NumRecords ,
2530
2541
Size = iolist_size (Chunk ),
@@ -2854,6 +2865,24 @@ validate_crc(ChunkId, Crc, IOData) ->
2854
2865
exit ({crc_validation_failure , {chunk_id , ChunkId }})
2855
2866
end .
2856
2867
2868
+
2869
+ -spec with_defaults (config ()) -> config ().
2870
+ with_defaults (Config0 ) ->
2871
+ Shared = case Config0 of
2872
+ #{shared := S } ->
2873
+ S ;
2874
+ _ ->
2875
+ osiris_log_shared :new ()
2876
+ end ,
2877
+ maps :merge (#{max_segment_size_bytes => ? DEFAULT_MAX_SEGMENT_SIZE_B ,
2878
+ retention => [],
2879
+ filter_size => ? DEFAULT_FILTER_SIZE ,
2880
+ shared => Shared ,
2881
+ counter => make_counter (Config0 ),
2882
+ counter_id => counter_id (Config0 ),
2883
+ tracking_config => #{}},
2884
+ Config0 ).
2885
+
2857
2886
-spec make_counter (osiris_log :config ()) ->
2858
2887
counters :counters_ref ().
2859
2888
make_counter (#{counter := Counter }) ->
@@ -3093,28 +3122,6 @@ read_header0(#?MODULE{cfg = #cfg{directory = Dir,
3093
3122
{end_of_stream , State }
3094
3123
end .
3095
3124
3096
- trigger_retention_eval (#? MODULE {cfg =
3097
- # cfg {name = Name ,
3098
- directory = Dir ,
3099
- retention = RetentionSpec ,
3100
- counter = Cnt ,
3101
- shared = Shared }} = State ) ->
3102
-
3103
- % % updates first offset and first timestamp
3104
- % % after retention has been evaluated
3105
- EvalFun = fun ({{FstOff , _ }, FstTs , NumSegLeft })
3106
- when is_integer (FstOff ),
3107
- is_integer (FstTs ) ->
3108
- osiris_log_shared :set_first_chunk_id (Shared , FstOff ),
3109
- counters :put (Cnt , ? C_FIRST_OFFSET , FstOff ),
3110
- counters :put (Cnt , ? C_FIRST_TIMESTAMP , FstTs ),
3111
- counters :put (Cnt , ? C_SEGMENTS , NumSegLeft );
3112
- (_ ) ->
3113
- ok
3114
- end ,
3115
- ok = osiris_retention :eval (Name , Dir , RetentionSpec , EvalFun ),
3116
- State .
3117
-
3118
3125
next_location (undefined ) ->
3119
3126
{0 , ? LOG_HEADER_SIZE };
3120
3127
next_location (# chunk_info {id = Id ,
@@ -3346,13 +3353,22 @@ list_dir(Dir) ->
3346
3353
[list_to_binary (F ) || F <- Files ]
3347
3354
end .
3348
3355
3349
- handle_event ({segment_opened , _OldSegment , NewSegment },
3350
- # manifest {dir = Dir ,
3356
+ handle_event ({segment_opened , OldSegment , NewSegment },
3357
+ # manifest {directory = Dir ,
3351
3358
index_fd = Fd0 } = Manifest ) ->
3352
3359
_ = close_fd (Fd0 ),
3353
3360
IdxFilename = unicode :characters_to_list (
3354
3361
string :replace (NewSegment , " .segment" , " .index" ,
3355
3362
trailing )),
3363
+
3364
+ case OldSegment of
3365
+ undefined ->
3366
+ % % Skip retention evaluation when opening a stream.
3367
+ ok ;
3368
+ _ ->
3369
+ ok = trigger_retention_eval (Manifest )
3370
+ end ,
3371
+
3356
3372
{ok , Fd } =
3357
3373
file :open (
3358
3374
filename :join (Dir , IdxFilename ), ? FILE_OPTS_WRITE ),
@@ -3379,8 +3395,18 @@ handle_event({chunk_written, #chunk_info{id = Offset,
3379
3395
Epoch :64 /unsigned ,
3380
3396
SegmentFilePos :32 /unsigned ,
3381
3397
ChType :8 /unsigned >>),
3398
+ Manifest ;
3399
+ handle_event ({retention_updated , Retention }, Manifest0 ) ->
3400
+ Manifest = Manifest0 # manifest {retention = Retention },
3401
+ trigger_retention_eval (Manifest ),
3382
3402
Manifest .
3383
3403
3404
+ trigger_retention_eval (# manifest {name = Name ,
3405
+ directory = Dir ,
3406
+ retention = RetentionSpec ,
3407
+ retention_eval_fun = EvalFun }) ->
3408
+ ok = osiris_retention :eval (Name , Dir , RetentionSpec , EvalFun ).
3409
+
3384
3410
close_manifest (# manifest {index_fd = Fd }) ->
3385
3411
_ = close_fd (Fd ),
3386
3412
ok .
0 commit comments