-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
360 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package store | ||
|
||
import ( | ||
"errors" | ||
"sync" | ||
) | ||
|
||
var ( | ||
// ErrLogUnmatch is an error causes when the log index can't be used because log file has updated. | ||
ErrLogUnmatch = errors.New("error: log is unmatch to the index") | ||
) | ||
|
||
// indexPeriod is a entry of [indexer]. | ||
type indexPeriod struct { | ||
Start int64 // Start position in the log file in bytes. | ||
End int64 // End position in the log file in bytes. | ||
Since int64 // Minimal timestamp in UNIX time that included in this period. | ||
Until int64 // Maximum timestamp in UNIX time that included in this period. | ||
Size int64 // Number of log records in this period. | ||
} | ||
|
||
// indexer is the index for make faster to read log file. | ||
type indexer struct { | ||
sync.Mutex | ||
|
||
periods []indexPeriod | ||
interval int64 | ||
} | ||
|
||
// newIndexer creates a new [indexer]. | ||
func newIndexer() *indexer { | ||
return &indexer{ | ||
periods: make([]indexPeriod, 1), | ||
interval: 10000, | ||
} | ||
} | ||
|
||
// AppendEntry stores new entry to the [indexer]. | ||
// | ||
// `start` is the head position of record in log file. | ||
// `end` is the tail position of record in log file. | ||
// `time` is the UNIX time of the log entry. | ||
// | ||
// The `start` should equal to the previous entry's `end`. Otherwise, this method returns error because log file could has been updated or rotated. | ||
func (idx *indexer) AppendEntry(start, end, time int64) error { | ||
idx.Lock() | ||
defer idx.Unlock() | ||
|
||
return idx.AppendEntryWithoutLock(start, end, time) | ||
} | ||
|
||
// AppendEntryWithoutLock stores new entry to the [indexer] without lock mutex. | ||
// | ||
// The arguments are the same as AppendEntryWithoutLock. | ||
func (idx *indexer) AppendEntryWithoutLock(start, end, time int64) error { | ||
i := len(idx.periods) - 1 | ||
|
||
if idx.periods[i].End != start { | ||
return ErrLogUnmatch | ||
} | ||
|
||
if idx.periods[i].Size == 0 { | ||
idx.periods[i].Start = start | ||
idx.periods[i].End = end | ||
idx.periods[i].Since = time | ||
idx.periods[i].Until = time | ||
idx.periods[i].Size = 1 | ||
} else if idx.periods[i].Size < idx.interval { | ||
if idx.periods[i].Since > time { | ||
idx.periods[i].Since = time | ||
} | ||
if idx.periods[i].Until < time { | ||
idx.periods[i].Until = time | ||
} | ||
idx.periods[i].Size++ | ||
idx.periods[i].End = end | ||
} else { | ||
idx.periods = append(idx.periods, indexPeriod{ | ||
Start: start, | ||
End: end, | ||
Since: time, | ||
Until: time, | ||
Size: 1, | ||
}) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// AppendInvalidRangeWithoutLock records a range of log file that doesn't contain valid log entry, without lock mutex. | ||
// | ||
// `start` and `end` of arguments are the same meaning to [indexer.AppendEntry]. | ||
func (idx *indexer) AppendInvalidRangeWithoutLock(start, end int64) error { | ||
i := len(idx.periods) - 1 | ||
|
||
if idx.periods[i].End != start { | ||
return ErrLogUnmatch | ||
} | ||
|
||
if idx.periods[i].Size == 0 { | ||
idx.periods[i].End = end | ||
} else { | ||
idx.periods = append(idx.periods, indexPeriod{ | ||
Start: start, | ||
End: end, | ||
}) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Search picks up the ranges in log file that includes specified period by arguments. | ||
func (idx *indexer) Search(since, until int64) []logRange { | ||
idx.Lock() | ||
defer idx.Unlock() | ||
|
||
var ranges []logRange | ||
|
||
for _, x := range idx.periods { | ||
if x.Since <= until && since <= x.Until && x.Size != 0 { | ||
if len(ranges) == 0 || ranges[len(ranges)-1].End != x.Start { | ||
ranges = append(ranges, logRange{ | ||
Start: x.Start, | ||
End: x.End, | ||
Size: x.Size, | ||
}) | ||
} else { | ||
ranges[len(ranges)-1].End = x.End | ||
ranges[len(ranges)-1].Size += x.Size | ||
} | ||
} | ||
} | ||
|
||
return ranges | ||
} | ||
|
||
// logRange is a range in log file. | ||
type logRange struct { | ||
Start int64 // Start position in bytes. | ||
End int64 // End position in bytes. | ||
Size int64 // Number of included log entries. | ||
} | ||
|
||
// Reset resets indexer. | ||
func (idx *indexer) Reset() { | ||
idx.Lock() | ||
defer idx.Unlock() | ||
|
||
idx.ResetWithoutLock() | ||
} | ||
|
||
// ResetWithoutLock resets indexer without lock mutex. | ||
func (idx *indexer) ResetWithoutLock() { | ||
idx.periods = make([]indexPeriod, 1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package store | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
) | ||
|
||
func Test_indexer(t *testing.T) { | ||
idx := newIndexer() | ||
idx.interval = 3 | ||
|
||
if err := idx.AppendEntry(0, 100, 1); err != nil { | ||
t.Fatalf("failed to append log entry: %s", err) | ||
} | ||
|
||
if err := idx.AppendEntry(0, 50, 2); err != ErrLogUnmatch { | ||
t.Fatalf("append log entry should return ErrLogUnmatch but got: %s", err) | ||
} | ||
|
||
for i := int64(2); i <= 4; i++ { | ||
if err := idx.AppendEntry(i*50, i*50+50, i); err != nil { | ||
t.Fatalf("failed to append log entry[%d]: %s", i, err) | ||
} | ||
} | ||
|
||
if err := idx.AppendEntry(250, 300, 10); err != nil { | ||
t.Fatalf("failed to append log entry: %s", err) | ||
} | ||
|
||
for i := int64(6); i <= 7; i++ { | ||
if err := idx.AppendEntry(i*50, i*50+50, i); err != nil { | ||
t.Fatalf("failed to append log entry[%d]: %s", i, err) | ||
} | ||
} | ||
|
||
idx.Lock() | ||
if err := idx.AppendInvalidRangeWithoutLock(400, 500); err != nil { | ||
t.Fatalf("failed to append log entry: %s", err) | ||
} | ||
idx.Unlock() | ||
|
||
if err := idx.AppendEntry(500, 550, 2); err != nil { | ||
t.Fatalf("failed to append log entry: %s", err) | ||
} | ||
|
||
// So far, the entries looks like this. | ||
// | ||
// time start end period | ||
// 1 0 100 0 | ||
// 2 100 150 0 | ||
// 3 150 200 0 | ||
// 4 200 250 1 | ||
// 10 250 300 1 | ||
// 6 300 350 1 | ||
// 7 350 400 2 | ||
// - 400 500 - | ||
// 2 500 550 2 | ||
|
||
tests := []struct { | ||
Since int64 | ||
Until int64 | ||
Want []logRange | ||
}{ | ||
{0, 0, nil}, | ||
{0, 1, []logRange{{0, 200, 3}}}, | ||
{2, 3, []logRange{{0, 200, 3}, {500, 550, 1}}}, | ||
{1, 4, []logRange{{0, 350, 6}, {500, 550, 1}}}, | ||
{5, 8, []logRange{{200, 400, 4}}}, | ||
{10, 20, []logRange{{200, 350, 3}}}, | ||
{11, 20, nil}, | ||
} | ||
|
||
for _, tt := range tests { | ||
if diff := cmp.Diff(tt.Want, idx.Search(tt.Since, tt.Until)); diff != "" { | ||
t.Errorf("unexpected range: interested period is %d-%d\n%s", tt.Since, tt.Until, diff) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.