-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
support sending eventkit events in batches...
We have a specific Destination implementation which directly saves the Events to BQ (instead of sending via UDP to a daemon, which saves them to BQ). But this endpoint doesn't have any queue. In this patch, I copy pasted the queue logic from the UDP client and made a generic BatchQueue destination. Change-Id: I25ad4c3c2c9a2a261985b9efab1cd12f9acec9cc
- Loading branch information
Showing
5 changed files
with
193 additions
and
30 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
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,107 @@ | ||
package bigquery | ||
|
||
import ( | ||
"context" | ||
"sync" | ||
"sync/atomic" | ||
"time" | ||
|
||
"golang.org/x/sync/errgroup" | ||
|
||
"storj.io/eventkit" | ||
"storj.io/eventkit/utils" | ||
) | ||
|
||
// BatchQueue collects events and send them in batches. | ||
type BatchQueue struct { | ||
batchThreshold int | ||
flushInterval time.Duration | ||
submitQueue chan *eventkit.Event | ||
target eventkit.Destination | ||
mu sync.Mutex | ||
events []*eventkit.Event | ||
droppedEvents atomic.Int64 | ||
} | ||
|
||
// NewBatchQueue creates a new batchQueue. It sends out the received events in batch. Either after the flushInterval is | ||
// expired or when there are more than batchSize element in the queue. | ||
func NewBatchQueue(target eventkit.Destination, queueSize int, batchSize int, flushInterval time.Duration) *BatchQueue { | ||
c := &BatchQueue{ | ||
submitQueue: make(chan *eventkit.Event, queueSize), | ||
batchThreshold: batchSize, | ||
events: make([]*eventkit.Event, 0), | ||
flushInterval: flushInterval, | ||
target: target, | ||
} | ||
return c | ||
} | ||
|
||
// Run implements Destination. | ||
func (c *BatchQueue) Run(ctx context.Context) { | ||
ticker := utils.NewJitteredTicker(c.flushInterval) | ||
var background errgroup.Group | ||
defer func() { _ = background.Wait() }() | ||
background.Go(func() error { | ||
c.target.Run(ctx) | ||
return nil | ||
}) | ||
background.Go(func() error { | ||
ticker.Run(ctx) | ||
return nil | ||
}) | ||
|
||
sendAndReset := func() { | ||
c.mu.Lock() | ||
eventsToSend := c.events | ||
c.events = make([]*eventkit.Event, 0) | ||
c.mu.Unlock() | ||
|
||
c.target.Submit(eventsToSend...) | ||
} | ||
|
||
for { | ||
if drops := c.droppedEvents.Load(); drops > 0 { | ||
mon.Counter("dropped_events").Inc(drops) | ||
c.droppedEvents.Add(-drops) | ||
} | ||
|
||
select { | ||
case em := <-c.submitQueue: | ||
if c.addEvent(em) { | ||
sendAndReset() | ||
} | ||
case <-ticker.C: | ||
if len(c.events) > 0 { | ||
sendAndReset() | ||
} | ||
case <-ctx.Done(): | ||
left := len(c.submitQueue) | ||
for i := 0; i < left; i++ { | ||
if c.addEvent(<-c.submitQueue) { | ||
sendAndReset() | ||
} | ||
} | ||
if len(c.events) > 0 { | ||
c.target.Submit(c.events...) | ||
} | ||
return | ||
} | ||
} | ||
} | ||
|
||
func (c *BatchQueue) addEvent(ev *eventkit.Event) (full bool) { | ||
c.mu.Lock() | ||
defer c.mu.Unlock() | ||
c.events = append(c.events, ev) | ||
return len(c.events) >= c.batchThreshold | ||
} | ||
|
||
// Submit implements Destination. | ||
func (c *BatchQueue) Submit(event *eventkit.Event) { | ||
select { | ||
case c.submitQueue <- event: | ||
return | ||
default: | ||
c.droppedEvents.Add(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,44 @@ | ||
package bigquery | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
"time" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"storj.io/eventkit" | ||
) | ||
|
||
func TestBatchQueue(t *testing.T) { | ||
m := &mockDestination{} | ||
ctx, cancel := context.WithCancel(context.Background()) | ||
defer cancel() | ||
queue := NewBatchQueue(m, 1000, 10, 1*time.Hour) | ||
go func() { | ||
queue.Run(ctx) | ||
}() | ||
for i := 0; i < 25; i++ { | ||
queue.Submit(&eventkit.Event{ | ||
Name: "foobar", | ||
}) | ||
} | ||
require.Eventually(t, func() bool { | ||
return len(m.events) == 2 | ||
}, 5*time.Second, 10*time.Millisecond) | ||
require.Len(t, m.events[0], 10) | ||
require.Len(t, m.events[1], 10) | ||
} | ||
|
||
type mockDestination struct { | ||
events [][]*eventkit.Event | ||
} | ||
|
||
func (m *mockDestination) Submit(event ...*eventkit.Event) { | ||
m.events = append(m.events, event) | ||
} | ||
|
||
func (m *mockDestination) Run(ctx context.Context) { | ||
} | ||
|
||
var _ eventkit.Destination = &mockDestination{} |
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
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